
Раньше, когда трава была зеленей, а доллар стоил 30, в мире enterprise-разработки преимущественно использовались десктопные приложения. Если вспомнить, как выглядели пользовательские интерфейсы в то время, то перед глазами возникает грустная и серая картина из кучи таблиц, кнопок, форм и бесконечно открывающихся экранов поверх других экранов. Также не забудем про тот факт, что бизнес довольно сдержанно относится к любым изменениям, особенно если они требуют дополнительных трат. Из всего этого можно сделать вывод, что красивого UI бизнес-приложений не могло существовать в то время. Однако современные фреймворки, такие как React, позволяют довольно быстро построить красивый и функциональный интерфейс. Но React'ом ли единым? Есть ли другие инструменты для эффективного написания бизнес-приложений?
VK Cloud — облачная платформа, объединяющая современные технологии для разработчиков и бизнеса, нацеленных на эффективное внедрение инновационных решений. В сотрудничестве с партнерами, такими как JMIX, мы стремимся делиться практическим опытом, который помогает компаниям ускорять процесс разработки и оптимизировать затраты на сопровождение приложений. В этой статье мы представляем материал, подготовленный экспертами JMIX и посвященный подходам и лучшим практикам разработки приложений.

Главные требования бизнес-интерфейсов
Для начала предлагаю взглянуть на типичный вариант бизнес-интерфейса на примере приложения из SAPUI5 Demo:

Если кратко — выглядит довольно утилитарно, но, самое главное, свою функцию выполняет. Показывает все доступные заказы с подробной информацией и предоставляет возможность фильтрации. Однако чувствуется, что необходимо приложить руку дизайнера: непонятные отступы и выравнивания в карточке адресата, сепаратор сплиттера съехал от своего предполагаемого места, кнопки управления поиском имеют разный размер и так далее.
Никто, конечно, исправлять этого не будет, так как в enterprise-приложении это, скажем мягко, не самое главное. Пользователи не избалованы суперэстетикой. Основное время разработчик потратит на выполнение требований из области «что делает», а не «как выглядит».
Уже из этого небольшого примера можно выделить несколько требований:
Много данных = много экранов
В обычном бизнес-приложении спокойно может быть более 50 сущностей в модели данных. Соответственно, необходимы экраны для работы с этими сущностями. Если обойтись базовыми CRUD-операциями для каждой сущности, то получается уже около 100 однотипных экранов (экран просмотра + экран редактирования).Функциональность
Каждый бизнес-пользователь хочет видеть при работе с данными любимую excel-функциональность. Но это веб! Чтобы приблизить мечту пользователей, разработчики размещают в интерфейсе различные инструменты взаимодействия с данными: фильтры, логические разделы, сортировки, возможность изменения структуры таблицы и так далее. Это все нагромождает интерфейс.Безопасность
Возможно, вы удивитесь, но в корпоративных приложениях разработчики конфигурируют права доступа до 40% времени работы над проектом! Почему? Смотрите: как минимум нужны две роли для самого простого бизнес-приложения — пользователь системы и администратор системы. Прибавим к этому начальника отдела, пару менеджеров, также специалиста поддержки, которому нужен свой отдельный экран. И если доступ к этому экрану должен быть только у него и у начальника отдела, то в сумме уже выходит более 6 ролей, под которые должны подстраиваться все экраны, загрузчики данных и так далее. Это усложняет компоновку всех необходимых элементов управления для пользователя.Дешевле — значит лучше
Основная задача бизнеса — максимизация прибыли и выполнение проекта в рамках бюджета, а значит, и уменьшение расходов. Поэтому дополнительных средств на «наведение красоты», тем более для внутренних бизнес-систем, как правило, не выделяют.
Технологии
С требованиями разобрались, теперь нужно разобраться с технологиями, которые максимально соответствуют им. С бэкендом все просто. Обычно берется Python с Django или Java со Spring Boot. Однако в крупных enterprise-решениях Java давно зарекомендовала себе как де-факто-стандарт для бэкенда корпоративных приложений. Остановим свой выбор на ней.
А вот с выбором фронтенда все намного сложнее. Нам нужно, чтобы получилось дешево (вспоминаем 4 пункт) и красиво. Популярность и тренды в этом мире никогда не стоят на месте, но за последнее время стандартными решениями являются React, Next, Angular, Vue, Express. Здесь выбор упадет на самый популярный и максимально гибкий вариант — React.
С базой для старта определились, теперь перейдем к определению оппонента. Соперником для выбранных технологий станет Jmix, потому что он позиционирует себя как готовое решение для определенных выше требований для корпоративных приложений. Jmix — fullstack java framework для создания корпоративных приложений.
Для сравнения будут использованы варианты реализации стандартного CRUD приложения Petclinic на Jmix и React + Spring Boot соответственно.
Про архитектуру
Так получается, что будет сравниваться общепризнанный стандарт с нишевым решением. Различия начинаются с уровня архитектуры приложения. С веб-приложением на Java Spring + React все примерно понятно.

Будем строить бэкенд
Сущности
Репозитории для сущностей
Сервисы для работы с сущностями
Валидаторы
DTO для сущностей
Маппинги
Rest-контроллеры
Будем строить фронтенд
Компоненты для редактирования и просмотра списков сущностей
Запросы к API и привязка данных к компонентам
Настройка маршрутов внутри приложения
Настройка валидации UI-компонентов и фильтров
Стилизация
C Jmix все довольно интересно. Это fullstack-решение, бэкенд которого основан на том же Spring Boot. Для конструирования UI используется фреймворк Vaadin Flow, что может поначалу удивить. Может показаться, что происходит ситуация как на картинке снизу, но обо всем по порядку.

Vaadin использует метод SSR, то есть сервер на себе целиком и полностью хранит состояние всего интерфейса. Vaadin Flow — это web-framework, основанный на спецификации Web Components и наборе библиотек Google Polymer. Серверная часть Vaadin написана на Java. Таким образом, в Jmix используется один язык для написания бэкенда и фронтенда приложения.

При таком подходе не может произойти изменения UI state, чтобы сервер в конце концов каким-то запросом синхронизации не узнал о нем. Это решает многие проблемы безопасности. Для написания переиспользуемых компонентов используется концепция custom elements, которая имеет сходство с концепцией компонентов React. Для написания собственных web-компонентов можно использовать JavaScript и TypeScript.
Старт проекта
Если обсуждать широкими мазками, то для старта проекта в случае React придется воспользоваться create-react-app
, самостоятельно сконфигурировать webpack, поставить npm-модули, для Spring Boot создать приложение с помощью Spring Initializr, определить свойства приложения, настроить Spring-конфигурацию.
Тут в копилку fullstack-фреймворка падает первая монета: существует предопределенный набор шаблонов и аддонов. Стандартный шаблон создает fullstack-приложение базовой сущностью пользователя, CRUD экранами для ее редактирования и подсистемой ролей. Такого рода шаблонизация, естественно, ограничивает возможность кастомизации, это необходимо иметь в виду.

Создание экранов
Посмотрим на экран Pets в приложении React. Чтобы получить вот такой вид, пришлось написать простенькую верстку, запросить данные с бэкенда по REST API, смаппить их в компонент строки таблицы и отобразить.

// ...some boilerplate code above
render() {
const { pets, isLoading } = this.state;
if (isLoading) {
return <p>Loading...</p>;
}
const petList = pets.map((pet) => {
return (
<tr key={pet.id}>
<td>{pet.name}</td>
<td>{pet.birthDate}</td>
<td>{pet.type?.name}</td>
<td>{pet.owner.user.username}</td>
<td>
<Button size="sm" color="info" tag={Link}
to={/pets/${pet.id}/visits}>
Visits
</Button>
</td>
<td>
<ButtonGroup>
<Button size="sm" color="primary" tag={Link}
to={"/pets/" + pet.id}>
Edit
</Button>
<Button size="sm" color="danger" onClick={() => this.remove(pet.id)}>
Delete
</Button>
</ButtonGroup>
</td>
</tr>
);
});
return (
<div>
{/* <AppNavbar /> */}
<Container style={{ marginTop: "15px" }} fluid>
<h1 className="text-center">Pets</h1>
<Button color="success" tag={Link} to="/pets/new">
Add Pet
</Button>
<Table className="mt-4">
<thead>
<tr>
<th>Name</th>
<th>Birth Date</th>
<th>Type</th>
<th>Owner</th>
<th>Visits</th>
<th>Actions</th>
</tr>
</thead>
<tbody>{petList}</tbody>
</Table>
</Container>
</div>
);
}
Это максимально простая реализация, с inline-кнопками редактирования. Так сделано для того, чтобы не реализовывать функцию выбора элементов таблицы.
Также присутствуют простенькие кнопки, которые просто делают rerouting на соответствующий экран создания/редактирования Pet. Нажатие на кнопку Remove имеет обработчик по удалению, который рассмотрим чуть позже.
Теперь то же самое с Jmix. В Jmix существует стандартный генератор CRUD-экранов для сущностей. Этот генератор использует готовые шаблоны: наполняет их, основываясь на данных, создает необходимые колонки для столбцов таблицы, создает необходимые поля для редактирования в форме на экране редактирования. Все представленные компоненты основаны на готовой библиотеке компонентов Vaadin Flow.

Что потребовалось сделать, чтобы получить такого вида экран? В концепции Jmix для описания экрана используются контроллер и дескриптор. Контроллер — обычный Java-класс, объекты которого являются экранами, открываемыми на UI. Дескриптор (опциональный) — XML-файл, служащий для декларативного описания верстки экрана, так код становится лаконичнее и его становится меньше. Генератор как раз создает контроллеры и дескрипторы для экранов. Если представить, что нужно создавать экраны для 12+ сущностей, то иметь такой генератор под рукой намного удобнее, чем создавать руками весь макет с нуля.
<layout>
<hbox width="100%" alignItems="BASELINE" classNames="buttons-panel">
<propertyFilter
id="identificationNumberFilter"
property="identificationNumber"
operation="CONTAINS"
dataLoader="petsDl"
labelPosition="TOP"
label="msg://io.jmix.petclinic.entity.pet/Pet.identificationNumber"
/>
<propertyFilter
id="typeFilter"
property="type"
operation="EQUAL"
dataLoader="petsDl"
labelPosition="TOP"
label="msg://io.jmix.petclinic.entity.pet/Pet.type"
/>
<propertyFilter
id="ownerFilter"
property="owner"
operation="EQUAL"
dataLoader="petsDl"
labelPosition="TOP"
label="msg://io.jmix.petclinic.entity.pet/Pet.owner">
<entityComboBox metaClass="petclinic_Owner" itemsContainer="ownersDc" />
</propertyFilter>
<button id="clearFilterBtn" action="clearFilterAction" />
</hbox>
<genericFilter id="genericFilter"
dataLoader="petsDl" opened="false" summaryText="Advanced Filter">
<properties include=".*"/>
</genericFilter>
<hbox id="buttonsPanel" classNames="buttons-panel">
<button id="createBtn" action="petsDataGrid.create"/>
<button id="editBtn" action="petsDataGrid.edit"/>
<button id="removeBtn" action="petsDataGrid.remove"/>
<button id="excelExportBtn" action="petsDataGrid.excelExport"/>
<simplePagination id="pagination" dataLoader="petsDl"/>
</hbox>
<dataGrid id="petsDataGrid"
width="100%"
minHeight="20em"
dataContainer="petsDc">
<actions>
<action id="create" type="list_create"/>
<action id="edit" type="list_edit"/>
<action id="remove" type="list_remove"/>
<action id="excelExport" type="grdexp_excelExport"/>
</actions>
<columns>
<column property="name"/>
<column property="identificationNumber"/>
<column property="birthdate"/>
<column property="type"/>
<column property="owner"/>
</columns>
</dataGrid>
<hbox id="lookupActions" visible="false">
<button id="selectBtn" action="selectAction"/>
<button id="discardBtn" action="discardAction"/>
</hbox>
</layout>
Тут можно увидеть декларативное объявление таблицы, ее действий, кнопок над ними, различных фильтров.
В целом этого достаточно, чтобы экран был полнофункциональным. Из коробки во фреймворке реализованы все стандартные операции для работы с сущностями.
Работа с данными
Для того чтобы получить данные с сервера в приложении на React, необходимо совершить REST-запрос. Например, в вышеописанном экране Pet это происходит следующим образом.
componentDidMount() {
fetch("/api/v1/pets", {
headers: {
"Authorization": Bearer ${this.jwt},
"Content-Type": "application/json",
},
}).then((response) => response.json())
.then((data) => this.setState({ pets: data }));
}
Происходит один запрос на сервер при открытии экрана.
А теперь обратите особое внимание, что происходит по нажатию на кнопку Remove
. Для того чтобы удалить данные, нужно снова сделать запрос на сервер:
async remove(id) {
await fetch(/api/v1/pets/${id}, {
method: "DELETE",
headers: {
"Authorization": Bearer ${this.jwt},
Accept: "application/json",
"Content-Type": "application/json",
},
}).then((response) => {
if (response.status === 200) {
let updatedPets = [...this.state.pets].filter((i) => i.id !== id);
this.setState({ pets: updatedPets });
}
return response.json();
}).then(function (data) {
alert(data.message);
});
}
В целом это несложно, но много кода, если брать в расчет реализацию обработки всех запросов на бекенде.
В Jmix в том же самом дескрипторе используется специальный раздел data
для загрузки данных встроенными механизмами из БД.
<data readOnly="true">
<collection id="petsDc"
class="io.jmix.petclinic.entity.pet.Pet">
<fetchPlan extends="_base">
<property name="type" fetchPlan="_instance_name"/>
<property name="owner" fetchPlan="_instance_name"/>
</fetchPlan>
<loader id="petsDl">
<query>
<![CDATA[select e from petclinic_Pet e]]>
</query>
</loader>
</collection>
<collection id="ownersDc"
class="io.jmix.petclinic.entity.owner.Owner">
<fetchPlan extends="_base"/>
<loader id="ownersDl">
<query>
<![CDATA[select e from petclinic_Owner e]]>
</query>
</loader>
</collection>
</data>
<!-- layout -->
Прямо в верстке указывается JPQL-запрос, по которому данные будут забираться из базы.
Если нужно добавить какой-либо обработчик на события компонентов UI, то это можно сделать в контроллере, с помощью написания Java-кода:
@Route(value = "pets", layout = MainView.class)
@ViewController("petclinic_Pet.list")
@ViewDescriptor("pet-list-view.xml")
@LookupComponent("petsDataGrid")
@DialogMode(width = "50em")
public class PetListView extends StandardListView<Pet> {
@ViewComponent
private PropertyFilter identificationNumberFilter;
@ViewComponent
private PropertyFilter typeFilter;
@ViewComponent
private PropertyFilter ownerFilter;
@Subscribe("clearFilterAction")
public void onClearFilterAction(final ActionPerformedEvent event) {
identificationNumberFilter.clear();
typeFilter.clear();
ownerFilter.clear();
}
}
Разница с React в том, что мы находимся на бэкенде и по нажатию кнопки может произойти обращение к сервису, дополнительная обработка данных или взаимодействие с БД.
Верстка экранов в XML, контроллеры экранов на Java... Может показаться, что это Spring MVC «на стероидах», но это не так. Покажем отличительные черты в виде таблички:
Jmix UI | Spring MVC | |
Технология верстки | Собственная технология верстки, основанная на декларативном создании UI компонентов в XML | Шаблонизаторы HTML (Thymeleaf, Freemarker, etc.) |
Общение с клиентской частью | Передача JSON для конфигурации web-компонентов | Передача статичных HTML |
Технология навигации | Vaadin Flow React Router | Маппинг HTTP запросов на контроллеры |
Кстати, раз речь зашла о технологии навигации, пора поговорить о том, как это происходит в React и Jmix.
Создание роутинга
Хорошо, в приложении есть экраны, на них загружены данные. Теперь взглянем, как происходит навигация между ними.
В React-приложении реализован простой Navbar
, который выглядит как горизонтальное меню в хедере страницы:
return (
<div>
<ErrorBoundary FallbackComponent={ErrorFallback} >
<AppNavbar />
<Routes>
<Route path="/" exact={true} element={<Home />} />
<Route path="/plans" element={<PlanList />} />
<Route path="/docs" element={<SwaggerDocs />} />
{publicRoutes}
{userRoutes}
{adminRoutes}
{ownerRoutes}
{vetRoutes}
</Routes>
</ErrorBoundary>
</div>
);
Чтобы настроить отображение в зависимости от роли пользователя, необходимо отдельно для каждой роли определять доступные роуты:
roles.forEach((role) => {
if (role === "ADMIN") {
adminRoutes = (
<>
<Route path="/users" exact={true} element={<PrivateRoute><UserListAdmin /></PrivateRoute>} />
<Route path="/users/:username" exact={true} element={<PrivateRoute><UserEditAdmin /></PrivateRoute>} />
<Route path="/owners" exact={true} element={<PrivateRoute><OwnerListAdmin /></PrivateRoute>} />
<Route path="/owners/:id" exact={true} element={<PrivateRoute><OwnerEditAdmin /></PrivateRoute>} />
<Route path="/clinics" exact={true} element={<PrivateRoute><ClinicListAdmin /></PrivateRoute>} />
<Route path="/clinics/:id" exact={true} element={<PrivateRoute><ClinicEditAdmin /></PrivateRoute>} />
<Route path="/clinicOwners" exact={true} element={<PrivateRoute><ClinicOwnerListAdmin /></PrivateRoute>} />
<Route path="/clinicOwners/:id" exact={true} element={<PrivateRoute><ClinicOwnerEditAdmin /></PrivateRoute>} />
<Route path="/pets" exact={true} element={<PrivateRoute><PetListAdmin /></PrivateRoute>} />
<Route path="/pets/:id" exact={true} element={<PrivateRoute><PetEditAdmin /></PrivateRoute>} />
<Route path="/pets/:petId/visits" exact={true} element={<PrivateRoute><VisitListAdmin /></PrivateRoute>} />
<Route path="/pets/:petId/visits/:visitId" exact={true} element={<PrivateRoute><VisitEditAdmin /></PrivateRoute>} />
<Route path="/vets" exact={true} element={<PrivateRoute><VetListAdmin /></PrivateRoute>} />
<Route path="/vets/:id" exact={true} element={<PrivateRoute><VetEditAdmin /></PrivateRoute>} />
<Route path="/vets/specialties" exact={true} element={<PrivateRoute><SpecialtyListAdmin /></PrivateRoute>} />
<Route path="/vets/specialties/:specialtyId" exact={true} element={<PrivateRoute><SpecialtyEditAdmin /></PrivateRoute>} />
<Route path="/consultations" exact={true} element={<PrivateRoute><ConsultationListAdmin /></PrivateRoute>} />
<Route path="/consultations/:consultationId" exact={true} element={<PrivateRoute><ConsultationEditAdmin /></PrivateRoute>} />
<Route path="/consultations/:consultationId/tickets" exact={true} element={<PrivateRoute><TicketListAdmin /></PrivateRoute>} />
</>)
}
// etc.
}
В Jmix же подсистема ролей определена таким образом, что сама роль несет в себе информацию о том, какие экраны ей доступны. Для описания ролей используются специальные аннотированные интерфейсы. Вот пример для роли Nurse:
@ResourceRole(name = "Nurse", code = NurseRole.CODE)
public interface NurseRole {
String CODE = "Nurse";
// ... other policies
@ViewPolicy(viewIds = {"petclinic_MyVisits",
"petclinic_Pet.list", "petclinic_Pet.detail",
"petclinic_Owner.list", "petclinic_Owner.detail",
"petclinic_Visit.list", "petclinic_Visit.detail",
"petclinic_Specialty.list", "petclinic_Specialty.detail",
"petclinic_Veterinarian.list", "petclinic_Veterinarian.detail",
"petclinic_PetType.list", "petclinic_PetType.detail", "petclinic_PetType.lookup",})
void views();
@MenuPolicy(menuIds = {"petclinic_MyVisits",
"petclinic_Pet.list",
"petclinic_Owner.list",
"petclinic_Visit.list",
"petclinic_Specialty.list",
"petclinic_Veterinarian.list",
"petclinic_PetType.list"})
void screens();
}
В этом интерфейсе используются специальные аннотации (политики) для настройки доступа роли к сущностям и элементам интерфейса. @MenuPolicy отвечает за разрешение отображения элементов в боковом меню. @ViewPolicy предоставляет доступ роли на экраны. Также существуют различные политики доступа к сущностям, но в рамках этого сравнения они нас не интересуют.
Помимо этого, существует возможность создавать роли в уже запущенном приложении, чтобы не только разработчик, но и администратор системы мог определять свои роли и ограничивать или предоставлять доступ к экранам.
Сами же роутинги определяются с помощью аннотаций @Route
над контроллерами экранов. Такие роутинги поддерживают параметризацию через routeParameters
(например, ID экземпляра редактируемой сущности в экране редактора) или queryParameters
(произвольные параметры состояния экрана).
Чтобы установить порядок экранов в стандартном меню, используется соответствующий XML-дескриптор:
<menu-config xmlns="http://jmix.io/schema/flowui/menu">
<item view="petclinic_MyVisits" title="msg://io.jmix.petclinic.view.visit/myVisitsView.title" icon="CLIPBOARD_USER"/>
<menu opened="true" id="petclinic" title="msg://io.jmix.petclinic/menu.petclinic.title" icon="CLIPBOARD_PULSE">
<item view="petclinic_Pet.list" title="msg://io.jmix.petclinic.view.pet.pet/petListView.title"/>
<item view="petclinic_Owner.list" title="msg://io.jmix.petclinic.view.owner/ownerListView.title"/>
<item view="petclinic_Visit.list" title="msg://io.jmix.petclinic.view.visit/visitListView.title"/>
</menu>
<menu id="application-masterdata" title="msg://io.jmix.petclinic/menu.application-masterdata.title" icon="ARCHIVE">
<item view="petclinic_Specialty.list"
title="msg://io.jmix.petclinic.view.veterinarian.specialty/specialtyListView.title"/>
<item view="petclinic_Veterinarian.list"
title="msg://io.jmix.petclinic.view.veterinarian.veterinarian/veterinarianListView.title"/>
<item view="petclinic_PetType.list" title="msg://io.jmix.petclinic.view.pet.pettype/petTypeListView.title"/>
</menu>
<menu id="security" title="msg://io.jmix.petclinic/menu.security.title" icon="KEY_O">
<item view="User.list" title="msg://io.jmix.petclinic.view.user/UserListView.title"/>
<item view="sec_ResourceRoleModel.list"
title="msg://io.jmix.securityflowui.view.resourcerole/resourceRoleModelListView.menu"/>
<item view="sec_RowLevelRoleModel.list"
title="msg://io.jmix.securityflowui.view.rowlevelrole/rowLevelRoleModelListView.menu"/>
</menu>
<menu id="datatools" title="msg://io.jmix.petclinic/menu.datatools.title" icon="DATABASE">
<item view="datatl_entityInspectorListView"
title="msg://io.jmix.datatoolsflowui.view.entityinspector/listTitle"/>
</menu>
</menu-config>
Здесь не нужно указывать доступные экраны для каждой роли отдельно, достаточно определить общий состав меню.
Так, например, выглядит меню для пользователя с ролью Nurse
:

Здесь нет разделов security
и datatools
, указанных в menu.xml
. Они отфильтрованы встроенной системой безопасности.
Очевидные недостатки
Кастомизация и гибкость
Естественно, при использовании таких предопределенных компонентов и подходов первым делом страдает кастомизация. Приходится идти по той дороге, которую предусмотрели разработчики фреймворка. В React же с этим проблем нет. Используя его, вы можете воплотить интерфейс любой сложности и функциональности.
Хоть и существует возможность самостоятельно стилизовать приложение с помощью CSS, все же макет веб-компонентов будет неизменным. Таким образом, не получится просто добавить какую-то экстрафункциональность для той же таблицы, придется либо переопределять существующий веб-компонент, либо создавать свой.
Если захочется интегрировать JavaScript-библиотеку с визуальным компонентом, то придется писать его серверную и клиентскую реализацию, вместо того чтобы просто воспользоваться напрямую JavaScript компонентом. На то, чтобы самостоятельно встроить его в инфраструктуру фреймворка, может уйти немало времени и сил.

Масштабируемость
Так как это server-side UI, то по стандарту идут повышенные требования к ресурсам, а в первую очередь к серверным ресурсам. При создании сессии пользователя на сервере создается экземпляр пользовательского интерфейса, который будет занимать свою память на сервере. Таким образом, нужно точно знать количество пользователей, которые работают в системе, чтобы предоставить необходимые вычислительные мощности.
Репликация сессий
Помимо этого, из-за хранения UI и сессии на сервере возникает проблема репликации сессий. Во-первых, чтобы работала репликация, необходимо, чтобы все состояние UI было сериализуемо. Базово все стандартные компоненты Vaadin сериализуемы, однако никто не дает гарантии, что какой-то новый кастомный визуальный компонент будет сериализуем.
Во-вторых, мы находимся в Java. Получается, что сессия — Java-объект. Большой и объемный Java-объект, который имеет много ссылок на другие Java-объекты. Таким образом, размер сессии может спокойно достигать нескольких мегабайтов.
В-третьих, каждый клиентский запрос к приложению Jmix приведет к некоторым изменениям в сеансе. Даже в базовых случаях, когда в самом приложении ничего не меняется, все равно есть точки синхронизации, которые обновляют свое состояние. Это означает, что крайне важно, чтобы несколько запросов для одного и того же сеанса никогда не обрабатывались параллельно.
Все это приводит к тому, что стандартные решения репликации сеансов неприменимы в концепции Vaadin и Jmix. Единственным решением в данном случае остается sticky-sessions, что не всегда бывает удобно. Это является довольно серьезной проблемой Vaadin.
Комьюнити
React обладает одним из самых больших комьюнити в мире фронтенд-разработки. Огромное количество готовых решений, ответов на вопросы на форумах и найденных обходных решений могут быть найдены по первой ссылке запроса в Google.
Очевидно, что сообщество разработчиков Jmix мало по сравнению с React. Однако это все активное комьюнити. Существует популярный форум, на котором разработчики фреймворка совместно с активными пользователями оказывают всестороннюю поддержку. Помимо того, существует большое количество обучающих материалов и демоприложений.
Заключение
Выглядит так, будто для построения бизнес-приложений в определенных случаях использование React будет являться избыточным. Как минимум необходимо будет иметь двух специалистов, каждый из которых будет обладать необходимыми навыками, чтобы написать свою часть приложения. При использовании нишевого решения в виде Jmix это можно сделать силами одного разработчика и при этом получить довольно симпатичный, функциональный и современный интерфейс. Различные генераторы кратно ускорят процесс создания однотипных экранов для CRUD-операций. Такое решение не лишено недостатков, некоторые из которых могут быть довольно критичны (например, проблема с репликацией сессий). Несмотря на это, использование Jmix при построении бизнес heavy-data приложений выглядит более рациональным подходом по сравнению с использованием общепризнанного подхода использования Spring Boot + React.