Approche DDD des opérations CRUD de base dans une application complexe centrée sur le domaine

9

Mon entreprise est en train de réécrire notre application Web à partir de zéro. Il s'agit d'une grande application de niveau entreprise avec un domaine complexe dans le secteur financier.

Nous utilisons un ORM (Entity Framework) pour la persistance.

Essentiellement, la moitié de notre application se concentre sur la collecte de données brutes de l'utilisateur, leur stockage, puis l'autre moitié de l'application qui contient la plupart de notre logique de domaine réelle prend ces données brutes pour créer notre image de domaine qui diffère grandement de celles d'origine entrées brutes, et le passe dans un moteur de calcul, exécute des calculs et crache des résultats, qui sont ensuite affichés à l'utilisateur.

Dans une approche DDD utilisant des couches, il semble que les opérations CRUD passent par la couche domaine. mais au moins dans notre cas, cela ne semble pas avoir de sens.

Par exemple, lorsqu'un utilisateur accède à l'écran d'édition pour modifier un compte d'investissement, les champs à l'écran sont les champs exacts stockés dans la base de données, et non la représentation de domaine utilisée ultérieurement pour les calculs. Alors, pourquoi devrais-je charger la représentation du domaine du compte d'investissement lorsque l'écran d'édition a besoin de la représentation de la base de données (entrées brutes)?

Une fois que l'utilisateur a cliqué sur "Terminé" sur l'écran du compte d'investissement et qu'un POST a été effectué sur le contrôleur, le contrôleur a maintenant à peu près une représentation exacte de la base de données du compte d'investissement qu'il doit enregistrer. Mais pour une raison quelconque, je suis censé charger la représentation du domaine pour apporter des modifications au lieu de simplement mapper le modèle du contrôleur directement sur le modèle de base de données (modèle de structure d'entité)?

Donc, en gros, je mappe un modèle de données au modèle de domaine, juste pour qu'il puisse ensuite être mappé sur le modèle de données pour persister. Comment cela peut-il avoir un sens?

wired_in
la source

Réponses:

9

Imaginez que vous implémentiez votre page de création de compte en mappant la publication de formulaire directement sur un objet EF qui est ensuite enregistré dans la base de données.

Supposons en outre que la base de données comporte diverses restrictions qui empêchent la saisie de données complètement erronées. Les comptes ont toujours des clients, etc.

Tout semble fonctionner correctement. Mais alors, l'entreprise établit une nouvelle règle.

  • Les comptes créés un jeudi bénéficient d'un taux d'intérêt boni de 2%. (supposez que le taux d'intérêt est l'un des champs du compte)

Maintenant, vous devez mettre cette logique quelque part et vous n'avez pas d'objet de domaine pour le mettre.

DDD suppose que vous aurez toujours ce genre de règles, et c'est probablement le cas. La création d'un compte doit comporter plusieurs vérifications, un journal d'audit, etc., cela ne va pas simplement être «écrire une ligne dans la base de données»

Planifiez votre domaine en supposant qu'il n'y a pas de persistance ou de contrôleurs MVC avec une logique supplémentaire. Assurez-vous de capturer toutes les exigences et elles sont toutes dans le modèle de domaine.

Ewan
la source
3
C'est une bonne façon de le dire. Je déteste trouver des règles métier mélangées avec des détails de base de données. +1
candied_orange
Bon point, mais que faire si ces règles de validation ne s'appliquent que lors de la création et de la mise à jour des entrées de l'utilisateur? Ensuite, une fois que nous avons les entrées utilisateur, le modèle qui est créé lors de l'exécution des calculs est un modèle complètement différent. Devrions-nous avoir deux modèles de domaine pour un compte d'investissement? Un pour les opérations CRUD des entrées brutes pour l'utilisateur et un autre lorsque ces entrées sont utilisées pour créer le modèle de domaine utilisé dans les calculs?
wired_in
obtenir mes questions confuses. vous devrez donner un exemple complet. Si vous avez une logique de domaine, elle doit aller dans un objet de domaine. Cela ne signifie pas que vous ne pouvez pas créer un autre objet de domaine plus tard à partir du premier
Ewan
Imaginez un moteur de calcul complexe. L'un des intrants dont il a besoin pour effectuer les calculs est un compte d'investissement, mais tout le compte d'investissement est pour le moteur de calcul est un flux de revenus sur une certaine période de temps. Ce modèle de domaine d'un compte d'investissement est complètement différent des entrées brutes que l'utilisateur a entrées pour ce compte d'investissement. Cependant, lorsque l'utilisateur saisit des entrées de base comme le nom, la valeur actuelle, etc., il doit toujours y avoir une logique de validation, mais cela ne devrait avoir rien à voir avec le modèle utilisé par le moteur de calcul. Existe-t-il donc ici deux modèles de domaine pour un compte d'investissement?
wired_in
..... ou peut-être avoir un modèle de compte d'investissement dans le domaine est exagéré pour les opérations CRUD et il ne devrait y avoir que des attributs de validateur utilisés ou quelque chose
wired_in
7

Comment cela peut-il avoir un sens?

Réponse courte: ce n'est pas le cas .

Réponse plus longue: les modèles lourds de développement d'un modèle de domaine ne s'appliquent pas aux parties de votre solution qui ne sont qu'une base de données.

Udi Dahan avait une observation intéressante qui pourrait aider à clarifier cette

Dahan considère qu'un service doit avoir à la fois une sorte de fonctionnalité et des données. S'il n'a pas de données, alors c'est juste une fonction. Si tout ce qu'il fait, c'est effectuer des opérations CRUD sur des données, alors c'est une base de données.

Le but du modèle de domaine, après tout, est de garantir que toutes les mises à jour des données maintiennent l'invariant commercial actuel. Ou, pour le dire autrement, le modèle de domaine est chargé de s'assurer que la base de données qui agit comme système d'enregistrement est correcte.

Lorsque vous traitez avec un système CRUD, vous n'êtes généralement pas le système d'enregistrement des données. Le monde réel est le livre des records, et votre base de données n'est qu'une représentation localement mise en cache du monde réel.

Par exemple, la plupart des informations qui apparaissent dans un profil d'utilisateur, comme une adresse e-mail ou un numéro d'identification émis par le gouvernement, ont une source de vérité qui vit en dehors de votre entreprise - c'est l' administrateur de messagerie de quelqu'un d'autre qui attribue et révoque les adresses e-mail, pas votre application. C'est le gouvernement qui attribue les SSN, pas votre application.

Donc, vous n'allez normalement pas faire de validation de domaine sur les données qui vous viennent du monde extérieur; vous pourriez avoir des contrôles en place pour vous assurer que les données sont bien formées et correctement nettoyées ; mais ce ne sont pas vos données - votre modèle de domaine ne reçoit pas de veto.

Dans une approche DDD utilisant des couches, il semble que les opérations CRUD passent par la couche domaine. mais au moins dans notre cas, cela ne semble pas avoir de sens.

C'est le cas dans le cas où la base de données est le livre d'archives .

Ouarzy l'a exprimé ainsi .

Cependant, en travaillant sur beaucoup de code hérité, j'observe des erreurs courantes pour identifier ce qui est à l'intérieur du domaine et ce qui est à l'extérieur.

Une application ne peut être considérée comme CRUD que s'il n'y a pas de logique métier autour du modèle de données. Même dans ce (rare) cas, votre modèle de données n'est pas votre modèle de domaine. Cela signifie simplement que, comme aucune logique métier n'est impliquée, nous n'avons besoin d'aucune abstraction pour la gérer, et donc nous n'avons pas de modèle de domaine.

Nous utilisons le modèle de domaine pour gérer les données qui appartiennent à l'intérieur du domaine; les données provenant de l'extérieur du domaine sont déjà gérées ailleurs - nous ne faisons que mettre en cache une copie.

Greg Young utilise les systèmes d'entrepôt comme illustration principale des solutions où le livre d'archives est ailleurs (c.-à-d. L'étage de l'entrepôt). L'implémentation qu'il décrit ressemble beaucoup à la vôtre - une base de données logique pour capturer les messages reçus de l'entrepôt, puis une base de données logique distincte mettant en cache les conclusions tirées de l'analyse de ces messages.

Alors peut-être que nous avons deux contextes bornés ici? Chacun avec un modèle différent pour uninvestment account

Peut être. Je serais réticent à le marquer comme un contexte délimité, car il n'est pas clair quels autres bagages sont accompagnés. Il se peut que vous ayez deux contextes, ce pourrait être un contexte avec des différences subtiles dans le langage omniprésent que vous n'avez pas encore compris.

Test décisif possible: combien d'experts de domaine avez-vous besoin de deux experts de domaine pour couvrir ce spectre, ou d'un seul qui parle des composants de différentes manières. Fondamentalement, vous pourriez être en mesure de deviner combien de contextes délimités vous avez en travaillant la loi de Conway à l'envers.

Si vous considérez que les contextes délimités sont alignés sur les services, cela peut être plus simple: devriez-vous être en mesure de déployer ces deux fonctionnalités indépendamment? Oui suggère deux contextes délimités; mais s'ils doivent être synchronisés, alors peut-être que ce n'est qu'un.

VoiceOfUnreason
la source
Il existe bien une logique de validation et de défaut, mais elle ne s'applique que lors de la création / mise à jour des entrées brutes pour un compte d'investissement. Ensuite, nous utilisons un modèle beaucoup plus riche pour le compte d'investissement lorsque nous l'utilisons comme entrée dans un moteur de calcul. Alors peut-être que nous avons deux contextes bornés ici? Chacun avec un modèle différent pour un compte d'investissement '
wired_in
Je viens d'y revenir après plusieurs années, et votre commentaire résonne maintenant plus qu'avant pour une raison quelconque. Il y a beaucoup de bonnes choses ici, mais pourriez-vous clarifier une chose pour moi? Vous avez dit "Le but du modèle de domaine, après tout, est de garantir que toutes les mises à jour des données maintiennent l'invariant commercial actuel." Cela s'appliquerait à la partie de notre application qui enregistre / met à jour les informations. L'autre partie n'est qu'un moteur de calcul. Il prend une représentation des données en entrée et crache des résultats. Cela ne fait-il pas partie du modèle de domaine alors? Puisqu'il n'affecte pas les données stockées?
wired_in
2

Dans votre domaine, vous ne devriez pas avoir à savoir que la base de données existe même.

Votre domaine concerne les règles métier. Les choses qui doivent survivre lorsque l'entreprise qui a créé votre base de données ferme ses portes. Autrement dit, si vous voulez que votre entreprise survive. C'est vraiment bien quand ces règles ne se soucient pas que vous ayez changé la façon dont vous conservez les données.

Les détails de la base de données existent et doivent être traités. Ils devraient vivre ailleurs. Mettez-les à travers une frontière. Contrôlez soigneusement la façon dont vous communiquez à travers cette frontière ou ce n'est pas une frontière.

Oncle Bob a ceci à dire sur quoi mettre vos données:

En règle générale, les données qui franchissent les frontières sont de simples structures de données. Vous pouvez utiliser des structures de base ou de simples objets de transfert de données si vous le souhaitez. Ou les données peuvent simplement être des arguments dans les appels de fonction. Vous pouvez également l'intégrer dans une table de hachage ou la construire dans un objet.

L'important est que des structures de données isolées et simples traversent les frontières. Nous ne voulons pas tricher et transmettre des lignes d'entités ou de bases de données. Nous ne voulons pas que les structures de données aient une sorte de dépendance qui viole la règle de dépendance.

[…] Lorsque nous transmettons des données à travers une frontière, c'est toujours sous la forme qui convient le mieux au cercle intérieur.

Architecture propre

Il explique également comment vos couches externes devraient être des plugins pour vos couches internes afin que les couches internes ne sachent même pas qu'elles existent.

Aide-mémoire sur l'architecture propre

Suivez quelque chose comme ça et vous aurez un bel endroit pour ignorer la base de données où vous pouvez vous soucier des règles de validation des entrées, des règles qui doivent être persistées d'une manière ou d'une autre, des règles pour exécuter des calculs, des règles pour envoyer ces résultats à n'importe quelle sortie. Il est en fait plus facile de lire ce type de code.

C'est soit ça, soit vous décidez que votre domaine est vraiment juste pour manipuler la base de données. Dans ce cas, la langue de votre domaine est SQL. Si c'est bien, mais ne vous attendez pas à ce que votre mise en œuvre des règles métier survive à un changement de persistance. Vous finirez par avoir besoin de les réécrire complètement.

candied_orange
la source
Nous utilisons un ORM (Entity Framework), donc notre base de données est déjà abstraite, mais les modèles de données (classes Entity Framework) sont naturellement à peu près 1 pour 1 avec les tables de base de données. Le problème est que dans certaines parties de notre application, l'utilisateur met essentiellement à jour le modèle de données (l'écran n'est qu'une liste de zones de texte où chaque zone de texte est un champ de la base de données (modèle de données).
wired_in
Je ne vois donc pas de raison de ne pas simplement utiliser des représentations des données brutes (modèle de données) lors des opérations CRUD. Nous avons une représentation de domaine complexe utilisée pour les calculs, ce que je considère comme notre modèle de domaine, mais je ne vois pas pourquoi je chargerais cette image dans la partie CRUD de notre application.
wired_in
Définissez ce que vous entendez par «utiliser des représentations des données brutes». Les données sont entrées, les données sont validées selon les règles du domaine, les données sont conservées d'une manière ou d'une autre, les données sont calculées par rapport, les résultats sont envoyés à quoi que ce soit. Suis-je en train de manquer quelque chose?
candied_orange
J'essaie de dire que les données brutes que nous obtenons de l'utilisateur pour un compte d'investissement ne sont pas la façon dont nous représentons ce compte d'investissement dans les principales parties de notre application, comme lorsqu'il est utilisé pour les calculs. Par exemple, nous pouvons avoir une entrée booléenne que nous enregistrons dans la base de données appelée IsManagedAccount. L'utilisateur nous fournit cela via un bouton radio sur l'écran d'édition. La représentation de la base de données à l'écran est donc de 1 à 1. Lorsque nous construisons notre modèle de domaine plus tard dans l'application, nous pouvons avoir une classe ManagedAccount, donc pas de propriété booléenne. Les deux structures sont très différentes.
wired_in
Donc, lorsque l'utilisateur modifie simplement les entrées brutes sur un écran d'édition, pourquoi devrais-je charger l'image de domaine, puis ajouter beaucoup de complexité pour mapper en quelque sorte la classe ManagedAccount fortement typée vers une représentation plate qui n'est qu'une seule classe avec un IsManagedAccount propriété?
wired_in
1

Appliquer la théorie DDD:

Il existe deux contextes délimités dans ce domaine:

  • Les calculs du compte d'investissement. Le modèle mathématique du compte d'investissement est un élément, peut-être un agrégat.
  • Finance de base. Le compte d'investissement client est l'une des Entités.

Chaque contexte délimité peut avoir une conception architecturale différente.

Exemple:

Le compte d'investissement client est une entité (peut-être un agrégat, dépend du domaine) et la persistance des données est effectuée via le référentiel de l'entité (RDB ou autre type de base de données comme une base de données OO).

Il n'y a pas d'approche DDD pour les opérations CRUD. Pour avoir un champ DB lié aux données d'un objet, les principes de conception sont rompus.

Derek
la source