Статьи

Что нового в CUBA 7

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

КУБА 7

Инструменты разработки

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

КУБА 7

Студия была отдельным веб-приложением, и этот факт вызвал некоторые существенные ограничения:

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

Мы решили опереться на плечо другого гиганта, чтобы преодолеть эти ограничения. Студия была объединена с IntelliJ IDEA компанией JetBrains. Теперь вы можете установить его как плагин для вашей IntelliJ IDEA или загрузить в виде отдельного отдельного пакета.

КУБА 7

Это открывает новые горизонты:

  • Поддержка других языков JVM (и прежде всего Kotlin)
  • Улучшено горячее развертывание
  • Интуитивно понятная навигация по всему проекту
  • Умные подсказки и генераторы кода

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

Обновление стека

Традиционно базовый стек также был значительно обновлен, например, Java 8/11, Vaadin 8, Spring 5.

КУБА 7

По умолчанию новые проекты используют 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 — благодаря другому предположению первого этапа — открытому наследованию. Эффективно открытое наследование означает, что вы можете переопределить любой открытый или защищенный метод базового класса, чтобы адаптировать его поведение к тому, что вам нужно. Это может звучать как лекарство от всех болезней, но на самом деле это не дает вам даже краткосрочного контракта: что если переопределенный метод будет переименован, удален или просто никогда не будет использоваться в будущих версиях фреймворка?

КУБА 7
1
2
3
@UiController("new-screen"// screen id
public class NewScreen extends Screen {
}

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

01
02
03
04
05
06
07
08
09
10
@Inject
private ScreenBuilders screenBuilders;
 
@Subscribe
private 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 на нашем экране. Это может показаться немного сложным, однако разработчики применяют эту функцию на удивление быстро.

Построители экрана / Уведомления / Диалоги

КУБА 7

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. Чтобы не копировать хорошо написанную информацию о том, для чего могут использоваться эти обратные вызовы, позвольте мне поделиться этой ссылкой , которая полностью охватывает эту тему.

Как насчет совместимости?

КУБА 7

Справедливый вопрос для любого крупного релиза, особенно когда так много, казалось бы, серьезных изменений! Мы разработали все эти новые функции и API с учетом обратной совместимости:

  • Старый API экранов поддерживается в CUBA 7 и реализуется через новый под капотом 🙂
  • Мы также предоставили адаптеры для старой привязки данных, которые продолжают работать для устаревших экранов.

Итак, хорошие новости, путь перехода с версии 6 на 7 должен быть довольно простым.

Вывод

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

  • Ограничение в 10 сущностей для Студии уже прошло
  • Отчеты, BPM, диаграммы и карты, а также дополнения для полнотекстового поиска теперь бесплатны и имеют открытый исходный код.
  • Коммерческая версия Studio обеспечивает дополнительный комфорт при разработке визуальных дизайнеров для сущностей, экранов, меню и других элементов платформы, а бесплатная версия ориентирована на работу с кодом.
  • Обратите внимание, что для версий 6.x и более ранних версий условия лицензирования Platform и Studio остаются прежними!

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