La transposition de processus métiers complexes en programmes informatiques est parfois difficile pour des raisons techniques et/ou communicationnelles. Le faire efficacement et sous une forme flexible l'est encore plus. Pousse-Café, un framework Java se basant sur le Domain-Driven Design (DDD), rend la tâche plus abordable. https://www.pousse-cafe-framework.org/
Par Gérard Dethier, aux Geeks Anonymes du 15 janvier 2021
Vidéo : https://youtu.be/DE0QpTIz1cQ
3. Tout est processus...
▶
La majorité des systèmes IT implémentent des processus
métier
▶
L’implémentation implique le stockage d’un état (les
données)
▶
L’état évolue par réaction à des stimuli (des commandes
soumises par les utilisateurs)
4. Une exécution efficace est importante
▶
L’exécution d’un grand nombre de transactions par unité
de temps est parfois nécessaire
▶
Ces transactions peuvent devoir être exécutées en
parallèle
▶
« Time is money »
5. La maintenabilité est essentielle
▶
Les conditions évoluent
▶
Besoins (métier)
▶
Attentes (utilisateurs)
▶
Équipes (composition)
6. Le Domain-Driven Design apporte des outils
▶
Domain-Driven Design = DDD
▶
Un langage commun entre développeurs et experts du domaine
▶
Le code est un modèle de la réalité du terrain
▶
Des règles de conception favorisant
▶
maintenabilité
▶
efficacité
▶
robustesse
8. DDD est une méthodologie de modélisation
▶
Développeurs et experts coconstruisent un modèle abstrait
▶
DDD fournit des composants de base
▶
Ils utilisent un langage commun
▶
Le Ubiquitous Language
▶
Le modèle est descriptible à l’aide de code
9. Les composants ont différentes caractéristiques
▶
État
▶
Représentation des données
▶
Les données sont immuables ou non
▶
Maintient des contraintes liées aux données
▶
Comportement
▶
Opérations pouvant impliquer l’état
▶
Structure
▶
Regroupement d’autre composants
▶
Stratégie
▶
Possibilité de raisonner sur des systèmes « morcelés » et à grande échelle
10. Les composants avec un état
▶
Value Object (VO)
▶
Données immuable
▶
Le comportement n’altère pas l’état
▶
Entity
▶
Données non-immuables
▶
Le comportement peut altérer l’état
▶
Domain event (ou event)
▶
Permet le transport de données
▶
Pas de comportement
11. Aggregate et composants liés
▶
Aggregate
▶
Graphe d’Entities et VOs liés par un ensemble de contraintes fortes
▶
Accès uniquement via l’Aggregate Root (= Entity garantissant le respect des contraintes)
▶
Factory
▶
Produit des Aggregates
▶
Les contraintes sont respectées initialement
▶
Repository
▶
Stocke et récupère des Aggregates
▶
Les contraintes sont respectées au moment du stockage et lors de la récupération
13. Autres composants
▶
Service
▶
Pas d’état
▶
Comportement qui ne peut être mis ailleurs
▶
Bounded Context
▶
Stratégie
▶
Partition d’un système
▶
Peut contenir un ou plusieurs Modules, voir des fragments
▶
Context Map
▶
Graphe liant les Bounded Contexts
▶
Lien = dépendance (librairies), transport de données (event ou autre)
14. Quelques règles de conception...
▶
Un seul Aggregate mis à jour par transaction
▶
Les règles de consistance portant sur plusieurs
Aggregates sont implémentées à l’aide Events
▶
Les Aggregates doivent être les plus petits possibles
▶
Le code documente le modèle implémenté
▶
Conventions de nommage
▶
Utilisation du Ubiquitous Language
15. Appliquer DDD n’est pas toujours simple
▶
Bonne connaissance théorique requise
▶
Trop abstrait pour les experts métier
▶
Pas assez concret pour les développeurs
▶
Implique la résolution de difficultés techniques
▶
Séparation claire entre modèle et détails techniques
▶
Dans un contexte distribué, transport et traitement des
événements
17. Pousse-Café (PC) est un framework...
▶
Java (11+)
▶
Libre et open-source (Apache 2.0)
▶
Léger
▶
Modulaire
▶
Facilitant...
▶
l’implémentation efficace de processus complexes
▶
l’abstraction du stockage et du transport des données
▶
une bonne communication entre développeurs et experts métier
18. PC interprète le DDD dans l’« univers » Java
▶
Classes
▶
VOs
▶
Entities
▶
Factories
▶
Repositories
▶
Services
▶
Aggregates
▶
Interfaces
▶
Events
▶
Modules
19. PC introduit quelques éléments supplémentaires
▶
Les Commands
▶
Requêtes soumises par les utilisateurs
▶
Définies par une classe ou une interface
▶
Les Processes
▶
Interfaces nommant et décrivant des processus métiers
▶
Les Listeners
▶
Méthodes traitant Event ou Command
▶
Liés à un ou plusieurs Processes
20. PC fournit un Runtime
▶
Charge un ensemble défini de composants au démarrage
▶
Reçoit des Commands
▶
Consomme les Events à partir d’un bus de communication
▶
Pour chaque Command/Event traité
▶
détermine le(s) Aggregates à créer/mettre à jour/supprimer
▶
exécute les Listeners des Aggregate Roots, Factories et Repositories
▶
sauvegarde les changements et publie les Events potentiellement émis
22. Il y a quelques étapes...
▶
Définir le Process
▶
Définir les Aggregates
▶
Aggregate Roots
▶
Factories
▶
Repositories
▶
Définir les Commands et Events
▶
Créer les Listeners et les lier au Process
24. Définition du Process ProductManagement
public interface ProductManagement extends Process {
}
25. Définition de l’Aggregate Product
@Aggregate
public class Product {
public static class Factory extends AggregateFactory<ProductId, Root, Root.Attributes> {
}
public static class Root extends AggregateRoot<ProductId, Root.Attributes> {
public static interface Attributes extends EntityAttributes<ProductId> {
Attribute<Integer> totalUnits();
Attribute<Integer> availableUnits();
}
}
public static class Repository extends AggregateRepository<ProductId, Root, Root.Attributes> {
@Override
public ProductDataAccess<Root.Attributes> dataAccess() {
return (ProductDataAccess<Attributes>) super.dataAccess();
}
}
}
public interface ProductDataAccess<N extends Product.Root.Attributes>
extends EntityDataAccess<ProductId, N> {
}
public class ProductId extends StringId {
public ProductId(String id) {
super(id);
}
}
Aggregate et composants liés
Définition des requêtes utilisées par le dépôt Identifiant de l’entité racine
26. Définition des Commands
public interface CreateProduct extends Command {
Attribute<ProductId> productId();
}
public interface AddUnits extends Command {
Attribute<ProductId> productId();
Attribute<Integer> units();
}
27. Ajout d’un Listener pour la création d’un produit
public class Factory extends AggregateFactory<ProductId, Root, Root.Attributes> {
@MessageListener(processes = ProductManagement.class)
public Root buildProductWithNoStock(CreateProduct command) {
Root product = newAggregateWithId(command.productId().value());
product.attributes().totalUnits().value(0);
product.attributes().availableUnits().value(0);
return product;
}
}
28. Ajout d’un Listener pour l’ajout d’unités
public static class Root extends AggregateRoot<ProductId, Root.Attributes> {
@MessageListener(runner = AddUnitsRunner.class, processes = ProductManagement.class)
public void addUnits(AddUnits command) {
int units = command.units().value();
if(units <= 0) {
throw new DomainException("Cannot add negative number of units");
}
attributes().availableUnits().value(attributes().availableUnits().value() + units);
attributes().totalUnits().value(attributes().totalUnits().value() + units);
}
...
}
public class AddUnitsRunner
extends UpdateOneRunner<AddUnits, ProductId, Product.Root> {
@Override
protected ProductId aggregateId(AddUnits message) {
return message.productId().value();
}
}
Service permettant la localisation de l’Aggregate
Listener dans l’Aggregate Root
29. Simple mais un peu fastidieux...
▶
Chaque classe/interface est simple à implémenter
▶
… mais il y en a « beaucoup »
▶
… et on en a vite oublié une.
▶
Le principe de séparation des préoccupations est à ce prix
▶
… tout comme la séparation entre définition et
implémentation
30. PC fournit des outils adressant ce problème
▶
EMIL (Extended Messaging Intermediate Language), un
DSL permettant la description concise d’un processus
implémenté par PC
▶
Des outils de génération de code prenant une description
EMIL en entrée
31. La représentation EMIL de ProductManagement
process ProductManagement
CreateProduct? -> F{Product.Factory}[buildProductWithNoStock]
AddUnits? -> Ru{AddUnitsRunner}
@Product.Root[addUnits]
▶
Les outils de génération de code vont créer toutes les
classes et méthodes
▶
Il ne restera plus qu’à remplir le corps des méthodes
33. PC utilise un mécanisme d’extensions
▶
Extension = un ensemble de classes implémentant certaines interfaces
▶
Deux types d’extensions
▶
Stockage
▶
Transport (des Events)
▶
Les classes fournies par les extensions aident les développeurs à
implémenter
▶
Les données concrètes (Entities et Events)
▶
Les requêtes vers une base de donnée (Storage)
▶
La publication des données liés aux Events (Messaging)
35. Plusieurs extensions de stockage disponibles
▶
Mémoire (en vue de tester la logique métier)
▶
Spring Data MongoDB
▶
Spring Data JPA
36. Implémentation des Commands
@MessageImplementation(message = CreateProduct.class)
@SuppressWarnings("serial")
public class CreateProductData implements
Serializable, CreateProduct {
@Override
public Attribute<ProductId> productId() {
return stringId(ProductId.class)
.read(() -> productId)
.write(value -> productId = value)
.build();
}
private String productId;
}
@MessageImplementation(message = AddUnits.class)
@SuppressWarnings("serial")
public class AddUnitsData implements Serializable,
AddUnits {
...
@Override
public Attribute<Integer> units() {
return single(Integer.class)
.read(() -> units)
.write(value -> units = value)
.build();
}
private int units;
}
37. Plusieurs extensions de transport disponibles
▶
Mémoire (en vue de tester la logique métier)
▶
Pulsar (messages JSON)
▶
Kafka (messages JSON)
38. Les extensions génèrent aussi du code
▶
L’outil de génération de code peut utiliser la logique fournie
par les extensions
▶
Les classes d’implémentation du stockage et du transport
seront générées
40. Cela dépend du type d’application
▶
JavaSE
▶
Application web propulsée par Spring Boot
▶
...
41. Dans tous les cas, il faut définir un Module
▶
Un Module est représenté par une interface
▶
Tout composant définit dans un subpackage y est inclus
▶
Chaque composant du module doit avoir un nom unique
package poussecafe.shop;
import poussecafe.domain.Module;
/**
* Models an online shop where Customers may buy Products by placing Orders. Customers receive Messages giving them
* an update about the handling of their Orders.
*/
public interface Shop extends Module {
}
42. Ensuite, sélectionner les Modules à charger
public class ShopBundle {
public static BundleConfigurer configure() {
return new BundleConfigurer.Builder()
.module(Shop.class)
.build();
}
private ShopBundle() {
}
}
Ajout du module Shop dans un Bundle
43. Enfin, instancier le Runtime
▶
Ci-dessous, un exemple Spring Boot
▶
Définition et implémentation sont « connectés »
@Configuration
@Import(RuntimeConfiguration.class)
public class ApplicationConfiguration {
@Bean
protected Bundles bundles(
Messaging messaging,
SpringJpaStorage storage) {
MessagingAndStorage messagingAndStorage = new MessagingAndStorage(messaging, storage);
return new Bundles.Builder()
.withBundle(ShopBundle.configure()
.defineThenImplement()
.messagingAndStorage(messagingAndStorage)
.build())
.build();
}
}
44. PC fournit un bridge avec Spring
▶
Ce bridge permet l’injection de composants PC dans des
Bean
@PostMapping(path = "/product")
public void createProduct(@RequestBody CreateProductView input) {
ProductId productId = new ProductId(input.id);
CreateProduct command = runtime.newCommand(CreateProduct.class);
command.productId().value(productId);
runtime.submitCommand(command);
}
@Autowired
private Runtime runtime;
Injection et interaction avec le Runtime PC
46. Le Ubiquitous Language apparaît dans le code
▶
PC favorise la définition d’éléments dont le nom est
naturellement issu du Ubiquitous Language
▶
… mais les experts lisent rarement du code
▶
… et un DSL comme EMIL reste rébarbatif
47. PC fournit un outil de génération de doc
▶
Le code est l’unique référence
▶
HTML ou PDF sont générés automatiquement
▶
La documentation inclut du texte et des schémas
Schéma de processusExtrait de texte
48. Je veux me lancer ! Quels sont
les outils à ma disposition ?
49. Un plug-in et un archetype Maven
▶
Génération de projet (via l’archétype)
▶
mvn archetype:generate ...
▶
Validation de code
▶
mvn pousse-cafe:validate
▶
Génération de documentation
▶
mvn pousse-cafe:generate-doc
▶
Génération de code
▶
mvn pousse-cafe:update-process -DprocessName=...
51. Du contenu en ligne
▶
Un site web (avec un guide de référence)
https://www.pousse-cafe-framework.org/
▶
Le code source et des exemples
https://github.com/pousse-cafe
▶
Un tag sur StackOverflow
https://stackoverflow.com/questions/tagged/pousse-cafe
▶
Un compte Twitter
@PousseCafeFW