Archive for the 'Domain Driven Design' Category

« Bounded contexts » et persistance

Julien on jan 18th 2010

Dans mon billet précédant, je faisais remarquer que la persistance pouvait être implémentée de différente façon au sein d'un même système dans le cadre d'une approche SOA/CQRS. Laissez moi clarifier ce que j'entends par cela (ou plutôt ce que j'ai appris du maitre, Udi Dahan !).

Il est possible de faire varier la persistance selon (au moins) 2 axes.

Tout d'abord, nous pouvons utiliser un ORM (Nhibernate, Entity Framework, etc) ou avoir une approche plus directe de type ado.net ou procédure stockée. L'idée ici est d'adapter la solution au contexte spécifique du cas d'utilisation.  Voici 2 exemples :

  • On souhaite mettre à jour l'ensemble des commande en cours d'un client pour changer le mode de livraison. Ce processus doit inspecter les commandes non traitées pour vérifier si le poids des colis est compatible avec le nouveau mode de livraison.
    => Ici, le processus de selection ést plus ou moins complexe ET le nombre de commandes en cours est probablement réduit. Un modèle objet est probablement adapté au traitement.
  • Notre système stocke des cours de bourse. Une société annonce un "split" (chaque action sera "coupée" en X de sorte que 1 action avant le split = X actions après le split. => Une société peut être amené à faire ce genre de choses quand elle estime qu'une action est devenue trop onéreuse). On souhaite appliquer le coefficient sur l'ensemble des cours stockés en base pour faciliter certains calculs. On a donc potentiellement des centaines ou des milliers de cours à mettre à jour.
    => Ici, utiliser un modèle objet n'a aucun sens. Une simple requête SQL du type "UPDATE quotes SET price = (price / X) WHERE ..." est clairement plus simple à mettre en œuvre et sera surtout beaucoup plus optimisée !

Peut importe la solution retenue, le plus important est d'assigner la responsabilité du schéma et la lecture/écriture à un seul et unique bounded context. Ce point est capital : il nous garantit de ne pas intégrer différents systèmes par l'intermédiaire d'un point central (la base de données relationnelles) qui sera :

  • difficile à faire évoluer, car couplé à l'ensemble du système
  • un point de défaillance unique
  • un goulot d'étranglement en terme de performances/montée en charge

Par conséquent, chaque bounded doit pouvoir faire évoluer sa persistance de façon transparente pour le reste du système, ce qui implique également que le support retenu peut être différent pour chaque cas (SQL, base objet, base de documents, fichier texte, etc).
Cependant, ce type de décisions sera fortement influencé par l'organisation interne du projet et la taille des équipes. Après tout, si chaque développeur choisit sa propre technologie, il sera difficile de coordoner le transfert de connaissance ou encore les backups. Il est donc tout à fait possible dans un premier temps de se limiter à un même support (ex : SQL Serveur) mais de séparer logiquement les bounded contexts en créeant des bases de données distinctes. Il faudra ensuite veiller à ce que les différents logins utilisé ait un accès limité à une seule et unique base.

Filed in Command Query Responsibility Segregation, Domain Driven Design | One response so far

« Command Query Responsibility Segregation » et « bounded contexts ».

Julien on jan 17th 2010

Je sors de mon sommeil pour aborder une question posée sur la mailing list française d'Alt.net qui me tient à coeur. Pour paraphraser, nous avons le cas d'un client qui devient privilégié sur un site de e-commerce, ce qui lui donne droit à une livraison expresse pour toutes ses commandes en cours. L'auteur de la question souhaite comprendre comment est réparti la logique (mise à jour du client / passage des commandes en cours à une livraison expresse) et pourquoi Udi Dahan considère que l'utilisation d'un modèle objet pour représenter le domaine est un détail d'implémentation dans le cadre d'une approche CQRS.

Voici mon interprétation après avoir assisté à la formation d'Udi sur le SOA :

Commençons par la répartition de la logique :

Dans cet exemple, notre commande "MakeCustomerPreferred" sera exécuté dans un bounded context (entendez "service") différent de celui qui gère les ventes en cours. "MakeCustomerPreferred" semble par exemple rattaché à un service de CRM ou de marketing. En conséquence, cette commande ne peut pas mettre à jour directement une entité du bounded context "ventes". Ce n'est pas sa responsabilité (pensez "loose coupling" !) et ce ne serait pas scalable. Une fois que "MakeCustomerPreferred" aura été exécuté, le message handler (l'unité qui va traiter une commande ou un évènement donné) pourra lever un évènement du type "CustomerBecamePreferred". Ce dernier pourra lui même être écouté par un autre bounded context, comme celui des ventes, qui pourra à son tours décider de mettre à jour son sous système en conséquence.

En ce qui concerne l'utilisation d'un modèle objet du domaine :

Quand on applique cette approche architecturale de façon globale, le choix d'utiliser un modèle objet ou une approche "transaction script" devient du cas par cas. Chaque message handler  étant indépendant, on est libre de choisir le meilleur outil pour chaque cas d'utilisation. Par exemple, si on ne fait que mettre à jour un champ d'une table : pas besoin d'ORM ! On pourrait tout à fait imaginer n'exécuter qu'une simple requête SQL de type UPDATE. Au contraire, si l'exécution d'une logique complexe fait parti du processus de mise à jour, alors il se peut qu'un modèle objet soit plus adéquat. (Et d'ailleurs, qui à dit que SQL était le système de persistance le plus adapté à notre cas d'utilisation? Tout dépend !)

Filed in Command Query Responsibility Segregation, Domain Driven Design | 4 responses so far

Domain Driven Design, pour aller plus loin…

Julien on déc 7th 2008

Pour faire suite à la présentation sur le DDD que Gauthier et moi avons fait à Alt.net, voici 2 ressources supplémentaires :

Je n'ai pas encore eu le temps d'écouter les 2 webcasts, mais je suppose que le 1er est très concret (les sources peuvent être téléchargées depuis le site de Rob Conery)

Filed in Domain Driven Design | One response so far

Slides de la présentation Domain Driven Design

Julien on déc 3rd 2008

Voici les slides que Gauthier et moi-même avons utilisé pour notre présentation sur le Domain Driven Design à Alt.net :

N'hésitez pas à les réutiliser si cela peut vous être utile ! N'hésitez pas non plus à nous laisser un petit mot si c'est le cas ;-) Juste par curiosité :-).

Enfin, petit message personnel : Je cherche du boulot ! Donc si vous montez une équipe agile de la mort qui tue, contactez moi!

Filed in Alt.net Foundations, Domain Driven Design, Rencontres Alt.net | 3 responses so far

DDD et internal

Julien on nov 21st 2008

Je poursuis mes réflexions sur l'impact que le domain driven design a sur ma façon de développer... :-)

Jusqu'à très récemment, le nombre de fois ou j'ai utilisé le mot clef internal devait se compter sur les doigts des 2 mains. La faute au fait que je n'ai que rarement développé des API consommés par l'extérieur? Ou peut être tout simplement un gros défaut dans mon style de développement :-)!
Quoiqu'il en soit, j'ai remarqué que j'avais fortement évolué depuis que je m'intéresse au domain driven design. Je vais reprendre l'exemple qui me tient à cœur en ce moment, a savoir le cas d'un panier sur un site de e-commerce !

J'ai donc 3 classes. ShoppingCart, Product et ShoppingCartItem :

  1.  
  2. public class ShoppingCart
  3. {
  4. private int _id;
  5. private readonly IList<ShoppingCartItem> _items;
  6.  
  7. public ShoppingCart()
  8. {
  9. _items = new List<ShoppingCartItem>();
  10. }
  11.  
  12. // + les getters
  13. }
  14.  
  15. public class Product
  16. {
  17. private int _id;
  18. private readonly string _name;
  19.  
  20. public Product(string name)
  21. {
  22. _name = name;
  23. }
  24.  
  25. // + les getters
  26. }
  27.  
  28. public class ShoppingCartItem
  29. {
  30. private readonly Product _product
  31. private readonly ShoppingCart _cart;
  32. private int _quantity;
  33.  
  34. public ShoppingCartItem(ShoppingCart cart, Product product)
  35. {
  36. _cart = cart;
  37. _product = product
  38. }
  39.  
  40. // + les getters
  41. }
  42.  

La modélisation est simple et à priori valide. Si je veux ajouter un Product à mon ShoppingCart, j'écrirai le code suivant :

  1.  
  2. var myCart = new ShoppingCart();
  3. var myProduct = new Product();
  4.  
  5. myCart.Items.Add(new ShoppingCartItem(myCart, myProduct));
  6.  

SSSSSTTTTOOOOOOPPPPPPP !!
Certes, ce code est valide mais je viole plusieurs principes (par exemple la loi de Déméter) et myCart.Items.Add(new ShoppingCartItem(myCart, myProduct)); n'a pas beaucoup de sens au niveau du modèle. En effet, si on se met à la place d'un client d'Amazon, on va ajouter un produit à son panier (un dvd ou autre) et non pas autre chose. Il serait donc beaucoup plus logique d'avoir le code suivant :

  1.  
  2. var myCart = new ShoppingCart();
  3. var myProduct = new Product();
  4.  
  5. myCart.AddProduct(myProduct);
  6.  

Une implémentation de AddProduct pourrait ressembler à cela :

  1.  
  2. public ShoppingCartItem AddProduct(Product product)
  3. {
  4. ShoppingCartItem item = _items.SingleOrDefault(x => x.Product == product);
  5.  
  6. if(item == null) {
  7. item = new ShoppingCartItem(this, product);
  8.  
  9. _items.Add(item);
  10. } else {
  11. item.Quantity += 1;
  12. }
  13.  
  14. return item;
  15. }
  16.  

Note : la collection Items deviendrait évidemment une ReadOnlyCollection !

Pour revenir au sujet de cet article, quel est le rapport avec le mot clef internal?

Dans ce contexte précis (et c'est évidemment très dépendant de votre domaine), la classe ShoppingCartItem ne devrait jamais être instancié directement en dehors du modèle, cela n'a pas de sens. J'irai même plus loin : seules la classe ShoppingCart et la couche de persistance ne devrait pouvoir l'instancier. A défaut de mieux, on devra donc modifier l'accessibilité du constructeur de ShoppingCartItem à internal afin d'éviter que les consommateurs du domaine n'en fasse mauvais usage.

Filed in Domain Driven Design | 2 responses so far

Le pattern Repository, testing et domain driven design…

Julien on nov 19th 2008

J'ai récemment posé la question suivante sur la mailling list française d'alt.net :

J'aimerai savoir comment vous testez vos repository dans vos projets,
et quels sont les + et les - de vos solutions.

Je sais qu'il y a plusieurs solutions :
1) Tester le repository avec une bdd de dev "classique"
2) Tester le repository avec une base sqlite chargée en mémoire
3) Ajouter une couche d'abstraction supplémentaire entre le repository
et la persistance, puis remplacer la couche persistance lors des tests
par une implémentation Fake à base de hastable ou autre.

La solution 1) n'est pas une option à cause de la vitesse d'execution
des tests.

La solution 2) est très intéressante mais un peu galère à mettre en
place (car il faut recréer le schéma systématiquement, sachant qu'un
schéma pour sql server n'est pas compatible avec sqlite sans
modifications manuelles). Je voulais utiliser migrator.net pour gérer
le problème mais c'est un peu buggé.

La solution 3) me parait la plus simple, mais elle ajoute un niveau
d'abstraction supplémentaire qui n'a à priori raison d'être que pour
les tests

Quel est votre expérience? Que pensez vous de chaque solution? quelles
sont vos best-practices?

Thanks!

J'ai eu des réponses très intéressantes à cette question qui m'ont permis de réfléchir et de trouver la méthode qui me satisfait le plus.

Pour comprendre mon choix, il faut d'abord revenir sur le pattern Repository...

La façon la plus simple d'implémenter ce pattern est d'utiliser un repository générique dont hériteront des repository typés. On se retrouve par exemple avec cela :

  1. public interface IRepository where T : Entity
  2. {
  3. T Find(long id);
  4. T FindBy(Expression&gt; where);
  5.  
  6. IList Fetch(Expression&gt; where);
  7. IList FetchAll();
  8.  
  9. void Save(T target);
  10. void Delete(T target);
  11. }
  12.  
  13. public interface IProductRepository : IRepository
  14. {
  15. IList FetchTopProducts();
  16. IPaginable Search(string keywords, long categoryId);
  17. }
  18.  

Puis :

  1. public class Repository : IRepository
  2. {
  3. // ...
  4. }
  5.  
  6. public class ProductRepository : Repository, IProductRepository
  7. {
  8. // ...
  9. }

Autrement dit, Repository se charge des fonctions de bases : chercher par id, sauvegarder, supprimer... Et ProductRepository se charge de construire les requêtes spécifiques a l'entité Product. C'est simple, rapide, efficace!

Cependant, cette implémentation à deux inconvénients :

  • Repository est une simple sur-couche à l'ORM que l'on utilise (NHibernate, Linq to Sql, Entity Framework, etc.). Pour tester ses repository, on est donc obliger de taper dans une base de test (ce qui est généralement lent) ou de mocker l'ORM (ce qui rends l'écriture des tests unitaires très fastidieuse).
  • Systématiquement hériter de Repository rends accessible l'ensemble des fonctionnalités de cette même classe. Hors cela peut être problématique du point de vue du domaine si l'on veut par exemple interdire de supprimer une entité! Pourquoi devrait-on offrir la fonction dans l'interface si le comportement est à proscrire?

Une solution plus flexible consiste à injecter Repository dans ProductRepository. Soit :

  1.  
  2. public interface IProductRepository
  3. {
  4. Product Find(long id);
  5. void Save(Product target);
  6.  
  7. IList<Product> FetchTopProducts();
  8. IPaginable<Product> Search(string keywords, long categoryId);
  9. }
  10.  
  11. public class ProductRepository : IProductRepository
  12. {
  13. private readonly IGenericRepository<Product> _genericRepository;
  14.  
  15. public ProductRepository(IGenericRepository<Product> genericRepository)
  16. {
  17. _genericRepository = genericRepository;
  18. }
  19.  
  20. public Product Find(long id)
  21. {
  22. return _genericRepository.Find(id);
  23. }
  24.  
  25. // ...
  26. }
  27.  

On constate alors que IProductRepository n'implémente plus IRepository, tout comme ProductRepository n'hérite plus de Repository.

Cette implémentation règle nos deux problèmes :

  • Je n'expose que les fonctionnalités qui ont du sens par rapport à mon domaine. La qualité de ce dernier s'améliore en conséquence!
  • Je peux injecter dans ProductRepository un InMemoryRepository pour faire mes tests. Je peux donc tester FetchTopProducts() sans faire d'aller retour sur la base et sans mocker mon ORM. Soit par exemple :
  1.  
  2. [Test]
  3. public void should_return_top_products()
  4. {
  5. var memoryRepository = new InMemoryRepository<Product>();
  6. var repository = new ProductRepository(memoryRepository);
  7.  
  8. memoryRepository.Save(new Product("Product1", true));
  9. memoryRepository.Save(new Product("Product2", true));
  10. memoryRepository.Save(new Product("Product3", false));
  11.  
  12. var results = _repository.FetchTopProducts();
  13.  
  14. results.Count.ShouldEqual(2);
  15. results[0].Name.ShouldEqual("Product1");
  16. results[0].IsTopProduct.ShouldBeTrue();
  17. }
  18.  

Filed in Domain Driven Design | 2 responses so far