KUBA 7: Was ist neu?


Vor drei Jahren haben wir die Veröffentlichung von CUBA 6 angekündigt. Diese Version wurde revolutionär: Anstelle einer geschlossenen proprietären Lizenz haben wir begonnen, das Framework unter der Apache 2.0-Lizenz frei zu vertreiben. Zu diesem Zeitpunkt konnten wir noch nicht einmal genau wissen, wie sich dies langfristig auf die Entwicklung des Frameworks auswirken würde. Die CUBA-Community begann exponentiell zu wachsen, und wir stießen auf alle möglichen (und manchmal unmöglichen) Möglichkeiten, das Framework zu nutzen. Wir stellen jetzt den CUBA 7 vor . Wir hoffen, dass diese Version die Entwicklung für alle Mitglieder der Community noch einfacher und angenehmer macht: von Anfängern, die sich gerade mit CUBA und Java vertraut gemacht haben, bis zu erfahrenen Entwicklern, die mehr als ein abgeschlossenes Projekt auf der Ebene eines großen Unternehmens haben.


Entwicklungswerkzeuge


Ein wesentlicher Teil des Erfolgs von CUBA ist CUBA Studio zu verdanken. Diese Entwicklungsumgebung hat die Implementierung typischer Aufgaben, die in jedem Java-Projekt ausgeführt werden, erheblich vereinfacht und sie auf die Erstellung einfacher Konfigurationen in visuellen Designern reduziert. Sie müssen nicht alle Annotationsattribute der Persistenz-API, die Gradle-Syntax oder die Feinheiten der Spring-Konfiguration kennen, um eine vollständige und funktionsreiche CRUD-Anwendung zu entwickeln. CUBA Studio kümmert sich um die Erstellung des typischen Codes.



Studio war eine separate Webanwendung, die eine Reihe bedeutender Einschränkungen verursachte:


  • Erstens war Studio keine vollwertige IDE, daher mussten Entwickler zwischen Studio und IntelliJ IDEA oder Eclipse wechseln, um Geschäftslogik zu entwickeln und gleichzeitig bequeme Navigation, Code-Vervollständigung und andere notwendige Dinge zu verwenden, was etwas ärgerlich war.
  • Zweitens beruhte all diese magische Einfachheit auf einer enormen Menge an Arbeit, die wir für das Schreiben von Algorithmen zum Parsen und Generieren von Quellcode aufgewendet haben. Die Implementierung noch erweiterter Funktionen würde bedeuten, dass eine vollwertige IDE entwickelt wird - ein zu ehrgeiziges Unterfangen für uns.

Wir haben uns entschlossen, uns auf einen anderen Riesen zu verlassen, um diese Einschränkungen zu überwinden, und Studio basierend auf IntelliJ IDEA entwickelt. Jetzt können Sie Studio sowohl als eigenständige Anwendung (IntelliJ IDEA Bundle) als auch als Plug-In für IDEA installieren.



Und das gibt uns neue Möglichkeiten:


  • Unterstützung für andere JVM-Sprachen (und vor allem Kotlin)
  • Überlegene Hot-Bereitstellung
  • Intuitive projektweite Navigation
  • Intelligentere Hinweise und Codegeneratoren

Derzeit entwickeln wir aktiv eine neue Version von Studio - wir übertragen Funktionen aus der alten Version und fügen mithilfe der Funktionen der IntelliJ-Plattform neue Dinge hinzu. In naher Zukunft - die Übersetzung von CUBA-spezifischen Editoren in IntelliJ-Komponenten und die Verbesserung der Navigation durch den Projektcode.


Technologie-Stack-Update


Traditionell wurde der Technologie-Stack im Kern von CUBA auf neue Versionen aktualisiert: Java 8/11, Vaadin 8, Spring 5.


Standardmäßig verwenden neue Projekte Java 8, aber Sie können die Java-Version angeben, indem Sie der Datei build.gradle den folgenden Code hinzufügen:


subprojects { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } 

Ein besonders großes Problem war das Upgrade auf Vaadin 8, bei dem sich die Datenbindungs-API stark verändert hat. Glücklicherweise schützt CUBA Entwickler vor den internen Komponenten von Vaadin und verpackt sie in eine eigene API. Das CUBA-Team hat die internen Komponenten hervorragend aktualisiert, ohne die CUBA-API zu ändern. Dies bedeutet, dass die Kompatibilität vollständig erhalten bleibt und Sie alle neuen Funktionen von Vaadin 8 unmittelbar nach der Migration des Projekts auf CUBA 7 nutzen können, ohne Refactoring durchführen zu müssen.


Eine vollständige Liste der aktualisierten Abhängigkeiten finden Sie in der Änderungsliste .


Neue Bildschirm-API


Dieser Abschnitt wird möglicherweise als "API für den ersten Bildschirm" bezeichnet, da CUBA im Webclient-Modul nie eine offiziell angekündigte Bildschirm-API hatte. Dies geschah historisch, auch aufgrund einiger Annahmen, die im Anfangsstadium entstanden sind:


  • Ein deklarativ orientierter Ansatz - alles, was deklarativ beschrieben werden konnte, musste im Bildschirmdeskriptor und nicht im Controller-Code deklariert werden.
  • Standardbildschirme (Browser und Editor) boten eine bestimmte allgemeine Funktionalität, die nicht geändert werden musste.


Als die ersten tausend Mitglieder unserer Community beitraten, stellten wir fest, wie viele unterschiedliche Anforderungen an die „Standard“ -CRUD-Bildschirme gestellt werden. Und all diese Anforderungen gingen weit über die ursprüngliche Funktionalität hinaus. Lange Zeit konnten wir jedoch Anfragen nach der Implementierung eines atypischen Bildschirmverhaltens erfüllen, ohne die API zu ändern - dank eines anderen Architekturprinzips, das in der Anfangsphase festgelegt wurde: Open Inheritance. In der Tat bedeutet Open Inheritance, dass Sie jede öffentliche oder geschützte Methode der Hauptklasse neu schreiben können, um ihr Verhalten an Ihre Bedürfnisse anzupassen. Dies mag wie ein wunderbares Allheilmittel erscheinen, aber tatsächlich konnte man sich auch kurzfristig nicht darauf verlassen. Was ist, wenn eine überschriebene Methode umbenannt, gelöscht oder in zukünftigen Versionen des Frameworks einfach nie verwendet wird?


Als Reaktion auf die wachsende Nachfrage der Community haben wir uns entschlossen, eine neue Bildschirm-API einzuführen. Diese API bietet klare (ohne versteckte Magie), flexible und benutzerfreundliche Erweiterungspunkte, die sich garantiert lange Zeit nicht ändern.


Bildschirmdeklaration


In CUBA 7 ist das Deklarieren von Bildschirmen äußerst einfach:


 @UiController("new-screen") // screen id public class NewScreen extends Screen { } 

Das obige Beispiel zeigt, dass die Bildschirmkennung direkt über der Controller-Klassendeklaration explizit definiert wird. Mit anderen Worten, die Bildschirm-ID und die Controller-Klasse stimmen jetzt auf einzigartige Weise überein. Wir haben also einige gute Neuigkeiten: Jetzt können Sie sicher direkt nach Controllertyp auf Bildschirme zugreifen:


 @Inject private ScreenBuilders screenBuilders; @Subscribe private void onBeforeClose(BeforeCloseEvent event) { screenBuilders.screen(this) .withScreenClass(SomeConfirmationScreen.class) .build() .show(); } 

Der Bildschirmdeskriptor wird ein optionaler Teil des Bildschirms. Eine Benutzeroberfläche kann programmgesteuert erstellt oder als XML-Bildschirmdeskriptor deklariert werden, der durch die Annotation @UiDescriptor über die Controller-Klasse definiert wird. Dies erleichtert das Lesen und Verstehen von Controllern und Markups erheblich - dieser Ansatz ist dem in der Android-Entwicklung verwendeten sehr ähnlich.


Zuvor war es auch erforderlich, ein Bildschirmhandle in der Datei web-screen.xml zu registrieren und ihm eine Kennung zuzuweisen. In CUBA 7 wurde diese Datei aus Kompatibilitätsgründen gespeichert, für die neue Methode zum Erstellen von Bildschirmen ist jedoch keine solche Registrierung erforderlich.


Bildschirmlebenszyklus


Die neue API führt einfache und selbsterklärende Ereignisse im Bildschirmlebenszyklus ein:


  • Init
  • Nachher
  • Vorab
  • Aftershow
  • Vorher schließen
  • Nach dem Schließen

Sie können alle Ereignisse in CUBA 7 wie folgt abonnieren:


 @UiController("new-screen") public class NewScreen extends Screen { @Subscribe private void onInit(InitEvent event) { } @Subscribe private void onBeforeShow(BeforeShowEvent event) { } } 

Im Vergleich zum alten Ansatz zeigt die neue API, dass wir Hook-Methoden, die bei der Initialisierung implizit aufgerufen werden, nicht überlappen, sondern die Logik für die Verarbeitung eines bestimmten, bestimmten Bildschirmlebenszyklusereignisses explizit definieren.


Ereignisbehandlung und funktionale Delegierte


Im vorherigen Abschnitt haben wir gelernt, wie man Lebenszyklusereignisse abonniert, aber was ist mit den anderen Komponenten? Müssen beim Initialisieren des Bildschirms in der init () -Methode wie in Version 6.x immer noch alle erforderlichen Listener in denselben Heap eingefügt werden? Die neue API ist ziemlich konsistent, sodass Sie andere Ereignisse auf die gleiche Weise abonnieren können wie die Lebensereignisse auf dem Bildschirm.


Betrachten Sie ein einfaches Beispiel mit zwei Elementen der Benutzeroberfläche: einer Schaltfläche und einem Feld zum Anzeigen des Geldbetrags in einer bestimmten Währung; Der XML-Deskriptor sieht folgendermaßen aus:


 <?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> 

Wenn Sie auf die Schaltfläche klicken, rufen wir den Service vom Backend aus auf, das eine Nummer zurückgibt. Wir schreiben sie in das Betragsfeld. Dieses Feld sollte den Stil abhängig vom Preiswert ändern.


 @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) { currencyField.setStyleName(getStyleNameByPrice(event.getValue())); } private String getStyleNameByPrice(BigDecimal price) { ... } } 

Im obigen Beispiel sehen wir zwei Ereignishandler: Einer wird aufgerufen, wenn die Taste gedrückt wird, und der andere wird gestartet, wenn das Währungsfeld seinen Wert ändert - alles ist einfach.


Stellen Sie sich nun vor, wir müssen den Preis überprüfen und sicherstellen, dass sein Wert positiv ist. Dies kann "Stirn" erfolgen - fügen Sie während der Bildschirminitialisierung einen Validator hinzu:


 @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 realen Anwendungen wird die Initialisierungsmethode nach einiger Zeit ein Durcheinander von Initialisierern, Validatoren, Listenern usw. sein. Um dieses Problem zu lösen, verfügt CUBA über eine nützliche @Install Annotation. Mal sehen, wie es in unserem Fall helfen kann:


 @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"); } } 

Tatsächlich delegieren wir die Validierungslogik für Währungsfelder an die CurrencyFieldValidator-Methode auf dem Bildschirm. Dies mag auf den ersten Blick etwas kompliziert erscheinen, aber die Entwickler gewöhnten sich überraschend schnell an diese Methode des Hinzufügens von Funktionen und begannen sofort damit.


Screen Builder, Benachrichtigungen, Dialoge



CUBA 7 verfügt über eine Reihe nützlicher Komponenten mit einer praktischen API:


  • ScreenBuilders kombiniert fließende Fabriken, um Standardbildschirme zum Anzeigen und Bearbeiten von Entitäten sowie benutzerdefinierte Bildschirme zu erstellen. Das folgende Beispiel zeigt, wie Sie einen Bildschirm von einem anderen aus öffnen können. Beachten Sie, dass die build () -Methode sofort den Bildschirm des gewünschten Typs zurückgibt, ohne dass eine Umwandlung erforderlich ist:
     CurrencyConversions currencyConversions = screenBuilders.screen(this) .withScreenClass(CurrencyConversions.class) .withLaunchMode(OpenMode.DIALOG) .build(); currencyConversions.setBaseCurrency(Currency.EUR); currencyConversions.show(); 
  • Die Komponente "Bildschirme" bietet im Gegensatz zur ScreenBuilders-API eine niedrigere Abstraktionsebene zum Erstellen und Anzeigen von Bildschirmen. Es bietet auch Zugriff auf Informationen zu allen geöffneten Bildschirmen in Ihrer CUBA-Anwendung ( Screens # getOpenedScreens ), wenn Sie plötzlich alle in einem Zyklus durchlaufen müssen.
  • Die Komponenten Benachrichtigungen und Dialoge bieten eine praktische und buchstäblich selbstdokumentierende API. Das folgende Beispiel zeigt das Erstellen und Anzeigen eines Dialogfelds und einer Benachrichtigung:
     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(); 

Datenbindung


CUBA bietet eine extrem schnelle Entwicklung der Benutzeroberfläche für das Backoffice, nicht nur mit fortschrittlichen visuellen Entwicklungstools und einem leistungsstarken Codegenerierungssystem, sondern auch mit einer Vielzahl von Komponenten, die sofort verfügbar sind. Diese Komponenten müssen nur wissen, mit welchen Daten sie arbeiten, und der Rest wird automatisch erledigt. Zum Beispiel Dropdown-Listen, Kalender, Tabellen mit integrierten CRUD-Operationen usw.


Vor Version 7 wurde die Datenbindung über die sogenannte Datenquelle durchgeführt - Objekte, die eine oder mehrere Entitäten für ihre reaktive Bindung an Komponenten umschließen. Dieser Ansatz hat sehr gut funktioniert, aber in Bezug auf die Implementierung war es ein Monolith. Die Konfiguration der monolithischen Architektur ist normalerweise problematisch. In CUBA 7 wurde dieser riesige Kopfsteinpflaster für die Arbeit mit Daten in drei Komponenten unterteilt:


  • Data Loader ist ein Datenprovider für Datencontainer. Der Loader speichert keine Daten, übergibt einfach alle erforderlichen Abfrageparameter an das Data Warehouse und legt die resultierenden Daten in Datencontainern ab.
  • Der Datencontainer speichert die geladenen Daten (eine oder mehrere Entitäten) und stellt sie den Datenkomponenten zur Verfügung: Alle Änderungen an diesen Entitäten werden auf die entsprechenden Komponenten übertragen, und umgekehrt führen alle Änderungen an den Komponenten zu entsprechenden Änderungen an den Entitäten, die im Datencontainer liegen.
  • Datenkontext (Datenkontext) ist eine Klasse, die Änderungen verfolgt und alle geänderten Entitäten speichert. Überwachte Entitäten werden bei jeder Änderung ihrer Attribute als fehlerhaft markiert, und der DataContext speichert beim Aufruf der Methode commit () fehlerhafte Instanzen in Middleware.

Somit besteht Flexibilität bei der Arbeit mit Daten. Ein künstliches Beispiel: Der Loader kann Daten in der Benutzeroberfläche aus einem RDBMS auswählen und der Kontext kann Änderungen am REST-Service speichern.


In CUBA 6.x müssen Sie hierfür eine eigene Datenquelle schreiben, die sowohl mit RDBMS als auch mit REST funktioniert. In CUBA 7 können Sie einen Standardlader verwenden, der mit der Datenbank arbeiten kann, und nur deren Kontextimplementierung für die Arbeit mit REST schreiben.


Komponenten für die Arbeit mit Daten können in Bildschirmdeskriptoren deklariert oder mithilfe einer speziellen Factory - DataComponents - programmgesteuert erstellt werden.


Andere


Uff ... Die wichtigsten Teile der neuen Bildschirm-API werden beschrieben. Lassen Sie uns daher kurz andere wichtige Funktionen auf Web-Client-Ebene auflisten:


  • URL-Verlauf und Navigation . Diese Funktion löst ein sehr häufiges SPA-Problem - das Verhalten der Zurück-Schaltfläche im Webbrowser ist nicht immer korrekt. Es bietet jetzt eine einfache Möglichkeit, Routen zu Anwendungsbildschirmen zuzuweisen, und ermöglicht der API, den aktuellen Status des Bildschirms in einer URL anzuzeigen.
  • Formular anstelle von FieldGroup . FieldGroup ist eine Komponente zum Anzeigen und Ändern von Feldern einer Entität. Es zeigt die Benutzeroberfläche für das Feld zur Laufzeit an. Mit anderen Worten, wenn die Entität ein Datumsfeld hat, wird sie als Datumsfeld angezeigt . Wenn Sie jedoch programmgesteuert mit diesem Feld arbeiten möchten, müssen Sie es in die Bildschirmsteuerung eingeben und manuell in den richtigen Typ umwandeln ( in unserem Beispiel DateField ). Wenn wir später den Feldtyp in einen anderen ändern, stürzt unsere Anwendung zur Laufzeit ab. Das Formular löst dieses Problem, indem der Feldtyp explizit deklariert wird. Weitere Informationen zur Komponente finden Sie hier .
  • Die Integration von JavaScript-Komponenten von Drittanbietern wird erheblich vereinfacht. Lesen Sie die Dokumentation zum Einbetten benutzerdefinierter JavaScript-Komponenten in eine CUBA-Anwendung.
  • HTML / CSS- Attribute können jetzt einfach direkt aus dem XML-Bildschirmdeskriptor definiert oder programmgesteuert festgelegt werden. Weitere Informationen finden Sie hier .

Neue Funktionen des Backend-Moduls


Der vorherige Abschnitt über die neue Bildschirm-API war mehr als erwartet, daher werde ich mich in diesem Abschnitt kurz fassen.


Entity Changed Event


Das Ereignis "Entität geändert" ist ein Ereignis in einer Spring-Anwendung, das ausgelöst wird, wenn eine Entität einen Datenspeicher betritt, physisch platziert wird und nur einen Schritt vom Festschreiben entfernt ist. Bei der Verarbeitung dieses Ereignisses können Sie zusätzliche Prüfungen konfigurieren (z. B. die Verfügbarkeit von Waren im Lager prüfen, bevor Sie die Bestellung bestätigen) und die Daten ändern (z. B. die Summen neu berechnen), bevor sie für andere Transaktionen sichtbar sind (natürlich, wenn Sie eine Ebene haben gelesene Isolation lesen). Dieses Ereignis kann auch die letzte Gelegenheit sein, die Transaktion durch Auslösen einer Ausnahme abzubrechen, was in einigen schwierigen Fällen hilfreich sein kann.


Es gibt auch eine Möglichkeit, das Ereignis "Entity Changed" unmittelbar nach einem Commit zu behandeln.


Sie können sich ein Beispiel in diesem Kapitel der Dokumentation ansehen.


Transaktionsdatenmanager


Bei der Entwicklung einer Anwendung arbeiten wir normalerweise mit getrennten Entitäten - solchen, die nicht im Kontext einer Transaktion stehen. Die Arbeit mit getrennten Entitäten ist jedoch nicht immer möglich, insbesondere wenn Sie die ACID-Anforderungen vollständig erfüllen müssen. Dies ist der Fall, wenn Sie einen Transaktionsdatenmanager verwenden können. Er ist einem regulären Manager sehr ähnlich, unterscheidet sich jedoch in folgenden Aspekten:


  • Er kann einer vorhandenen Transaktion beitreten (die im Rahmen dieser Transaktion aufgerufen wird) oder eine eigene Transaktion erstellen.
  • Es gibt keine Festschreibungsmethode, aber es gibt eine Speichermethode, die nicht sofort festschreibt, sondern darauf wartet, dass die aktuelle Transaktion festgeschrieben wird.

Hier ist ein Beispiel für seine Verwendung.


JPA-Rückrufe


Schließlich unterstützt CUBA 7 JPA-Rückrufe. Um die bekannten Materialien für die Verwendung dieser Rückrufe nicht zu wiederholen, hinterlasse ich hier einfach einen Link . In diesem Material wird das Thema Rückrufe vollständig behandelt.


Wie wäre es mit Kompatibilität?



Eine faire Frage für jede größere Veröffentlichung, besonders wenn es so viele kritische Änderungen gibt! Wir haben all diese neuen Funktionen und APIs abwärtskompatibel entwickelt:


  • Die alte API wird in CUBA 7 unterstützt und über die neue unter der Haube implementiert :)
  • Wir haben auch Adapter für die Datenbindung über die alte API bereitgestellt. Diese Adapter eignen sich perfekt für Bildschirme, die nach dem alten Schema erstellt wurden.

Die gute Nachricht ist, dass der Migrationsprozess von Version 6 auf 7 recht einfach sein sollte.


Fazit


Abschließend möchte ich feststellen, dass es weitere wichtige Neuerungen gibt, insbesondere im Bereich der Lizenzierung:


  • Das Limit von 10 Entitäten für Studio wurde jetzt entfernt
  • Die Add-Ons für Berichterstellung, BPM, Diagramme und Karten sowie Volltextsuche sind jetzt kostenlos und Open Source.
  • Die kommerzielle Version von Studio trägt zur Entwicklung von Komfort mithilfe visueller Designer von Entitäten, Bildschirmen, Menüs und anderen Plattformelementen bei, und die kostenlose Version konzentriert sich auf die Arbeit mit Code.
  • Bitte beachten Sie, dass für Versionen 6.x und frühere Versionen die Lizenzbedingungen für Platform und Studio gleich bleiben!

Abschließend möchte ich mich noch einmal bei der Community für ihre Unterstützung und ihr Feedback bedanken. Ich hoffe du liebst Version 7! Vollständige Informationen finden Sie traditionell im offiziellen Changelog .

Source: https://habr.com/ru/post/de447538/


All Articles