Quoi de neuf dans CUBA 7
Il y a trois ans, nous avons annoncé la deuxième version majeure du cadre accessible au public. CUBA 6 était la version révolutionnaire - la licence est passée de propriétaire à Apache 2.0. Ces jours-là, nous ne pouvions même pas deviner où cela allait amener le cadre à long terme. La communauté CUBA a commencé à croître de façon exponentielle, nous avons donc appris beaucoup de façons possibles (et parfois impossibles) de savoir comment les développeurs utilisent le framework. Nous sommes maintenant heureux d'annoncer CUBA 7 , qui, nous l'espérons, rendra le développement plus cohérent et joyeux pour tous les membres de la communauté, de ceux qui commencent tout juste leur voyage en CUBA et Java aux développeurs d'entreprise qualifiés et aux experts Java.

De toute évidence, une grande partie du succès de CUBA que nous devons à CUBA Studio . Il a remarquablement simplifié la routine d'entreprise Java surmenée, en de nombreux endroits la basant sur la création de configurations triviales dans les concepteurs visuels: pas besoin de connaître l'API Persistence ou Gradle ou même Spring pour développer une application CRUD complète et riche en fonctionnalités - Studio le fera pour toi.
Le Studio était une application Web distincte et ce fait a entraîné des limitations importantes:
- Tout d'abord, Studio n'était pas un IDE complet, les développeurs ont donc dû basculer entre Studio et IntelliJ IDEA ou Eclipse pour développer une logique métier et bénéficier d'une navigation pratique, de la complétion de code et d'autres choses essentielles, ce qui était ennuyeux.
- Deuxièmement, cette simplicité magique a été construite sur une analyse et une génération massives de code source. Améliorer les capacités de génération de code signifierait s'orienter vers le développement d'un IDE complet - une entreprise trop ambitieuse.
Nous avons décidé de nous appuyer sur l'épaule d'un autre géant pour surmonter ces limites. Studio a été fusionné dans IntelliJ IDEA par JetBrains. Vous pouvez maintenant l'installer en tant que plugin pour votre IntelliJ IDEA ou le télécharger en tant que bundle autonome séparé.

Cela ouvre de nouveaux horizons:
- Prise en charge d'autres langages JVM (et Kotlin en premier lieu)
- Déploiement à chaud amélioré
- Navigation intuitive à travers tout le projet
- Astuces et générateurs de code plus intelligents
Actuellement, le nouveau Studio est en cours de développement: nous portons des fonctionnalités de l'ancienne version. Le plan à court terme consiste également à réimplémenter les concepteurs Web à l'aide de l'interface utilisateur IntelliJ native et à améliorer l'expérience de navigation du projet.
Mise à niveau de la pile
Traditionnellement, la pile sous-jacente a également été largement mise à niveau, par exemple Java 8/11, Vaadin 8, Spring 5.

Par défaut, les nouveaux projets utilisent Java 8, mais vous pouvez spécifier la version de Java en ajoutant la clause suivante au fichier build.gradle:
subprojects { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 }
La mise à niveau vers Vaadin 8 était un grand défi en raison de changements majeurs dans l'API de liaison de données Vaadin. Heureusement, CUBA extrait les développeurs des éléments internes de Vaadin en l'enveloppant dans sa propre couche d'API. L'équipe de CUBA a fait un excellent travail en réimplémentant les composants internes en gardant sa propre API intacte. Cela signifie que la compatibilité est entièrement sauvegardée et vous pouvez bénéficier de Vaadin 8 juste après la migration d'un projet vers CUBA 7 sans refactoring.
La liste complète des dépendances mises à jour est disponible dans les notes de version officielles.
API Nouveaux écrans
Cette section pourrait également être intitulée "L'API des premiers écrans" - car CUBA n'a jamais eu d'API officiellement déclarée dans le niveau client Web. Elle provient de l'historique du cadre et de certaines hypothèses qui ont été faites lors de la première étape:
- Approche centrée sur la déclaration - tout ce qui peut être décrit de manière déclarative doit être déclaré dans un descripteur d'écran et non codé dans son contrôleur
- Les écrans standard (navigateur et éditeur) fournissent des fonctionnalités génériques concrètes et il n'est pas nécessaire de les modifier
Depuis que les mille premiers membres ont rejoint notre communauté, nous avons réalisé à quel point la variété des exigences pour les écrans CRUD «standard» est bien au-delà de l'ensemble de fonctionnalités initialement conçu. Néanmoins, pendant longtemps, nous avons pu traiter les demandes de comportement personnalisé même sans couche API - grâce à une autre hypothèse de première étape - l'héritage ouvert. Un héritage ouvert efficace signifie que vous pouvez remplacer toute méthode publique ou protégée d'une classe sous-jacente pour adapter son comportement à vos besoins. Cela peut sembler un remède contre toutes les maladies, mais en fait, cela ne vous donne même pas de contrat à court terme: que se passe-t-il si la méthode remplacée sera renommée, supprimée ou simplement jamais utilisée dans les futures versions du cadre?

Donc, en réponse à la demande croissante de la communauté, nous avons décidé d'introduire une nouvelle API d'écrans. L'API fournit des points d'extension clairs et à long terme sans magie déclarative cachée, flexible et très facile à utiliser.
Déclaration d'écran
Dans CUBA 7, la déclaration d'écran est extrêmement simple:
@UiController("new-screen") // screen id public class NewScreen extends Screen { }
Dans l'exemple ci-dessus, nous pouvons voir que l'identifiant d'écran est explicitement défini juste au-dessus de la classe du contrôleur. En d'autres termes, l'ID d'écran et la classe de contrôleur correspondent désormais de manière unique. Donc, bonne nouvelle, les écrans peuvent désormais être adressés directement par leur classe de contrôleur de manière sûre:
@Inject private ScreenBuilders screenBuilders; @Subscribe private void onBeforeClose(BeforeCloseEvent event) { screenBuilders.screen(this) .withScreenClass(SomeConfirmationScreen.class) .build() .show(); }
Le descripteur d'écran devient une partie complémentaire au lieu d'être obligatoire. La disposition peut être créée par programme ou déclarée en tant que descripteur d'écran xml, qui est défini par l'annotation @UiDescriptor sur la classe de contrôleur. Cela rend les contrôleurs et la mise en page beaucoup plus faciles à lire et à comprendre - cette approche est très similaire à celle utilisée dans le développement Android.
Auparavant, il était également nécessaire d'enregistrer un descripteur d'écran dans le fichier web-screens.xml et de lui attribuer un identifiant. Dans CUBA 7, ce fichier est conservé pour des raisons de compatibilité, cependant, la création d'écrans d'une nouvelle manière ne nécessite pas un tel enregistrement.
Cycle de vie des écrans
La nouvelle API introduit des événements de cycle de vie d'écran clairs et explicites:
- Init
- Afterinit
- Avant
- Après-spectacle
- Avantfermer
- Afterclose
Tous les événements liés à l'écran dans CUBA 7 peuvent être abonnés comme suit:
@UiController("new-screen") public class NewScreen extends Screen { @Subscribe private void onInit(InitEvent event) { } @Subscribe private void onBeforeShow(BeforeShowEvent event) { } }
En comparant la nouvelle API à l'ancienne approche, vous pouvez voir que nous ne remplaçons pas les méthodes de hook, qui sont obscurément appelées dans la hiérarchie des classes parentes, mais définissons la logique en des points prédéfinis clairs du cycle de vie de l'écran.
Gestion des événements et délégués fonctionnels
Dans la section précédente, nous avons appris comment s'abonner aux événements du cycle de vie, alors qu'en est-il des autres composants? Faut-il encore disperser tous les écouteurs requis lors de l'initialisation de l'écran comme c'était le cas dans les versions 6.x? La nouvelle API est très uniforme, donc l'abonnement à d'autres événements est absolument similaire à ceux du cycle de vie.
Prenons un exemple simple avec deux éléments d'interface utilisateur: un bouton et un champ monétaire, de sorte que son descripteur xml ressemble à:
<?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>
En cliquant sur le bouton, nous appelons le service middleware renvoyant un numéro, qui va dans le champ de la devise. Le champ devise doit changer de style en fonction de la valeur du prix.
@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) { ... } }
Dans l'exemple ci-dessus, nous pouvons voir deux gestionnaires d'événements: l'un est appelé lorsque le bouton est cliqué et un autre est exécuté lorsque le champ monétaire change sa valeur - aussi simple que cela.
Imaginons maintenant que nous devions valider notre prix et vérifier que sa valeur est positive. La manière la plus simple serait d'ajouter un validateur lors de l'initialisation de l'écran:
@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"); }); } }
Dans les applications du monde réel, un point d'entrée d'écran est généralement jonché de ce type d'initialiseurs d'éléments d'écran. Pour résoudre ce problème, CUBA fournit l'annotation utile @Install
. Voyons comment cela peut aider dans notre cas:
@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"); } }
En fait, nous déléguons la logique de validation de notre champ de devise à la méthode currencyFieldValidator de notre écran. Cela peut sembler un peu compliqué, cependant, les développeurs adoptent cette fonctionnalité étonnamment rapidement.
Constructeurs d'écran / Notifications / Boîtes de dialogue

CUBA 7 présente également un ensemble de composants utiles avec des API fluides:
ScreenBuilders combine des usines fluides pour générer des recherches standard, des éditeurs et des écrans personnalisés. L'exemple ci-dessous montre comment ouvrir un écran à partir d'un autre. Notez que la méthode build () renvoie l'instance d'écran du bon type, sans qu'il soit nécessaire de la convertir de manière non sécurisée.
CurrencyConversions currencyConversions = screenBuilders.screen (this)
.withScreenClass (CurrencyConversions.class)
.withLaunchMode (OpenMode.DIALOG)
.build ();
currencyConversions.setBaseCurrency (Currency.EUR);
currencyConversions.show ();
Le composant Écrans fournit une abstraction de niveau inférieur pour créer et afficher des écrans plutôt que ScreenBuilders . Il donne également accès aux informations sur tous les écrans ouverts dans votre application CUBA ( Screens # getOpenedScreens ) au cas où vous auriez besoin de les parcourir.
Les composants Notifications et Dialogues introduisent tous deux des interfaces pratiques et explicites. Voici un exemple de création et d'affichage d'une boîte de dialogue et d'une notification:
dialogs.createOptionDialog ()
.withCaption ("Ma première boîte de dialogue")
.withMessage ("Souhaitez-vous remercier l'équipe CUBA?")
.withActions (
nouvelle DialogAction (DialogAction.Type.YES) .withHandler (e ->
notifications.create ()
.withCaption ("Merci!")
.withDescription ("Nous apprécions tous les membres de la communauté")
.withPosition (Notifications.Position.MIDDLE_CENTER)
.withHideDelayMs (3000)
.show ()),
nouvelle DialogAction (DialogAction.Type.CANCEL)
)
.show ();
Liaison de données
CUBA permet un développement extrêmement rapide des interfaces utilisateur de backoffice non seulement en fournissant des outils visuels avancés avec des capacités étendues de génération de code, mais également par un ensemble riche de composants orientés données disponibles dès la sortie de l'emballage. Ces composants ont juste besoin de savoir avec quelles données ils travaillent et le reste sera géré automatiquement, par exemple les listes de recherche, les champs de sélection, les grilles diverses avec les opérations CRUD, etc.
Avant la version 7, la liaison de données était implémentée via des sources de données - des objets qui enveloppent une seule entité ou une collection d'entités pour les lier de manière réactive avec des composants orientés données. Cette approche a très bien fonctionné, cependant, au niveau de la mise en œuvre, il s'agissait d'un monolithe. L'architecture monolithique pose généralement des problèmes de personnalisation, donc dans CUBA 7, ce bloc solide a été divisé en 3 composants de données:
- Le chargeur de données est un fournisseur de données pour les conteneurs de données. Les chargeurs de données ne conservent pas les données, ils transmettent simplement tous les paramètres de requête requis à un magasin de données et alimentent les conteneurs de données avec l'ensemble de données résultant.
- Le conteneur de données conserve les données chargées (une seule entité ou un certain nombre d'entités) et les fournit aux composants sensibles aux données de manière réactive: toutes les modifications des entités enveloppées sont exposées aux composants d'interface utilisateur correspondants et vice versa, toutes les modifications de les composants de l'interface utilisateur entraîneront les modifications correspondantes dans son conteneur de données.
- Le contexte de données est un puissant gestionnaire de modification de données qui suit les modifications et valide toutes les entités modifiées. Une entité peut être fusionnée dans un contexte de données, elle fournira donc une copie de l'entité d'origine avec la seule différence, mais très importante: toutes les modifications de l'entité résultante et de toutes les entités auxquelles elle fait référence (y compris les collections) seront suivies, stockées et engagé en conséquence.
Les composants de données peuvent être déclarés dans des descripteurs d'écran ou instanciés par programmation à l'aide d'une fabrique spécialisée - DataComponents .
Divers
Ufff, les parties les plus importantes de l'API des nouveaux écrans sont décrites, alors permettez-moi de lister brièvement d'autres fonctionnalités importantes dans le niveau client Web:
- Historique et navigation des URL . Cette fonctionnalité résout un problème très courant de SPA avec le bouton «revenir en arrière» dans un navigateur Web, fournit un moyen facile d'attribuer des itinéraires aux écrans d'application et permet à une API de refléter l'état actuel d'un écran dans son URL.
- Formulaire au lieu de FieldGroup . FieldGroup est un composant orienté données pour afficher et modifier les champs d'une seule entité. Il déduit l'interface utilisateur réelle affichée pour un champ lors de l'exécution. En d'autres termes, si vous avez un champ Date dans votre entité, il sera affiché sous la forme d'un champ DateField . Cependant, si vous souhaitez utiliser ce champ par programme, vous devrez injecter ce champ dans le contrôleur d'écran et le caster manuellement dans le bon type ( DateField dans notre exemple ). Plus tard, nous changeons notre type de champ en un autre et notre application se bloque à l'exécution ... Le formulaire résout ce problème par une déclaration de type de champ explicite. Trouvez plus d'informations sur ce nouveau composant ici .
- L'intégration de composants JavaScript tiers est considérablement simplifiée, suivez la documentation pour intégrer des composants JavaScript personnalisés dans une application CUBA.
- Les attributs HTML / CSS peuvent désormais être facilement définis directement à partir du descripteur d'écran xml ou définis par programme. Trouvez plus d'informations ici .
Fonctionnalités du middleware
Le bloc précédent sur l'API des nouveaux écrans était plus grand que ce à quoi je m'attendais, donc dans cette section, je vais essayer d'être soigné!
Événement modifié par l'entité
L'événement Entity Changed est un événement d'application Spring qui se déclenche lorsque votre entité se rend dans un magasin de données, est physiquement insérée et se trouve à moins d'un pouce de sa validation. Ici, vous pouvez fournir des contrôles supplémentaires (par exemple, vérifier la disponibilité des produits en stock avant de confirmer une commande) et les modifier (par exemple recalculer les totaux) juste avant qu'il ne soit visible pour d'autres transactions (bien sûr avec un niveau d'isolement validé lu). Vous pouvez également utiliser cet événement comme dernière chance d'interrompre la validation de la transaction en lançant une exception, ce qui peut être utile dans certains cas d'angle.
Il existe également un moyen d'attraper l'événement Entity Changed juste après la validation.
Suivez ce chapitre de la documentation pour voir un exemple.
Gestionnaire de données transactionnelles
Lors du développement d'une application, nous fonctionnons normalement avec des entités détachées - celles qui ne sont gérées par aucune transaction. Cependant, travailler avec des entités détachées n'est pas toujours possible, en particulier lorsque vous essayez de répondre aux exigences ACID - c'est le cas lorsque vous pouvez utiliser le gestionnaire de données transactionnelles. Il ressemble beaucoup au gestionnaire de données ordinaire, mais diffère par les aspects suivants:
- Il peut rejoindre une transaction existante (au cas où il est appelé dans un contexte transactionnel) ou créer sa propre transaction.
- Il n'a pas de méthode de validation , mais il existe la méthode save, qui ne conduit pas à une validation immédiate, mais attend que la transaction jointe soit validée.
Trouvez un exemple d'utilisation ici .
Rappels du cycle de vie JPA
Enfin, CUBA 7 prend en charge les rappels de cycle de vie JPA. Pour ne pas reproduire une information bien écrite sur l'utilisation de ces rappels, permettez-moi de partager ce lien , qui couvre entièrement le sujet.
Et la compatibilité?

Une bonne question pour toute version majeure, surtout quand il y a tellement de changements apparemment révolutionnaires! Nous avons conçu toutes ces nouvelles fonctionnalités et API en gardant à l'esprit la compatibilité descendante:
- L'ancienne API des écrans est prise en charge dans CUBA 7 et est implémentée via la nouvelle sous le capot :)
- Nous avons également fourni des adaptateurs pour l'ancienne liaison de données, qui continuent de fonctionner pour les écrans à l'ancienne.
Donc, bonne nouvelle, le chemin de migration de la version 6 vers la 7 devrait être assez simple.
Conclusion
Pour conclure cet aperçu technique, je voudrais mentionner qu'il existe d'autres innovations importantes, en particulier avec les licences:
- La limite de 10 entités pour Studio a maintenant disparu
- Rapports, BPM, graphiques et cartes et les compléments de recherche en texte intégral sont maintenant gratuits et open source.
- La version commerciale de Studio apporte un confort de développement supplémentaire avec les concepteurs visuels pour les entités, les écrans, les menus et d'autres éléments de plate-forme, tandis que la version gratuite se concentre sur l'utilisation du code
- Veuillez noter que pour 6.x et les versions antérieures des conditions de licence de Platform et Studio, les mêmes!
Enfin, permettez-moi de remercier à nouveau les membres de la communauté pour tout le soutien et les commentaires. J'espère que vous aimerez la version 7! La liste complète des modifications est traditionnellement disponible dans les notes de version .