Обзор возможностей

В данном разделе обсуждаются возможности и внутренние особенности фреймворка, обеспечивающие реализацию принципов, изложенных в предыдущем разделе.

Модель данных и метаданные

Подход, основанный на единой модели данных, позволяет избежать дублирования модели на разных уровнях и написания шаблонного кода. В этом разделе объясняется, как Jmix предлагает эффективные методы для работы с единой моделью разнородных данных.

Сборка метаданных

Давайте рассмотрим различные варианты источников данных приложения, от простых до более сложных.

В простейшем случае приложение подключается к одной реляционной базе данных, а его модель данных состоит из некоторого числа взаимосвязанных сущностей JPA:

metadata 1

Иногда данные находятся в нескольких базах данных. Тогда у вас есть несколько модулей персистенции (persistence units) с JPA-сущностями. Если вам нужно подключиться к внешнему источнику данных, вам также понадобятся некоторые POJO или DTO для представления его данных:

metadata 2

Таким образом, в исходном коде у вас могут быть отдельные наборы классов Java, обрабатываемые разными механизмами загрузки и сохранения данных, по-разному структурированные и снабженные разными аннотациями.

Jmix объединяет все эти фрагменты в общую модель данных и рассматривает все классы как просто "сущности" с одинаковыми характеристиками:

metadata 3

Модель данных может даже содержать ссылки между объектами из разных хранилищ (как показано на диаграмме пунктирными линиями), и Jmix помогает поддерживать эти ссылки на уровне приложения.

Общая модель данных с унифицированной информацией обо всех сущностях, атрибутах и связях предоставляется с помощью механизма метаданных. Он сканирует классы Java и представляет сущности с помощью объектов MetaClass, а атрибуты сущностей - с помощью объектов MetaProperty.

На рисунке ниже показан пример того, как несколько JPA-сущностей отражаются в метаданных:

metadata 4

Обратите внимание, что атрибут employeeCount здесь является транзиентным и не обрабатывается JPA. Но в метаданных он имеет тот же набор характеристик, что и другие атрибуты.

Вы можете заметить, что классы Java имеют несколько необычных аннотаций: @JmixEntity, @InstanceName, @Secret. Они специфичны для Jmix: @JmixEntity является обязательной и указывает на то, что класс должен быть включен в метаданные, остальные необязательны и просто предоставляют дополнительную информацию об атрибутах.

Вы можете добавить @JmixEntity в любой POJO, тем самым включив его в метаданные как сущность. Именно так строится общая модель данных - путем аннотирования всех модельных классов независимо от технологии их хранения.

На самом деле, API метаданных редко используется в коде приложения. Когда вы пишете бизнес-логику и код для загрузки и сохранения объектов, вы используете классы Java и их геттеры/сеттеры, то есть пользуетесь статической типизацией и автозавершением кода в среде IDE.

Но унифицированный API для всей модели данных совершенно необходим для универсальных механизмов фреймворка, в первую очередь для UI-компонентов, связанных с данными. Он обеспечивает абстрагирование от различных методов определения характеристик модели данных, поэтому UI-компоненты полностью независимы от Java-классов и аннотаций. Они могут одинаково хорошо работать как непосредственно с объектами JPA, так и с POJO, если вам нужно преобразовать хранимые данные для пользовательского интерфейса.

API метаданных полезен также и для других функций, работающих с моделью данных. Ниже приведен код, который может быть использован гипотетическим решением для аудита для получения всех атрибутов сущности, не являющихся коллекциями:

Class<?> javaClass = // ...
MetaClass metaClass = metadata.getClass(javaClass);
List<MetaProperty> singleValueProperties = metaClass.getProperties().stream()
        .filter(metaProperty ->
                !metaProperty.getRange().getCardinality().isMany())
        .toList();

Сущность в Jmix может быть даже определена динамически во время работы приложения как набор пар ключ-значение. Такие сущности не имеют определенного класса Java, но, тем не менее, могут отображаться и редактироваться в пользовательском интерфейсе. Компонентам UI не нужен класс Java, им нужен только объект, который может считывать/записывать свойства по имени, и соответствующий MetaClass с набором MetaProperty для описания этих свойств.

Enhancement сущностей

Теперь кратко рассмотрим другой аспект единой модели данных, поддерживаемой Jmix: enhancement (усовершенствование) классов сущностей, которое также известно как модификация байт-кода или weaving. Jmix расширяет возможности классов сущностей во время сборки, чтобы обеспечить требуемые характеристики для всех типов сущностей. Эти характеристики включают наличие идентификатора, подходящие методы equals() и hashCode(), возможность считывать/записывать атрибуты объекта по имени и отслеживать изменения значений атрибутов.

Например, объект DTO может не иметь атрибута-идентификатора, если он не хранится в каком-либо постоянном хранилище и существует исключительно в памяти. Но для единообразия с персистентными объектами, Jmix изменяет байт-код объекта и добавляет поле идентификатора с автоматически генерируемым значением. То же самое с equals() и hashCode() - они добавляются в классы сущностей во время сборки с реализацией, основанной на сравнении идентификаторов.

На следующей диаграмме показано, что после enhancement класс сущности User реализует интерфейс Entity, содержит сгенерированные методы equals() и hashCode() и ссылку на объект EntityEntry, который предоставляет общие методы, требуемые фреймворком:

entity enhancement 1

Интересная особенность, которую приобретают все сущности в результате enhancement - это их наблюдаемость. В экземпляре сущности можно зарегистрировать слушателей и получать уведомления об изменениях значений атрибутов данного экземпляра. Это упрощает работу с данными на уровне пользовательского интерфейса: любые изменения в сущностях, вносимые кодом приложения, например user.setFirstName("Joe"), немедленно отображаются связанными UI-компонентами, а измененные сущности автоматически сохраняются в базе данных. Разработчику приложения не нужно писать для этого какой-либо специальный код, это происходит потому, что компоненты фреймворка автоматически отслеживают изменения в сущностях.

Доступ к данным

В этом разделе рассматриваются функции Jmix, связанные с доступом к данным, хранящимся в базах данных или других хранилищах.

DataManager

Jmix предоставляет собственную абстракцию для загрузки и сохранения сущностей: DataManager. Он предоставляет простой fluent API:

// Load all customers
List<Customer> customers = dataManager.load(Customer.class)
        .all()
        .list();

// Load customers by query
List<Customer> customers = dataManager.load(Customer.class)
        .query("e.email like ?1", "%@company.com")
        .maxResults(1000)
        .list();

// Load a customer by id
Customer customer = dataManager.load(Customer.class)
        .id(customerId)
        .one();

// Save a customer instance
dataManager.save(customer);

Однако основной причиной введения DataManager была не его API, а необходимость создания точки, через которую проходят все данные при загрузке из хранилищ и сохранении в них. Он служит центральным узлом, обеспечивающим дополнительную обработку данных для реализации функциональности Jmix.

В типичном сценарии DataManager вызывается непосредственно из экранов пользовательского интерфейса и REST-контроллеров (если вы создаете REST API в своем приложении). И экраны и контроллеры могут делегировать выполнение уровню сервисов, который, в свою очередь, работает с сущностями через DataManager:

data manager 1

Jmix также поддерживает популярный Spring Data API, который позволяет сконцентрировать все методы доступа к данным для конкретной сущности в репозитории. В этом случае интерфейс репозитория должен расширять JmixDataRepository, тогда его реализация также будет делегировать в DataManager:

data manager 2

Jmix не препятствует обходу DataManager и доступу к хранилищам данных через альтернативный API, такой как JPA EntityManager или JDBC:

data manager 3

Однако в этом случае Jmix не сможет перехватить поток данных и предоставить свою расширенную функциональность.

Далее рассматриваются возможности, которые Jmix предоставляет при использовании DataManager.

  1. Одной из ключевых особенностей Jmix является встроенный механизм контроля доступа к данным. По умолчанию DataManager применяет ограничения уровня строк и политики операций с сущностями. При постраничной загрузке сущностей он следит за тем, чтобы на каждой странице (кроме последней) было запрошенное количество строк, даже если некоторые объекты были отфильтрованы ограничениями уровня строк.

    В результате, при написании бизнес-логики вы можете быть уверены, что ваш код работает только с данными, разрешенными для текущего пользователя.

  2. DataManager поддерживает перекрестные ссылки между хранилищами данных, которые позволяют вам связывать сущности, расположенные в разных базах данных, без написания какого-либо кода.

  3. DataManager отправляет события жизненного цикла сущности, которые позволяют выполнять дополнительные действия при загрузке и сохранении экземпляров сущности: копировать данные между хранимыми и транзиентными атрибутами, обновлять связанные сущности, отправлять уведомления и тому подобное.

  4. Ссылки объектов, загруженных с помощью DataManager, могут быть загружены лениво, то есть при первом обращении к ним. Это позволяет легко перемещаться по графу объектов, независимо от того, какой исходный граф был загружен вместе с корневым объектом:

    Order order = dataManager.load(Order.class).id(orderId).one();
    String cityName = order.getCustomer().getAddress().getCity().getName();
  5. DataManager поддерживает подключаемый механизм для интеграции дополнений в процесс загрузки и сохранения данных. Он используется, например, дополнением Динамические атрибуты для добавления динамических атрибутов к экземплярам сущностей и дополнением Поиск для автоматической отправки измененных экземпляров на индексацию.

DataManager не выполняет всю работу самостоятельно. Вместо этого он делегирует фактические задачи загрузки и сохранения реализациям DataStore. Интерфейс DataStore представляет собой абстракцию для конкретной системы хранения, такой как база данных или любой сервис, который может хранить сущности.

Jmix содержит единственную встроенную реализацию интерфейса DataStore: JpaDataStore. Она может работать с сущностями, расположенными в реляционной базе данных, используя EntityManager, предоставляемый JPA (Jakarta Persistence API).

Приложение или дополнение могут предоставлять собственные реализации DataStore для работы с объектами из нереляционных баз данных или из различных веб-сервисов.

Таким образом, сам DataManager служит в основном в качестве шлюза, предоставляя удобный API и направляя запросы реализациям DataStore:

data manager 4

Специфика JPA

В данном разделе рассматривается функциональность DataManager, реализованная в JpaDataStore, и дополняющая стандартные возможности JPA.

Загрузка графов сущностей

Jmix предлагает расширенные способы извлечения графов объектов, которые недоступны в основной реализации JPA на основе Hibernate. Ниже приводится обзор и мотивация, лежащая в основе этих функций. Более подробную информацию см. в разделе Извлечение данных.

Прежде всего, Jmix обеспечивает ленивую загрузку ссылок для detached-сущностей, то есть за пределами начальной транзакции. Вы можете перемещаться по всему графу объектов, обращаясь к ссылочным атрибутам в любое время в бизнес-логике или в связанных с данными UI-компонентах, и Jmix при необходимости дозагрузит связанные сущности из базы данных.

Вторая особенность касается жадной загрузки. Jmix предлагает механизм фетч-планов, аналогичный Entity Graphs в JPA. Фетч-план позволяет управлять набором связанных сущностей, загружаемых вместе с корневой, а также, при необходимости, набором локальных атрибутов для каждого объекта графа. Возможность ограничить набор загружаемых локальных атрибутов может значительно снизить нагрузку на базу данных, особенно в корпоративных приложениях, где сущности с десятками или даже сотнями атрибутов не являются редкостью.

Фетч-планы Jmix предоставляют полностью динамичный способ жадной загрузки объектов модели данных частично, без необходимости вводить какие-либо статические частичные объекты. В отличие от возможностей Jmix, JPA Entity Graphs, реализованные в Hibernate, позволяют определять загружаемый граф только на уровне связанных сущностей. Чтобы ограничить набор локальных атрибутов, необходимо использовать отдельный механизм, например, Spring Data Projections. Такой механизм требует дополнительного шаблонного кода и наводняет вашу модель данных дополнительными DTO, которые служат частичными сущностями.

Возможность загружать частичные сущности для повышения производительности является основной причиной, по которой Jmix использует EclipseLink в качестве реализации JPA. В дополнение к возможностям EclipseLink, Jmix добавляет удобное определение фетч-планов, автоматический выбор режимов выборки (JOIN или BATCH) и ленивую загрузку, которая делегирует в DataManager.

Мягкое удаление

Еще одной отличительной особенностью Jmix, реализованной на уровне JPA, является мягкое удаление. Это популярный подход в корпоративных приложениях, поскольку он помогает снизить риск потери данных из-за неправильных действий пользователя.

Мягкое удаление в Jmix полностью прозрачно для разработчика и легко в использовании. Вы можете просто добавить пару аннотированных атрибутов к сущности, и Jmix запишет в этих атрибутах, кто и когда "удалил" экземпляр сущности, вместо физического удаления строки из таблицы базы данных.

При загрузке сущностей любым JPQL-запросом, мягко удаленные экземпляры будут автоматически отфильтрованы из списка корневых экземпляров сущностей и из всех вложенных коллекций (ссылок one-to-many и many-to-many).

Более того, мягкое удаление в Jmix может быть динамически отключено для конкретной операции. То есть в зависимости от ситуации, вы можете либо загружать только не удаленные экземпляры, либо и не удаленные и мягко удаленные. Когда функция автоматического удаления отключена, операция удаления действительно удаляет строку из базы данных.

См. более подробную информацию в разделе Мягкое удаление.

Пользовательский интерфейс

Следуя принципу фулл-стек разработки, Jmix использует фреймворк Vaadin для разработки пользовательского интерфейса. В этом разделе мы опишем возможности, которые Jmix предоставляет в дополнение к Vaadin для повышения эффективности создания корпоративных приложений с большими моделями данных и UI.

Экраны

Пользовательский интерфейс Jmix-приложения состоит из некоторого количества экранов (views). Экран - это отдельная часть UI, которая выполняет определенную функцию. Например, экран может отображать список клиентов или управлять атрибутами клиентов.

Jmix предоставляет набор базовых классов для экранов, типичных для корпоративных приложений.

  • StandardMainView помогает создать основной экран, который определяет корневую веб-страницу с главным меню приложения.

  • StandardView - это базовый класс общего назначения, который можно использовать для создания любого экрана, открываемого из основного экрана.

  • StandardListView и StandardDetailView являются подклассами StandardView, предназначенными для управления сущностями модели данных.

Экраны в Jmix имеют некоторые особенности, которые подробно обсуждаются ниже.

Экран может быть сопоставлен с URL, тогда его можно открывать внутри основного экрана, переходя по этому URL. Кроме того, Jmix позволяет открывать этот же экран в диалоговом окне, которое всплывает на текущей странице, не изменяя URL. В первом случае предыдущий экран будет закрыт, во втором случае URL останется неизменным, а предыдущий экран останется открытым.

Эта возможность предназначена для решения двух задач, типичных для корпоративных приложений: выбор связанных сущностей и редактирование агрегатов.

Первое требование рассматривается ниже, а второе в отдельном разделе.

Обычно для выбора связанных сущностей веб-приложения предлагают использовать выпадающие списки. Например, когда пользователь создает заказ, он может выбрать связанного клиента из списка, отображающего имена всех клиентов. Но что если клиент должен быть найден не по имени, а по его ИНН или какому-то другому атрибуту? Или клиент еще не зарегистрирован и должен быть введен вместе с заказом?

Jmix предлагает универсальное решение проблемы выбора связанных сущностей: позволяет пользователям открыть CRUD-экран для желаемой сущности в диалоговом окне, найти экземпляр сущности и вернуть его оттуда. Это функция реализована с помощью специального действия в UI-компонентах выбора сущностей. По умолчанию, оно использует тот же CRUD-экран, что и для управления сущностями, но вы можете создать специальный экран для поиска.

Открытие экрана поиска в диалоге, который не уничтожает исходный экран, делает максимально простым возвращение результатов из открытого экрана — они передаются как объекты Java на серверной стороне.

Диалоговые окна с экранами поиска могут автоматически накладываться друг на друга, предоставляя возможность углубления в модель данных без потери начального контекста. Например, когда создается заказ, пользователь может открыть список клиентов в диалоге, затем создать клиента в отдельном диалоге, затем создать контакт клиента в своем диалоге, а затем выбрать клиента и продолжить редактирование заказа. Jmix обеспечивает эту функциональность из коробки, используя имеющиеся CRUD-экраны, созданные для управления сущностями.

XML-дескрипторы

Содержимое экрана может быть определено в XML. Это подход существенно уменьшает количество кода, необходимого для создания корректной структуры UI-компонентов. Кроме того, для нетривиальных экранов читаемость XML намного выше, чем императивного кода, который инициализирует компоненты, задает их свойства, добавляет компоненты в контейнеры и назначает слушатели событий.

XML был выбран из-за его следующих достоинств:

  • Обеспечивает полноценный синтаксис для описания дерева UI-компонентов: элементы для компонентов и атрибуты для их свойств. Поддерживает комментарии.

  • Может быть провалидирован с помощью XSD. IDE обеспечивает автодополнение кода на основе XSD без применения дополнительных средств.

  • Расширяем с помощью пространств имен.

  • Может быть легко сгенерирован, прочитан и преобразован.

  • Широко известен разработчикам.

Обычно экран указывает на XML-файл со своим определением с помощью аннотации @ViewDescriptor на классе экрана. После инициализации экрана фреймворк читает XML и строит соответствующее дерево компонентов. Класс экрана может содержать методы, связанные с компонентами: слушатели событий и делегаты, которые обсуждаются в следующей секции. Компоненты экрана могут быть инжектированы в поля класса, поэтому методы экрана могут легко обращаться к компонентам и их свойствам.

Обработчики

Экран имеет определенный набор событий жизненного цикла и предоставляет декларативный способ подписки на все события UI (генерируемые экраном и его компонентами) с помощью аннотированных методов.

Слушатели событий помечены аннотацией @Subscribe, например:

@Subscribe
public void onReady(ReadyEvent event) {
    // the view is ready to be shown
}

Для подписки на событие компонента в аннотации указывается идентификатор компонента:

@Subscribe("generateButton")
public void onGenerateButtonClick(ClickEvent<Button> event) {
    // the button with `generateButton` id is clicked
}

При загрузке экрана Jmix автоматически создает MethodHandle для каждого аннотированного метода и добавляет его как слушатель для соответствующего компонента. Поэтому примеры выше являются декларативной заменой следующего императивного кода:

@ViewComponent
private JmixButton generateButton;

private void assignListeners() {
    addReadyListener(this::onReady);
    generateButton.addClickListener(this::onGenerateButtonClick);
}

public void onReady(ReadyEvent event) {
    // the view is ready to be shown
}

public void onGenerateButtonClick(ClickEvent<Button> event) {
    // the button with `generateButton` id is clicked
}

Подход Jmix с аннотированными методами уменьшает количество шаблонного кода и позволяет IDE надежно связывать UI-компоненты с их обработчиками. В результате, Jmix Studio содержит панель инспектора компонентов, которая отображает все существующие обработчики для компонента, позволяет переходить к их исходному коду и создавать новые.

Есть еще две аннотации, похожие на @Subscribe: @Install и @Supply. Они обозначают методы, которые не связаны с конкретными событиями, а просто должны быть вызваны компонентами для определенной цели. Например, следующий метод вызывается текстовым полем для валидации введенного значения:

@Install(to = "usernameField", subject = "validator")
private void usernameFieldValidator(final String value) {
    // check the field value
}

Состояние экрана

Jmix предоставляет набор абстракций для работы с состоянием экрана. Они позволяют координировать загрузку и сохранение данных, а также декларативно связывать UI-компоненты с загруженными сущностями.

Привязка к данным

Центральным элементом этой функциональности являются контейнеры данных, которые хранят сущности, загруженные в экран. Существует два типа контейнеров данных: InstanceContainer хранит единичный экземпляр сущности, а CollectionContainer хранит список сущностей.

Контейнеры данных обычно объявляются в XML-коде экрана, вместе с деревом UI-компонентов. Это позволяет декларативно связывать UI-компоненты с сущностями и их атрибутами, загруженными в контейнеры данных:

<data>
    <instance id="userDc" class="com.company.onboarding.entity.User"> (1)
        <collection id="stepsDc" property="steps"/> (2)
    </instance>
</data>
<layout>
    <textField id="usernameField" dataContainer="userDc" property="username"/> (3)

    <dataGrid id="stepsDataGrid" dataContainer="stepsDc"> (4)
        <columns>...</columns>
    </dataGrid>
1 Контейнер данных userDc хранит экземпляр сущности User.
2 Вложенный контейнер данных stepsDc связан с коллекцией steps сущности User. Вложенные контейнеры данных отражают загруженный граф объектов.
3 Текстовое поле редактирует свойство username сущности User, находящейся в контейнере данных userDc.
4 Таблица данных отображает коллекцию экземпляров Step, находящихся в контейнере данных stepsDc.

Помимо привязки к данным UI-компонентов, контейнеры данных предоставляют события изменения состояния, которые можно использовать в логике экрана. Например, событие ItemPropertyChangeEvent уведомляет об изменении значения атрибута сущности. Это событие не отсылается при заполнении экрана начальными значениями, поэтому оно лучше подходит для отслеживания изменений состояния, чем события изменения значений, отсылаемые UI-компонентами.

Загрузка данных

Контейнеры данных можно заполнять программно, используя их методы setItem() и setItems(). Но обычно контейнеры используются совместно с другой абстракцией пользовательского интерфейса Jmix - загрузчиками данных.

В XML-коде экрана загрузчики данных определяются внутри связанных с ними контейнеров данных:

<collection id="departmentsDc" class="com.company.onboarding.entity.Department">
    <loader id="departmentsDl">
        <query>
            <![CDATA[select e from Department e]]>
        </query>
    </loader>
</collection>

В приведенном выше примере загрузчик содержит JPQL-запрос, который будет передан в DataManager для загрузки JPA-сущностей.

Загрузчик может делегировать логику загрузки аннотированному методу экрана, например:

@Install(to = "departmentsDl", target = Target.DATA_LOADER)
private List<Department> departmentsDlLoadDelegate(LoadContext<Department> loadContext) {
    return departmentService.loadAllDepartments();
}

Такой делегат позволяет загружать сущности из произвольного сервиса или хранилища данных.

Цель загрузчика - собрать критерии загрузки (ID, запрос, условия, разбивка на страницы, сортировка, фетч-план и т.д.) в объекте LoadContext, вызвать DataManager или метод-делегат и заполнить связанный контейнер данных загруженными сущностями.

Сохранение данных

Jmix UI предлагает механизм для автоматического сохранения сущностей, измененных в экране. Он основан на интерфейсе DataContext.

Экран создает единственный экземпляр DataContext, и все загрузчики данных регистрируют в нем сущности перед передачей их в контейнеры данных.

Стандартная реализация DataContext поддерживает в памяти структуру со ссылками на все сущности, загруженные в экран. Когда сущность создается, обновляется или удаляется в UI, контекст данных помечает ее как "грязную".

Когда пользователь сохраняет экран (например, нажимая кнопку OK), экран вызывает метод DataContext.save(), и контекст данных сохраняет "грязные" сущности с помощью DataManager или путем вызова метода-делегата, определенного в экране.

Объекты DataContext могут образовывать иерархии, при этом дочерние контексты сохраняют изменения в родительский контекст, а не напрямую в бэкенд-слой. Эта функция играет ключевую роль при редактировании агрегатов, о котором говорится в следующем разделе.

Редактирование агрегатов

Модель данных может содержать сложные структуры, называемые агрегатами. Хорошее объяснение этой концепции, введенной в подходе Domain Driven Design, можно найти в этой статье.

Давайте рассмотрим модель, содержащую сущности Customer, Order, OrderLine и Product. Каждый экземпляр OrderLine создается для конкретного Order и становится его частью, то есть данный экземпляр OrderLine не может принадлежать другому Order. В то же время, Customer и Product — независимые сущности, на которые могут быть ссылки из различных экземпляров других сущностей. Поэтому сущности Order и OrderLine образуют агрегат, при этом Order является корнем агрегата:

aggregate 1

Состояние агрегата всегда должно быть согласованным, поэтому экземпляры OrderLine должны обновляться вместе с их владельцем Order в одной транзакции. С точки зрения пользователя, изменения в строках заказа должны быть сохранены только после подтверждения изменений во всем заказе.

Jmix позволяет организовывать редактирование агрегатов с помощью простых CRUD-экранов без необходимости писать специальный код. Вам нужно только пометить ссылку от корня агрегата к его компонентам с помощью аннотации @Composition. Например:

@JmixEntity
@Entity(name = "Order_")
public class Order {
    // ...

    @Composition
    @OneToMany(mappedBy = "order")
    private List<OrderLine> lines;

Эта аннотация добавляется, когда вы указываете COMPOSITION как тип атрибута в дизайнере сущностей Studio.

После этого, при редактировании сущностей Order и OrderLine с помощью экранов деталей (detail view), Jmix устанавливает parent-child отношения между контекстами данных этих экранов. В результате, когда пользователь подтверждает изменения в экране OrderLine, обновляются соответствующие экземпляры в родительском экране Order. И только после подтверждения всего заказа в экране Order, агрегат целиком отправляется в бэкенд-слой для сохранения в базе данных в одной транзакции.

Jmix поддерживает и многоуровневые агрегаты. В предыдущем примере, для каждого OrderLine могла бы существовать некая коллекция заметок. Тогда для включения сущности Note в агрегат с корнем в Order, вам нужно было бы просто пометить ссылку от OrderLine к Note с помощью аннотации @Composition.

Безопасность

Эффективная система безопасности и контроля доступа к данным является важным компонентом любого корпоративного приложения. Jmix создан с упором на требования безопасности и предлагает следующие возможности:

  • Готовая конфигурация аутентификации на основе Spring Security.

  • Глубоко проработанный механизм контроля доступа к данным.

  • Встроенное управление ролями и разрешениями.

Концепции безопасности Jmix подробно описаны в разделе Безопасность. В данном разделе будет рассмотрено только взаимосвязь подсистемы безопасности с основными принципами Jmix.

  • Фулл-стек архитектура Jmix, подразумевающая использование Java от бэкенда до UI позволяет обеспечить полностью интегрированный декларативный контроль доступа, которым легко управлять.

    Например, чтобы запретить атрибут сущности для некоторого пользователя, достаточно просто отменить разрешение на этот атрибут в роли, выданной данному пользователю. Экраны, отображающие этот атрибут в UI-компонентах (текстовых полях, колонках таблиц данных и т.д.) автоматически сделают эти компоненты невидимыми. В результате, значение атрибута не будет передаваться по сети и не будет доступно в браузере пользователя.

    То же самое для безопасности уровня строк (row-level security): достаточно написать политику на основе JPQL и/или предиката, и DataManager отфильтрует списки соответствующей сущности, независимо от места и способа запроса этой сущности: через DataManager или репозиторий данных, используя жадную или ленивую загрузку данных, как корневую сущность графа или как атрибут-коллекцию другой сущности.

  • Единая модель данных способствует простоте управления безопасностью. Контроль доступа к данным не разбросан по всей кодовой базе в виде аннотаций и операторов "if". Вместо этого, он сосредоточен вокруг консистентной структуры сущностей, их атрибутов и операций.

  • Подсистема безопасности — это широко используемый готовый компонент Jmix. Она работает из коробки в большинстве сценариев.

  • Механизм аутентификации Jmix основан на популярном фреймворке Spring Security и позволяет разработчикам использовать свои существующие знания для его настройки и для интеграции со сторонними решениями для аутентификации.

  • Подсистема безопасности Jmix легко расширяема. Ее аутентификационная часть может быть полностью перенастроена благодаря Spring Security. Механизм авторизации позволяет реализовать кастомное управление доступом на основе произвольных атрибутов (ABAC), если это необходимо.