Dans les précédents articles [6, 7], nous avons présenté les outils et les principes du Domain Driven Design (DDD). Cette fois, nous allons continuer l’Odyssée sur la mise en œuvre de la conception collaborative par des exemples codés en Python.
Le Domain Driven Design (DDD) met l'accent sur l'importance de la collaboration entre les développeurs, les analystes commerciaux et les autres parties prenantes dans le processus de développement logiciel. En travaillant ensemble, ils peuvent créer un modèle de domaine qui reflète avec précision les besoins du métier.
Le DDD invite à réviser la carte de contexte [7] pour analyser les liaisons en fonction de l’ouverture et la fermeture des contextes bornés.
1. Service Hôte Ouvert
Le service hôte ouvert (Open Host Service en anglais) est utilisé d'exposer le système externe en tant que service au modèle de domaine, plutôt que d'essayer de l'incorporer directement dans le domaine.
Supposons que nous ayons deux contextes bornés dans notre système de commerce électronique : la gestion des commandes et la gestion des clients. Le contexte borné Order Management est responsable de la gestion des commandes, tandis que le contexte borné Customer Management est responsable de la gestion des clients.
Nous voulons créer un Open Host Service qui permet au contexte borné Order Management d'accéder aux données du contexte borné Customer Management. Par ailleurs, nous allons profiter du module « dataclass » en Python 3.7+ qui fournit un moyen pratique de créer des classes avec des méthodes spéciales générées automatiquement, telles que __init__, __repr__ et __eq__.
Dans cet exemple, le contexte borné Customer Management expose un CustomerService qui permet au contexte borné Order Management d'accéder aux données de client. Le CustomerService prend un CustomerRepository comme dépendance, ce qui lui permet d'accéder aux données de client.
OrderService dans le contexte borné de Order Management prend un CustomerService comme dépendance et l'utilise pour obtenir les informations de client lors de la création d'une commande.
Cette implémentation d'un service d'hôte ouvert dans DDD permet une séparation des préoccupations entre les deux contextes bornés et fournit un moyen clair et normalisé pour le contexte borné de gestion des commandes d'accéder aux données du contexte borné de gestion des clients.
2. Noyau partagé
Le noyau partagé (Shared Kernel en anglais) est une relation entre deux ou plusieurs contextes bornés qui partagent une base de code ou un schéma de base de données commun.
Supposons que notre système de commerce électronique a deux contextes bornés, Product et Order. Dans le contexte Product, nous avons une entité Product qui possède des propriétés telles que le nom, la description et le prix. Dans le contexte Order, nous avons une entité Order qui possède des propriétés telles que customer, items et total_price.
Pour partager l'entité Product entre les deux contextes, nous pouvons définir l'entité Product dans un module partagé.
Désormais, dans les contextes bornés Order Management et Inventory Management, nous pouvons importer l'entité Product à partir du module partagé et l'utiliser au besoin.
Sinon, lorsqu'une classe de dataclass est créée, sa méthode __init__ est automatiquement générée en fonction des champs définis. Cependant, si vous souhaitez effectuer des opérations supplémentaires après l'initialisation de l'objet, vous pouvez définir la méthode __post_init__ dans la classe. En plus, elle ne prend aucun argument autre que self et peut accéder et modifier les attributs de l'objet selon les besoins. Elle sera appelée automatiquement par Python immédiatement après l'initialisation de l'objet.
De cette façon, toute modification apportée à l'entité Product dans le module partagé sera automatiquement répercutée dans les contextes Order et Inventory, garantissant la cohérence et évitant la duplication de code.
3. Client-fournisseur
Le client-fournisseur (Customer Supplier Teams en anglais) est une relation entre deux ou plusieurs contextes bornés où un contexte fournit un service ou des données à un autre contexte. Le contexte fournisseur est le fournisseur et le contexte récepteur est le client.
En e-commerce, l'équipe client serait responsable de la définition des exigences et des règles commerciales liées aux informations sur les produits et leurs stocks. Ils travailleraient en étroite collaboration avec l'équipe du fournisseur, qui serait l’inventaire qui gère les stocks de produits, y compris l’ajout et la suppression des données.
Dans cet exemple, le contexte Inventory Management définit la classe de données Inventory, qui gère une liste de produits, tandis que le contexte Order Management définit la classe de données Order et la classe OrderService, qui place les commandes. OrderService dépend de l'inventaire du contexte de gestion des stocks.
Dans cette relation, le service de commande est le client, qui dépend du fournisseur : l'inventaire pour vérifier si les articles d'une commande sont en stock avant de placer la commande.
En utilisant cette approche, l'équipe du client est en mesure de définir et de gérer sa logique de domaine séparément du fournisseur en amont, ce qui permet une plus grande flexibilité et une maintenabilité à long terme.
4. Conformiste
Le conformiste (Conformist en anglais) donne les conventions et les règles d'un autre contexte que le contexte conformiste suit en ajustant son comportement pour correspondre aux exigences du contexte dirigeant.
Supposons que nous ayons un contexte borné : Rating Management pour gérer les évaluations de produits. Ce contexte est responsable de la gestion des notes et des avis des clients pour chaque produit. Les évaluations sont stockées dans une base de données et peuvent être consultées par d'autres contextes bornés dans le système.
Du fait, dans un autre contexte borné responsable de la gestion des recommandations de produits, les données d'évaluation ne sont pas stockées dans le format requis pour des calculs de recommandation efficaces. Afin d'utiliser les données de notation dans ce contexte, nous devons les transformer dans le format correct.
C'est là que le modèle conformiste entre en jeu. The Conformist est une classe qui convertit les données d'un format à un autre.
Voici un exemple d'implémentation :
Dans cet exemple, nous obtenons un ensemble de tous les produits notés (à l'exclusion du produit actuel), puis calculons la note moyenne pour chaque produit noté dans une compréhension de liste qui crée une liste de tuples. La liste des tuples est ensuite triée par note moyenne et les meilleurs produits sont sélectionnés. Enfin, les produits recommandés sont obtenus à l'aide d'une liste en compréhension et renvoyés.
5. Couche anti-corruption (ACL)
La couche anti-corruption (Anti-corruption Layer en anglais) joue le rôle du pont entre les contextes ayant les incohérences des attributs, traduisant et validant les données échangées entre eux.
Disons que nous avons deux contextes bornés : Sales et Shipping. Le contexte Sales a une entité SalesCustomer qui a un nom et une adresse, tandis que le contexte Shipping a une entité ShippingCustomer similaire avec un nom et une adresse de livraison. Cependant, les deux contextes utilisent des termes différents pour désigner le même concept – « adresse de facturation » dans Sales et « adresse de livraison » dans Shipping. Pour éviter la confusion et les incohérences, nous pouvons utiliser une couche anti-corruption pour traduire entre les deux contextes.
Voici le code de la couche anti-corruption :
Dans cet exemple, lorsque le contexte Sales doit envoyer une entité SalesCustomer au contexte Shipping, il peut utiliser le SalesToShippingTranslator pour traduire l'entité Customer en une entité ShippingCustomer.
De cette façon, les deux contextes peuvent communiquer sans dupliquer le code ni causer de confusion.
6. Chemins séparés
Les Chemins séparés (Separate Ways en anglais) est la séparation des préoccupations par la décomposition d’un package ou un module monolithe en domaines ou sous-domaines distincts et bien définis.
Disons que nous avons un site Web de commerce électronique qui vend à la fois des produits physiques et numériques. Nous avons une classe Product qui représente les deux types de produits. Cependant, lorsque nous voulons traiter une commande, nous devons traiter différemment les produits physiques et numériques.
Pour appliquer le modèle Separate Ways, nous pouvons diviser la classe Product en deux classes distinctes : PhysicalProduct et DigitalProduct. Chaque classe peut avoir son propre ensemble d'attributs et de méthodes spécifiques au type de produit qu'elle représente.
Dans cet exemple, on crée une instance Customer avec son objet Adresse et deux instances de produit : PhysicalProduct et une DigitalProduct. Ensuite, il appelle la méthode process_order sur chaque instance de produit, en transmettant l'instance Customer. La logique de traitement de commande appropriée est exécutée en fonction du type de produit.
Du coup, le contexte PhysicalProduct et le contexte DigitalProduct peuvent être développés et déployés indépendamment.
Conclusion
Dans cet article, nous avons bien démarré les épreuve sur les pratiques de la conception collaborative en Python, tout en révisant la carte de contexte dans la conception pilotée par le domaine (DDD). En suivant ces pratiques, les développeurs peuvent créer des systèmes logiciels bien conçus, efficaces et faciles à maintenir.
Références
Evans, E. (2003). Domain-driven design: tackling complexity in the heart of software. Addison-Wesley Professional.
Vernon, V. (2011). Implementing domain-driven design. Addison-Wesley Professional.
Fowler, M. (2013). Patterns of enterprise application architecture. Addison-Wesley Professional.
Ghosh, S. (2016). Domain-driven design: A practical approach. Packt Publishing.
Larman, C. (2004). Applying UML and patterns: an introduction to object-oriented analysis and design and iterative development. Prentice Hall PTR.