Три года назад мы анонсировали вторую общедоступную основную версию фреймворка. CUBA 6 была изменяющей версию игры — лицензия была переведена с проприетарной на Apache 2.0. В те дни мы даже не могли догадаться, куда это приведет в долгосрочной перспективе. Сообщество CUBA начало расти в геометрической прогрессии, поэтому мы изучили множество возможных (а иногда и невозможных) способов того, как разработчики используют фреймворк. Теперь мы рады объявить о CUBA 7 , которая, как мы надеемся, сделает разработку более понятной и радостной для всех членов сообщества, начиная с тех, кто только начинает свой путь в CUBA и Java, и готовит опытных корпоративных разработчиков и экспертов по Java.

Инструменты разработки
Очевидно, что большая часть успеха CUBA мы обязаны CUBA Studio . Она значительно упростила перегруженную корпоративную подпрограмму Java, во многих местах основав ее на создании тривиальных конфигураций в визуальных дизайнерах: не нужно знать Persistence API, Gradle или даже Spring для разработки законченного и многофункционального приложения CRUD — Studio сделает это для вас.

Студия была отдельным веб-приложением, и этот факт вызвал некоторые существенные ограничения:
- Прежде всего, Studio не была полнофункциональной IDE, поэтому разработчикам приходилось переключаться между Studio и IntelliJ IDEA или Eclipse, чтобы разрабатывать бизнес-логику и получать выгоду от удобной навигации, дополнения кода и других важных вещей, что раздражало.
- Во-вторых, эта волшебная простота была построена на огромном разборе и генерации исходного кода. Улучшение возможностей генерации кода будет означать переход к разработке полнофункциональной IDE — слишком амбициозного обязательства.
Мы решили опереться на плечо другого гиганта, чтобы преодолеть эти ограничения. Студия была объединена с IntelliJ IDEA компанией JetBrains. Теперь вы можете установить его как плагин для вашей IntelliJ IDEA или загрузить в виде отдельного отдельного пакета.

Это открывает новые горизонты:
- Поддержка других языков JVM (и прежде всего Kotlin)
- Улучшено горячее развертывание
- Интуитивно понятная навигация по всему проекту
- Умные подсказки и генераторы кода
В настоящее время новая студия находится в стадии активной разработки: мы переносим функции из старой версии. Краткосрочный план также состоит в том, чтобы повторно внедрить веб-дизайнеров с использованием встроенного пользовательского интерфейса IntelliJ и улучшить возможности навигации по проекту.
Обновление стека
Традиционно базовый стек также был значительно обновлен, например, Java 8/11, Vaadin 8, Spring 5.

По умолчанию новые проекты используют Java 8, но вы можете указать версию Java, добавив следующее предложение в файл build.gradle:
|
1
2
3
4
|
subprojects { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11} |
Обновление до Vaadin 8 было большой проблемой из-за серьезных изменений в API привязки данных Vaadin. К счастью, CUBA абстрагирует разработчиков от внутренних компонентов Vaadin, обернув его в свой собственный уровень API. Команда CUBA проделала огромную работу, реализовав внутренние компоненты, не трогая собственный API. Это означает, что совместимость полностью сохранена, и вы можете воспользоваться Vaadin 8 сразу после переноса проекта на CUBA 7 без какого-либо рефакторинга.
Полный список обновленных зависимостей доступен в официальных примечаниях к выпуску .
API новых экранов
Этот раздел также может называться «API первых экранов» — поскольку у CUBA никогда не было официально объявленного API на уровне веб-клиента. Это происходит из истории структуры и некоторых предположений, которые были сделаны на первом этапе:
Декларативно-ориентированный подход — все, что можно описать декларативно, должно быть объявлено в дескрипторе экрана, а не закодировано в его контроллере
Стандартные экраны (Браузер и Редактор) предоставляют конкретную общую функциональность, и нет необходимости изменять ее
С тех пор, как к нашему сообществу присоединилась первая тысяча участников, мы поняли, насколько разнообразны требования к «стандартным» экранам CRUD — далеко за пределы изначально разработанного набора функций. Тем не менее, в течение долгого времени мы могли обрабатывать запросы на нестандартное поведение даже без уровня API — благодаря другому предположению первого этапа — открытому наследованию. Эффективно открытое наследование означает, что вы можете переопределить любой открытый или защищенный метод базового класса, чтобы адаптировать его поведение к тому, что вам нужно. Это может звучать как лекарство от всех болезней, но на самом деле это не дает вам даже краткосрочного контракта: что если переопределенный метод будет переименован, удален или просто никогда не будет использоваться в будущих версиях фреймворка?

|
1
2
3
|
@UiController("new-screen") // screen idpublic class NewScreen extends Screen {} |
Из приведенного выше примера мы видим, что идентификатор экрана явно определен прямо над классом контроллера. Другими словами, идентификатор экрана и класс контроллера теперь однозначно соответствуют друг другу. Итак, хорошая новость, теперь к экранам можно обращаться напрямую через класс контроллера:
|
01
02
03
04
05
06
07
08
09
10
|
@Injectprivate ScreenBuilders screenBuilders;@Subscribeprivate void onBeforeClose(BeforeCloseEvent event) { screenBuilders.screen(this) .withScreenClass(SomeConfirmationScreen.class) .build() .show();} |
Экранный дескриптор становится дополнительной, а не обязательной. Макет может быть создан программно или объявлен как дескриптор экрана XML, который определяется аннотацией @UiDescriptor над классом контроллера. Это значительно облегчает чтение и понимание контроллеров и макетов — этот подход очень похож на тот, который используется в разработке для Android.
Ранее также требовалось зарегистрировать дескриптор экрана в файле web-screens.xml и присвоить ему идентификатор. В CUBA 7 этот файл хранится по причинам совместимости, однако создание экранов по-новому не требует такой регистрации.
Жизненный цикл экранов
Новый API представляет понятные и понятные события жизненного цикла экрана:
- В этом
- AfterInit
- BeforeShow
- Aftershow
- BeforeClose
- AfterClose
Все связанные с экраном события в CUBA 7 могут быть подписаны следующим образом:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
@UiController("new-screen")public class NewScreen extends Screen { @Subscribe private void onInit(InitEvent event) { } @Subscribe private void onBeforeShow(BeforeShowEvent event) { }} |
Сравнивая новый API со старым подходом, вы можете видеть, что мы не переопределяем перехватчики, которые непонятно вызываются в иерархии родительских классов, но определяют логику в четких предопределенных точках жизненного цикла экрана.
Обработка событий и функциональные делегаты
В предыдущем разделе мы узнали, как подписаться на события жизненного цикла, а как насчет других компонентов? Должны ли мы по-прежнему рассеивать всех необходимых слушателей при инициализации экрана, как это было в версиях 6.x? Новый API очень унифицирован, поэтому подписка на другие события абсолютно аналогична тем, что происходят в жизненном цикле.
Давайте рассмотрим простой пример с двумя элементами пользовательского интерфейса: кнопкой и полем валюты, поэтому его XML-дескриптор выглядит следующим образом:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?> caption="msg://caption" messagesPack="com.company.demo.web"> <layout> <hbox spacing="true"> <currencyField id="currencyField" currency="$" currencyLabelPosition="LEFT"/> <button id="calcPriceBtn" caption="Calculate Price"/> </hbox> </layout></window> |
При нажатии на кнопку мы вызываем службу промежуточного программного обеспечения, возвращая номер, который переходит в поле валюты. Поле валюты должно изменить свой стиль в зависимости от значения цены.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
@UiController("demo_MyFirstScreen")@UiDescriptor("my-first-screen.xml")public class MyFirstScreen extends Screen { @Inject private PricingService pricingService; @Inject private CurrencyField<bigdecimal> currencyField; @Subscribe("calcPriceBtn") private void onCalcPriceBtnClick(Button.ClickEvent event) { currencyField.setValue(pricingService.calculatePrice()); } @Subscribe("currencyField") private void onPriceChange(HasValue.ValueChangeEvent<bigdecimal> event) { BigDecimal price = pricingService.calculatePrice(); currencyField.setStyleName(getStyleNameByPrice(price)); } private String getStyleNameByPrice(BigDecimal price) { ... } }</bigdecimal></bigdecimal> |
В приведенном выше примере мы видим два обработчика событий: один вызывается при нажатии кнопки, а другой запускается, когда поле валюты меняет свое значение — все так просто.
Теперь давайте представим, что нам нужно проверить нашу цену и проверить, является ли ее значение положительным. Прямой способ — добавить валидатор во время инициализации экрана:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
@UiController("demo_MyFirstScreen")@UiDescriptor("my-first-screen.xml")public class MyFirstScreen extends Screen { @Inject private CurrencyField<BigDecimal> currencyField; @Subscribe private void onInit(InitEvent event) { currencyField.addValidator(value -> { if (value.compareTo(BigDecimal.ZERO) <= 0) throw new ValidationException("Price should be greater than zero"); }); }} |
В реальных приложениях точка входа экрана обычно засоряется такими инициализаторами элементов экрана. Для решения этой проблемы CUBA предоставляет полезную аннотацию @Install . Давайте посмотрим, как это может помочь в нашем случае:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
@UiController("demo_MyFirstScreen")@UiDescriptor("my-first-screen.xml")public class MyFirstScreen extends Screen { @Inject private CurrencyField<BigDecimal> currencyField; @Install(to = "currencyField", subject = "validator") private void currencyFieldValidator(BigDecimal value) { if (value.compareTo(BigDecimal.ZERO) <= 0) throw new ValidationException("Price should be greater than zero"); } } |
Фактически, мы делегируем логику проверки из нашего поля валюты в метод currencyFieldValidator на нашем экране. Это может показаться немного сложным, однако разработчики применяют эту функцию на удивление быстро.
Построители экрана / Уведомления / Диалоги

CUBA 7 также представляет набор полезных компонентов с открытыми API:
- ScreenBuilders объединяет свободные фабрики для создания стандартных поисков, редакторов и пользовательских экранов. Пример ниже показывает, как вы можете открыть один экран с другого. Обратите внимание, что метод build () возвращает экземпляр экрана нужного типа без необходимости небезопасного приведения его.
|
1
2
3
4
5
6
|
CurrencyConversions currencyConversions = screenBuilders.screen(this) .withScreenClass(CurrencyConversions.class) .withLaunchMode(OpenMode.DIALOG) .build();currencyConversions.setBaseCurrency(Currency.EUR);currencyConversions.show(); |
- Компонент Screens обеспечивает низкоуровневую абстракцию для создания и отображения экранов, а не ScreenBuilders . Он также предоставляет доступ к информации обо всех открытых экранах в вашем приложении CUBA ( Screens # getOpenedScreens ) на случай, если вам потребуется выполнить их итерацию.
- Компоненты « Уведомления» и « Диалоги» предоставляют удобный интуитивно понятный интерфейс. Вот пример для создания и отображения диалогового окна и уведомления:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
dialogs.createOptionDialog() .withCaption("My first dialog") .withMessage("Would you like to thank CUBA team?").withActions( new DialogAction(DialogAction.Type.YES).withHandler(e -> notifications.create() .withCaption("Thank you!") .withDescription("We appreciate all community members") .withPosition(Notifications.Position.MIDDLE_CENTER) .withHideDelayMs(3000) .show()), new DialogAction(DialogAction.Type.CANCEL)) .show(); |
Привязка данных
CUBA позволяет чрезвычайно быстро разрабатывать пользовательские интерфейсы Backoffice не только благодаря расширенным визуальным инструментам с широкими возможностями генерации кода, но также благодаря широкому набору компонентов, учитывающих данные, доступных прямо из коробки. Такие компоненты просто должны знать, с какими данными они работают, а остальные будут управляться автоматически, например, списки поиска, поля выбора, различные таблицы с операциями CRUD и так далее.
До версии 7 связывание данных было реализовано через так называемые источники данных — объекты, которые обертывают одну сущность или набор сущностей, чтобы реактивно связать их с компонентами, учитывающими данные. Этот подход работал очень хорошо, однако с точки зрения реализации это был монолит. Монолитная архитектура обычно вызывает проблемы с ее настройкой, поэтому в CUBA 7 этот твердый валун был разделен на 3 компонента данных:
- Загрузчик данных — это поставщик данных для контейнеров данных. Загрузчики данных не хранят данные, они просто передают все необходимые параметры запроса в хранилище данных и передают контейнеры данных с результирующим набором данных.
- Контейнер данных хранит загруженные данные (один объект или несколько объектов) и предоставляет их компонентам, учитывающим данные, реагирующим образом: все изменения обернутых объектов становятся доступными для соответствующих компонентов пользовательского интерфейса и наоборот, все изменения в компоненты пользовательского интерфейса приведут к соответствующим изменениям в его контейнере данных.
- Контекст данных — это мощный менеджер модификации данных, который отслеживает изменения и фиксирует все измененные объекты. Сущность может быть объединена с контекстом данных, поэтому она будет предоставлять копию исходной сущности с единственным, но очень важным отличием: все модификации результирующей сущности и всех сущностей, на которые она ссылается (включая коллекции), будут отслеживаться, храниться и совершено соответственно.
Компоненты данных могут быть объявлены в дескрипторах экрана или созданы программно с помощью специализированной фабрики — DataComponents .
Разнообразный
Уффф, описаны наиболее важные части нового API экранов, поэтому позвольте мне кратко перечислить другие важные функции на уровне веб-клиента:
- История URL и навигация . Эта функция решает очень распространенную проблему SPA с помощью кнопки «вернуться» в веб-браузере, предоставляет простой способ назначения маршрутов экранам приложения и позволяет API отображать текущее состояние экрана в своем URL-адресе.
- Форма вместо FieldGroup. FieldGroup является компонентом, учитывающим данные, для отображения и изменения полей одного объекта. Он выводит фактический пользовательский интерфейс, отображаемый для поля во время выполнения. Другими словами, если у вас есть поле Date в вашей сущности, оно будет отображаться как DateField . Однако, если вы хотите работать с этим полем программно, вам нужно будет вставить это поле в контроллер экрана и вручную привести его к нужному типу (в нашем примере DateField ). Позже мы меняем наш тип поля на какой-то другой, и наше приложение вылетает во время выполнения … Форма решает эту проблему путем явного объявления типа поля. Найти больше информации об этом новом компоненте здесь .
- Интеграция сторонних компонентов JavaScript значительно упрощена, следуйте документации, чтобы встроить пользовательские компоненты JavaScript в приложение CUBA.
- Атрибуты HTML / CSS теперь могут быть легко определены прямо из дескриптора экрана xml или установлены программно. Найти больше информации здесь .
Функции промежуточного программного обеспечения
Предыдущий блок о API новых экранов был больше, чем я ожидал, поэтому в этом разделе я постараюсь быть аккуратным!
Событие, измененное сущностью
Событие Entity Changed — это событие приложения Spring, которое запускается, когда ваша сущность попадает в хранилище данных, физически вставляется и находится в пределах дюйма от момента принятия. Здесь вы можете предоставить некоторые дополнительные проверки (например, проверить наличие товара на складе, прежде чем подтвердить заказ) и изменить его (например, пересчитать итоги) прямо перед тем, как он будет виден для других транзакций (конечно, с уровнем фиксации прочитанного подтверждения). Вы также можете использовать это событие в качестве последнего шанса прервать транзакцию от совершения, выдав исключение — что может быть полезно в некоторых угловых случаях.
Существует также способ перехватить событие изменения сущности сразу после совершения коммита.
Следуйте этой главе документации, чтобы увидеть пример.
Менеджер транзакционных данных
При разработке приложения мы обычно работаем с отдельными объектами, которые не управляются какой-либо транзакцией. Однако работа с отсоединенными объектами не всегда возможна, особенно при попытке удовлетворить требования ACID — это тот случай, когда вы можете использовать диспетчер транзакционных данных. Он выглядит очень похоже на обычный менеджер данных, но отличается следующими аспектами:
- Он может присоединиться к существующей транзакции (если она вызывается в контексте транзакции) или создать собственную транзакцию.
- У него нет метода фиксации , но есть метод save, который не приводит к немедленной фиксации, но ожидает, пока присоединенная транзакция будет зафиксирована.
Найдите пример использования здесь .
Обратные вызовы жизненного цикла JPA
Наконец, CUBA 7 поддерживает обратные вызовы жизненного цикла JPA. Чтобы не копировать хорошо написанную информацию о том, для чего могут использоваться эти обратные вызовы, позвольте мне поделиться этой ссылкой , которая полностью охватывает эту тему.
Как насчет совместимости?

Справедливый вопрос для любого крупного релиза, особенно когда так много, казалось бы, серьезных изменений! Мы разработали все эти новые функции и API с учетом обратной совместимости:
- Старый API экранов поддерживается в CUBA 7 и реализуется через новый под капотом 🙂
- Мы также предоставили адаптеры для старой привязки данных, которые продолжают работать для устаревших экранов.
Итак, хорошие новости, путь перехода с версии 6 на 7 должен быть довольно простым.
Вывод
Завершая этот технический обзор, я хотел бы отметить, что есть и другие важные новации, особенно в области лицензирования:
- Ограничение в 10 сущностей для Студии уже прошло
- Отчеты, BPM, диаграммы и карты, а также дополнения для полнотекстового поиска теперь бесплатны и имеют открытый исходный код.
- Коммерческая версия Studio обеспечивает дополнительный комфорт при разработке визуальных дизайнеров для сущностей, экранов, меню и других элементов платформы, а бесплатная версия ориентирована на работу с кодом.
- Обратите внимание, что для версий 6.x и более ранних версий условия лицензирования Platform и Studio остаются прежними!
Наконец, позвольте мне еще раз поблагодарить членов сообщества за поддержку и отзывы. Надеюсь, вам понравится версия 7! Полный список изменений традиционно доступен в заметках о выпуске .