Valeurs calculées et lectures simples - Une douleur lancinante pour mes conceptions pilotées par domaine!

9

Le problème auquel je suis constamment confronté est de savoir comment gérer les valeurs calculées pilotées par la logique du domaine tout en travaillant efficacement contre le magasin de données.

Exemple:

Je renvoie une liste de produits de mon référentiel via un service. Cette liste est limitée par les informations de pagination de la demande DTO envoyée par le client. De plus, le DTO spécifie un paramètre de tri (une énumération conviviale pour le client).

Dans un scénario simple, tout fonctionne très bien: le service envoie des expressions de pagination et de tri au référentiel et le référentiel émet une requête efficace vers la base de données.

Cependant, tout cela tombe en panne lorsque je dois trier les valeurs générées en mémoire à partir de mon modèle de domaine. Par exemple, la classe Product a une méthode IsExpired () qui renvoie un booléen basé sur la logique métier. Maintenant, je ne peux pas trier et paginer au niveau du référentiel - tout aurait été fait en mémoire (inefficace) et mon service devrait connaître les subtilités du moment où émettre ces paramètres au référentiel et quand effectuer le tri / la pagination lui-même.

Le seul modèle qui semble avoir du sens pour moi est de stocker l'état de l'entité dans la base de données (faire d'IsExpired () un champ en lecture seule et de le mettre à jour via la logique du domaine avant de l'enregistrer). Si je sépare cette logique dans un référentiel séparé "read model / dto" et "reporting", je rend mon modèle plus anémique que je ne le souhaiterais.

BTW, chaque exemple que j'ai vu pour des calculs comme celui-ci semble vraiment s'appuyer sur le traitement en mémoire et sur le fait qu'il est beaucoup moins efficace à long terme. Peut-être que j'optimise prématurément, mais cela ne me convient pas.

J'adorerais savoir comment les autres ont géré cela, car je suis sûr que c'est courant sur presque tous les projets impliquant DDD.

drogon
la source

Réponses:

3

Je ne pense pas que le fait d'avoir deux modèles de domaine différents du même modèle de données rend votre domaine anémique. Un modèle de domaine anémique est un modèle dans lequel la logique métier qui change souvent est cachée au domaine dans une couche de service (ou, pire, dans la couche d'interface utilisateur).

La séparation des modèles de domaine de commande et de requête est souvent adoptée et a un joli acronyme que vous pouvez google dans CQRS (Command Query Responsibility Segregation).

Utilisation du modèle de modèle de domaine, Udi Dahan

Alors que je réussissais dans le passé à créer un modèle d'objet persistant unique qui traitait à la fois les commandes et les requêtes, il était souvent très difficile de le mettre à l'échelle, car chaque partie du système tirait le modèle dans une direction différente.

Il s'avère que les développeurs assument souvent des exigences plus strictes que celles dont l'entreprise a réellement besoin. La décision d'utiliser les entités de modèle de domaine pour afficher des informations à l'utilisateur n'est qu'un exemple.

[...]

Pour ceux qui sont assez âgés pour se souvenir, les meilleures pratiques autour de l'utilisation de COM + nous ont guidés pour créer des composants séparés pour la logique en lecture seule et pour la logique en lecture-écriture. Nous voici, une décennie plus tard, avec de nouvelles technologies comme Entity Framework, mais ces mêmes principes continuent de s'appliquer.

CQRS avec acteurs Akka et modèles de domaine fonctionnel, Debasish Ghosh

Greg Young a donné d'excellentes sessions sur DDD et CQRS. En 2008, il a déclaré: "Un modèle unique ne peut pas être approprié pour les rapports, les recherches et les comportements transactionnels". Nous avons au moins deux modèles - l'un qui traite les commandes et transmet les modifications à un autre modèle qui sert les requêtes et les rapports des utilisateurs. Le comportement transactionnel de l'application s'exécute via le modèle de domaine riche d'agrégats et de référentiels, tandis que les requêtes sont directement traitées à partir d'un modèle de données dénormalisé.

CQRS, Martin Fowler

Le changement que CQRS introduit consiste à diviser ce modèle conceptuel en modèles distincts pour la mise à jour et l'affichage, qu'il appelle respectivement Commande et Requête en suivant le vocabulaire de CommandQuerySeparation. La raison en est que pour de nombreux problèmes, en particulier dans des domaines plus complexes, le fait d'avoir le même modèle conceptuel pour les commandes et les requêtes conduit à un modèle plus complexe qui ne fonctionne pas bien.

En bref, votre idée d'avoir le modèle de commande gérer l'expiration et le transmettre à la base de données est tout à fait correcte. Lisez le premier article ci-dessus et vous verrez des scénarios similaires mais plus complexes.

pdr
la source
2

SPÉCIFICATION

Je sais que vous avez déjà accepté une réponse, mais, vous avez posé une question sur DDD, et la correspondance exacte pour cela est ce qu'Evans appelle une `` spécification '':
lien direct Google Books
Si ce lien ne fonctionne pas, vérifiez le livre dans ces résultats
C'est la page 226 si vous avez le livre.

À la page 227, vous trouverez 3 utilisations pour les spécifications: validation, sélection, construction d'un nouvel objet spécial. Le vôtre est «sélection» - IsExpired.

Une autre chose à propos du concept de `` specificaiton '' est qu'il admet que - pour des raisons d'efficacité - vous pouvez avoir besoin d'une version de code pour fonctionner sur les objets en mémoire, et d'une autre version du code pour interroger efficacement le référentiel sans avoir à obtenir au préalable tous les objets en mémoire.

Dans un monde simple, cela signifierait de mettre une version SQL dans votre référentiel et une version objets dans votre modèle, bien sûr qui présente des inconvénients. La logique est à 2 endroits (mauvais, quelqu'un va oublier de mettre à jour ces endroits) et il y a une logique de domaine dans votre référentiel.

La réponse est donc de mettre les deux ensembles de logique dans une spécification. La version en mémoire, bien sûr, mais aussi une version de référentiel. Si vous utilisez par exemple n-hibernate, vous pouvez utiliser son langage de requête intégré pour la version du référentiel.

Sinon, vous devrez créer une méthode de référentiel spéciale pour cette spécification qui est utilisée à partir de l'objet de spécification. Les appels de collecitons d'objets correspondant à la spécification passeraient par la spécification et non par le référentiel. Et au moins le code crie «je suis à 2 endroits, ne l'oubliez pas» aux futurs responsables. Il y a un merveilleux exemple à la page 231-232 pour résoudre un problème très similaire.

La spécification est une fuite / glissement «autorisée» de la «pureté» du DDD. Il pourrait ne pas répondre à vos besoins à diverses fins. Par exemple, l'ORM peut générer un mauvais SQL; il pourrait y avoir trop de codage supplémentaire. Vous devrez donc peut-être appeler des méthodes de référentiel de sorte que cela ressemble presque à mettre SQL dans la spécification. Une mauvaise chose bien sûr. Mais n'oubliez pas, votre programme doit fonctionner à une vitesse raisonnable. Il n'a pas à gagner un prix de pureté DDD. Donc, une réalité de changer de magasin de données pourrait signifier une intervention chirurgicale à l'ancienne tout au long du programme. C'est aussi une mauvaise chose. Mais pas aussi mauvais qu'un programme lent (aka SUCKing). Si la sortie de la boîte sur diverses bases de données est une réalité, vous dupliquerez évidemment les règles métier pour chaque magasin de données pour chaque spécification. Au moins, vous avez le doigt sur la question et vous pouvez utiliser le modèle de stratégie lorsque vous échangez des référentiels. Mais si vous utilisez déjà une base de données spécifique, souvenez-vousYAGNI.

Concernant CQRS: la citation de Fowler par pdr ci-dessus reste vraie ici: "avoir le même modèle conceptuel pour les commandes et les requêtes conduit à un modèle plus complexe qui ne fonctionne pas bien" ... et vous devrez peut-être utiliser CQRS ou similaire. Mais c'est beaucoup plus cher du point de vue du développement et de la maintenance. Si vous êtes un fournisseur de packages en concurrence avec d'autres, cela pourrait payer. Si vous écrivez une application LOB personnalisée pour un client, la recherche de la perfection est un mauvais choix. Vous devez décider si la valeur d'avoir un modèle complètement ou principalement double vaut l'effort supplémentaire. spécificationest un bon compromis car il vous permet de faire cette séparation en une seule petite partie du programme qui en a besoin, avec la rapidité (de développement) et la simplicité d'un modèle. Bonne chance!

FastAl
la source
Cela est parfaitement logique. Je pense que je dois mordre la balle et lire le livre d'Evans :-) Je vois maintenant que le fait d'avoir une compréhension superficielle de ces concepts peut vraiment vous paralyser!
drogon
0

Je suppose que je remettrais en question la logique métier qui détermine si isExpired est vrai ou non. Cette logique peut-elle être exécutée par une requête en l'état du modèle de données? Dans l'affirmative, pouvez-vous rendre votre référentiel suffisamment intelligent pour utiliser la logique "isExpired" lorsque vous lui demandez des produits d'une certaine manière? Sinon, vous devrez peut-être réexaminer votre modèle de données.

DDD ne signifie pas que vos référentiels doivent être stupides - cela signifie simplement que votre domaine doit savoir comment parler à vos référentiels.

Matthew Flynn
la source