Three years ago we announced the second publicly available major version of the framework. CUBA 6 was the game changing version - the licensing was turned from proprietary to Apache 2.0. Those days we couldn't even guess where it was going to bring the framework in long term. CUBA community started to grow exponentially, so we have learned a lot of possible (and sometimes impossible) ways of how developers use the framework. Now we are happy to announce CUBA 7, which, we hope, will make development more coherent and joyful for all community members from those just starting their journey in CUBA and Java to skilled enterprise developers and Java experts.
Development Tools
Obviously, a great part of CUBA success we owe to CUBA Studio. It has remarkably simplified the overwrought Java enterprise routine, in many places grounding it down to making trivial configurations in the visual designers: no need to know Persistence API or Gradle or even Spring to develop a complete and feature-rich CRUD application - Studio will do it for you.
The Studio was a separate web application and this fact caused some significant limitations:
- First of all, Studio was not a fully featured IDE, so developers had to switch between the Studio and IntelliJ IDEA or Eclipse to develop business logic and benefit from convenient navigation, code completion and other essential things, which was annoying.
- Secondly, this magical simplicity was built over massive source code parsing and generation. Improving the code generation capabilities would mean moving towards development of a fully featured IDE - a too ambitious undertaking.
We decided to lean on another giant's shoulder to overcome these limitations. Studio was merged into IntelliJ IDEA by JetBrains. Now you can install it as a plugin for your IntelliJ IDEA or download as a separate standalone bundle.
This opens new horizons:
- Other JVM languages support (and Kotlin in the first place)
- Improved hot deploy
- Intuitive navigation through the entire project
- Smarter hints and code generators
Currently new Studio is under active development: we are porting features from the old version. The short term plan is also to re-implement web-based designers using native IntelliJ UI and improve project navigation experience.
Stack Upgrade
Traditionally, the underlying stack has also been majorly upgraded, e.g. Java 8/11, Vaadin 8, Spring 5.
By default new projects use Java 8, but you can specify the version of Java by adding the following clause to the build.gradle file:
subprojects {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
Upgrade to Vaadin 8 was a big challenge because of massive breaking changes in the Vaadin data binding API. Fortunately, CUBA abstracts developers from Vaadin internals by wrapping it into its own API layer. CUBA team did a great job reimplementing internals keeping its own API untouched. This means that compatibility is fully saved and you can benefit from Vaadin 8 right after migrating a project to CUBA 7 without any refactoring.
The full list of updated dependencies is available in the official release notes.
New Screens API
This section could also be named "The first screens API" - as CUBA has never had any officially declared API in the web client tier. It comes from the history of the framework and certain assumptions that were made at the first stage:
Declarative-centric approach - everything that can be described declaratively, should be declared in a screen descriptor and not coded in its controller
Standard screens (Browser and Editor) provide concrete generic functionality and there is no need to modify it
Since the first thousand members joined our community we realized how wide the variety of requirements for "standard" CRUD screens is - way beyond the initially-designed set of features. Nevertheless, for a long time, we were able to handle requests for custom behaviour even without an API layer - thanks to another first stage assumption - Open Inheritance. Effectively Open Inheritance means that you can override any public or protected method of an underlying class to tailor its behaviour to what you need. This might sound like a cure for all diseases, but in fact it doesn't give you even a short-term contract: what if the overridden method will be renamed, deleted or simply never used in the future versions of the framework?
So, in response to the growing demand from the community we decided to introduce a new screens API. The API provides clear and long-term extension points with no hidden declarative magic, flexible and very easy to use.
Screen Declaration
In CUBA 7 screen declaration is extremely simple:
@UiController("new-screen") // screen id
public class NewScreen extends Screen { }
From the example above we can see that screen identifier is explicitly defined right above the controller class. In other words, screen id and controller class now uniquely correspond to each other. So, good news, now screens can be addressed directly by their controller class in a safe way:
@Inject
private ScreenBuilders screenBuilders;
@Subscribe
private void onBeforeClose(BeforeCloseEvent event) {
screenBuilders.screen(this)
.withScreenClass(SomeConfirmationScreen.class)
.build()
.show();
}
Screen descriptor becomes a complementary part instead of mandatory. The layout can be created programmatically or declared as an XML screen descriptor, which is defined by the @UiDescriptor annotation over the controller class. This makes controllers and layouting much easier to read and understand - this approach is very similar to the one used in Android development.
Before it was also required to register a screen descriptor in the web-screens.xml file and assign an identifier to it. In CUBA 7 this file is kept due to compatibility reasons, however, creating screens in a new way does not require such registration.
Screens Lifecycle
The new API introduces clear and self-explanatory screen lifecycle events:
- Init
- AfterInit
- BeforeShow
- AfterShow
- BeforeClose
- AfterClose
All screen-related events in CUBA 7 can be subscribed as follows:
@UiController("new-screen")
public class NewScreen extends Screen {
@Subscribe
private void onInit(InitEvent event) {
}
@Subscribe
private void onBeforeShow(BeforeShowEvent event) {
}
}
Comparing new API with the old approach you can see that we are not overriding hook-methods, which are obscurely called in the hierarchy of parent classes but define logic in clear predefined points of the screen lifecycle.
Event Handling and Functional Delegates
In the previous section we learned how to subscribe to the lifecycle events, so, what about other components? Should we still scatter all required listeners on screen initialization as it was in 6.x versions? The new API is very uniform, so subscribing to other events is absolutely similar to the lifecycle ones.
Let's take a simple example with two UI elements: a button and a currency field, so its XML descriptor looks like:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
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>
By clicking the button we call middleware service returning a number, which goes to the currency field. The currency field should change its style depending on the price value.
@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) {
...
}
}
In the example above we can see two event handlers: one is invoked when the button is clicked and another one gets executed when the currency field changes its value - as simple as that.
Now, let's imagine that we need to validate our price and check that its value is positive. The straightforward way would be to add a validator while screen initialization:
@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");
});
}
}
In real world applications a screen entry point usually becomes littered with this kind of screen element initializers. To address this issue CUBA provides the useful annotation @Install
. Let's see how it can help in our case:
@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");
}
}
In fact, we delegate validation logic from our currency field to the currencyFieldValidator method in our screen. This might look a bit complicated, however, developers adopt this feature surprisingly fast.
Screen Builders / Notifications / Dialogs
CUBA 7 also introduces a set of useful components with fluent APIs:
- ScreenBuilders combines fluent factories to generate standard lookups, editors and custom screens. The example below shows how you can open one screen from another. Note, that the build() method returns the screen instance of the right type, without a need to unsafely cast it.
CurrencyConversions currencyConversions = screenBuilders.screen(this)
.withScreenClass(CurrencyConversions.class)
.withLaunchMode(OpenMode.DIALOG)
.build();
currencyConversions.setBaseCurrency(Currency.EUR);
currencyConversions.show();
- Screens component provides a lower level abstraction to creating and showing screens rather than ScreenBuilders. It also provides access to the information about all opened screens in your CUBA application (Screens#getOpenedScreens) in case if you need to iterate through them.
- Notifications and Dialogs components both introduce convenient self-explanatory interfaces. Here is an example for creating and showing a dialog and a notification:
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();
Data Binding
CUBA enables extremely fast development of backoffice UIs not only by providing advanced visual tooling with extensive code-generation capabilities but also by a rich set of data-aware components available right out of the box. Such components just need to know what data they work with and the rest will be managed automatically, e.g. lookup lists, picker fields, various grids with CRUD operations and so on.
Before version 7 data binding was implemented via so-called datasources - objects that wrap a single entity or a collection of entities to reactively tie them with data-aware components. This approach worked very well, however, implementation-wise it was a monolith. The monolithic architecture typically causes problems with its customization, so in CUBA 7 this solid boulder was split into 3 data components:
- Data loader is a data provider for data containers. Data loaders don't keep data, they just pass all required query parameters to a data store and feed data containers with the resulting data set.
- Data container keeps the loaded data (a single entity or a number of entities) and provides it to the data-aware components in a reactive manner: all changes of the wrapped entities get exposed to the corresponding UI components and vice versa, all changes in the UI components will lead to the corresponding changes in its data container.
- Data context is a powerful data modification manager that tracks changes and commits all modified entities. An entity can be merged into a data context, so it will provide a copy of the original entity with the only, but very important difference: all modifications of the resulting entity and all entities it references (including collections) will be tracked, stored and committed accordingly.
Data components can be declared in screen descriptors or instantiated programmatically using a specialized factory - DataComponents.
Miscellaneous
Ufff, the most significant parts of the new screens API are described, so let me briefly list other important features in the web client tier:
- URL History and Navigation. This feature solves a very common problem of SPA with “go back” button in a web browser, provides an easy way to assign routes to application screens and enables an API to reflect a current state of a screen in its URL.
- Form instead of FieldGroup. FieldGroup is a data-aware component to show and modify fields of a single entity. It infers the actual UI shown for a field in runtime. In other words, if you have a Date field in your entity it will be shown as a DateField. However, if you would like to operate with this field programmatically, you will need to inject this field to the screen controller and cast it to the right type manually (DateField in our example). Later on, we change our field type to some other and our application crashes at runtime… Form addresses this issue by explicit field type declaration. Find more information about this new component here.
- Third party JavaScript components integration is significantly simplified, follow the documentation to embed custom JavaScript components into a CUBA application.
- HTML / CSS attributes now can be easily defined right from the xml screen descriptor or set programmatically. Find more information here.
Middleware Features
The previous block about the new screens API was larger than I expected, so in this section, I'll try to be neat!
Entity Changed Event
Entity Changed Event is a Spring application event that is fired when your entity made its way to a data store, got physically inserted and is within an inch of being committed. Here you can provide some additional checks (e.g. check product availability in stock before confirming an order) and modify it (e.g. recalculate totals) right before it will be visible for other transactions (of course with read committed isolation level). You can also use this event as the last chance to interrupt the transaction from being committed by throwing an exception - which might be useful in some corner cases.
There is also a way to catch the Entity Changed Event right after a commit has happened.
Follow this chapter of the documentation to see an example.
Transactional Data Manager
When developing an application we normally operate with detached entities - the ones which are not managed by any transaction. However, working with detached entities is not always possible, especially when trying to meet ACID requirements - this is the case when you can use the transactional data manager. It looks very similar to the ordinary data manager but differs in the following aspects:
- It can join existing transaction (in case it is called under transactional context) or create its own transaction.
- It has no commit method, but there is the save method, which doesn't lead to immediate commit, but waits until the attached transaction will be committed.
Find an example of using it here.
JPA Lifecycle Callbacks
Finally, CUBA 7 supports JPA lifecycle callbacks. To not replicate well-written information about what these callbacks can be used for, let me just share this link, which fully covers the subject.
What about Compatibility?
A fair question for any major release, especially when there are so many seemingly breaking changes! We have designed all these new features and APIs keeping in mind the backward compatibility:
- The old screens API is supported in CUBA 7 and is implemented via the new one under the hood :)
- We have also provided adapters for the old data binding, which keep working for the old fashioned screens.
So, good news, the migration path from version 6 to 7 should be quite straightforward.
Conclusion
Concluding this technical overview I would like to mention that there are other important novations, especially with licensing:
- The 10 entities limit for Studio is now gone
- Reporting, BPM, Charts and Maps and Full text search addons are now free and open source.
- The commercial version of Studio brings extra development comfort with visual designers for entities, screens, menus, and other platform elements, while the free version focuses on working with code
- Please note that for 6.x and older versions of the Platform and Studio licensing terms remain the same!
Finally, let me thank the community members again for all the support and feedback. I hope you will love version 7! The full list of changes are traditionally available in the release notes.