Pourquoi devrais-je isoler mes entités de domaine de ma couche de présentation?

Une partie de la conception pilotée par le domaine et sur laquelle il ne semble pas y avoir beaucoup de détails est de savoir comment et pourquoi vous devez isoler votre modèle de domaine de votre interface. J’essaie de convaincre mes collègues que c’est une bonne pratique, mais je ne semble pas faire beaucoup de progrès …

Ils utilisent les entités de domaine où qu’ils veulent dans les couches de présentation et d’interface. Lorsque je leur dis qu’ils doivent utiliser des modèles d’affichage ou des DTO pour isoler la couche Domain de la couche d’interface, ils s’opposent à ce qu’ils ne voient pas la valeur métier de faire quelque chose comme ça, car vous avez maintenant un object d’interface à gérer ainsi que l’object de domaine d’origine.

Donc, je cherche des raisons concrètes pour lesquelles je peux appuyer cela. Plus précisément:

  1. Pourquoi ne devrions-nous pas utiliser des objects de domaine dans notre couche de présentation?
    (si la réponse est évidente, «découplage», veuillez expliquer pourquoi cela est important dans ce contexte)
  2. Devrions-nous utiliser des objects ou des constructions supplémentaires pour isoler nos objects de domaine de l’interface?

Tout simplement, la raison en est une de mise en œuvre et de dérive. Oui, votre couche de présentation doit connaître vos objects métier pour pouvoir les représenter correctement. Oui, au départ, il semble y avoir beaucoup de chevauchement entre l’implémentation des deux types d’objects. Le problème est que, avec le temps, les choses s’ajoutent des deux côtés. Les modifications apscopes à la présentation et les besoins de la couche de présentation évoluent pour inclure des éléments totalement indépendants de votre couche métier (la couleur, par exemple). Pendant ce temps, vos objects de domaine changent au fil du temps et si vous ne disposez pas d’un découplage approprié de votre interface, vous risquez de visser votre couche d’interface en apportant des modifications apparemment bénignes à vos objects métier.

Personnellement, je pense que la meilleure façon d’aborder les choses est d’utiliser le paradigme d’interface ssortingctement appliqué; c’est-à-dire que votre couche d’object métier expose une interface avec laquelle elle peut uniquement communiquer; aucun détail d’implémentation (c.-à-d. des objects de domaine) concernant l’interface n’est exposé. Oui, cela signifie que vous devez implémenter vos objects de domaine à deux endroits différents. votre couche d’interface et dans votre couche BO. Mais cette réimplémentation, même si cela peut sembler initialement un travail supplémentaire, aide à imposer le découplage qui sauvera des tonnes de travail à un moment donné dans le futur.

J’ai lutté avec cela moi-même. Il y a des cas où un DTO est logique à utiliser dans le présentaton. Disons que je veux afficher une liste déroulante des sociétés dans mon système et que leur identifiant est lié à leur valeur.

Eh bien, au lieu de charger un object CompanyObject qui pourrait avoir des références à des abonnements ou qui sait quoi d’autre, je pourrais renvoyer un DTO avec le nom et l’identifiant. Ceci est un bon usage à mon humble avis.

Prenons maintenant un autre exemple. J’ai un object qui représente une estimation, cette estimation peut être constituée de main-d’œuvre, d’équipement, etc. Il peut avoir beaucoup de calculs définis par l’utilisateur qui prennent tous ces éléments et les résument (chaque estimation peut être différente avec différents types) des calculs). Pourquoi devrais-je modéliser cet object deux fois? Pourquoi ne puis-je pas simplement énumérer les calculs sur mon interface utilisateur et les afficher?

Je n’utilise généralement pas de DTO pour isoler ma couche de domaine de mon interface utilisateur. Je les utilise pour isoler ma couche de domaine d’une limite qui ne dépend pas de moi. L’idée que quelqu’un puisse mettre des informations de navigation dans son object métier est ridicule, ne pas contaminer votre object métier.

L’idée que quelqu’un mettrait la validation dans son object métier? Eh bien, je dis que c’est une bonne chose. Votre interface utilisateur ne devrait pas être seule responsable de la validation de vos objects métier. Votre couche métier DOIT faire sa propre validation.

Pourquoi voudriez-vous mettre du code de génération d’interface utilisateur dans un object busienss? Dans mon cas, j’ai des objects séparés qui génèrent le code d’interface utilisateur seperatley à partir de l’interface utilisateur. J’ai des objects sperate qui rendent mes objects métier dans Xml, l’idée que vous devez séparer vos calques pour éviter ce type de contamination me semble tellement étrange car pourquoi voudriez-vous même mettre du code de génération HTML dans un object métier …

Modifier Comme je le pense un peu plus, il existe des cas où les informations de l’interface utilisateur peuvent appartenir à la couche de domaine. Et cela pourrait obscurcir ce que vous appelez une couche de domaine, mais je travaillais sur une application multi-locataire, qui avait un comportement très différent à la fois en termes d’apparence et de stream de travail fonctionnel. En fonction de divers facteurs. Dans ce cas, nous avions un modèle de domaine qui représentait les locataires et leur configuration. Leur configuration comprenait des informations sur l’interface utilisateur (Label pour les champs génériques par exemple).

Si je devais concevoir mes objects pour les rendre persistants, devrais-je aussi dupliquer les objects? Gardez à l’esprit que si vous souhaitez append un nouveau champ, vous avez maintenant deux endroits pour l’append. Peut-être que cela soulève une autre question si vous utilisez DDD, est-ce que tous les objects de domaine des entités persistantes? Je sais dans mon exemple qu’ils l’étaient.

Vous le faites pour la même raison que vous gardez SQL de vos pages ASP / JSP.

Si vous ne conservez qu’un object de domaine, à utiliser dans la couche de présentation ET du domaine, cet object devient rapidement monolithique. Il commence à inclure le code de validation de l’interface utilisateur, le code de navigation de l’interface utilisateur et le code de génération de l’interface utilisateur. Ensuite, vous ajoutez rapidement toutes les méthodes de la couche de gestion. Désormais, votre couche métier et votre interface utilisateur sont toutes confondues et toutes sont gâchées par la couche d’entité du domaine.

Vous voulez réutiliser ce widget d’interface graphique utile dans une autre application? Eh bien, vous devez créer une firebase database avec ce nom, ces deux schémas et ces 18 tables. Vous devez également configurer Hibernate et Spring (ou vos frameworks de choix) pour effectuer la validation métier. Oh, vous devez également inclure ces 85 autres classes non liées car elles sont référencées dans la couche de gestion, qui se trouve juste dans le même fichier.

Je ne suis pas d’accord.

Je pense que la meilleure façon de procéder est de commencer avec des objects de domaine dans votre couche de présentation JUSQU’À CE QU’IL NE SOIT PAS SENSIBLE DE FAIRE AUTREMENT.

Contrairement aux idées reçues, les “objects de domaine” et les “objects de valeur” peuvent coexister dans la couche de présentation. Et c’est la meilleure façon de le faire – vous bénéficiez des deux mondes, la duplication réduite (et le code standard) avec les objects du domaine; et la personnalisation et la simplification conceptuelle de l’utilisation d’objects de valeur entre les requêtes.

Nous utilisons le même modèle sur le serveur et sur l’interface utilisateur. Et c’est une douleur. Nous devons le restructurer un jour.

Les problèmes sont principalement dus au fait que le modèle de domaine doit être découpé en morceaux plus petits pour pouvoir le sérialiser sans avoir la totalité de la firebase database référencée. Cela rend plus difficile à utiliser sur le serveur. Des liens importants manquent. Certains types ne sont pas sérialisables et ne peuvent pas être envoyés au client. Par exemple ‘Type’ ou toute classe générique. Ils doivent être non génériques et le type doit être transféré sous forme de chaîne. Cela génère des propriétés supplémentaires pour la sérialisation, elles sont redondantes et déroutantes.

Un autre problème est que les entités sur l’interface utilisateur ne correspondent pas vraiment. Nous utilisons la liaison de données et de nombreuses entités ont beaucoup de propriétés redondantes uniquement à des fins d’interface utilisateur. De plus, il y a beaucoup de «BrowsableAtsortingbute» et d’autres dans le modèle d’entité. C’est vraiment mauvais.

À la fin, je pense que la question est de savoir quelle voie est la plus facile. Il peut y avoir des projets où cela fonctionne très bien et où il n’est pas nécessaire d’écrire un autre modèle DTO.

Il s’agit de dépendances pour la plupart. La structure fonctionnelle de base de l’organisation a ses propres exigences fonctionnelles, et l’interface utilisateur devrait permettre aux personnes de modifier et de visualiser le kernel; mais le kernel lui-même ne devrait pas être requirejs pour accueillir l’interface utilisateur. (Si cela doit se produire, c’est généralement une indication que le kernel n’est pas une propriété conçue.)

Mon système comptable a une structure et un contenu (et des données) censés modéliser le fonctionnement de mon entreprise. Cette structure est réelle et existe quel que soit le logiciel de comptabilité que j’utilise. (Inévitablement, un progiciel donné contient une structure et un contenu pour lui-même, mais une partie du défi consiste à minimiser ces frais généraux.)

Fondamentalement, une personne a un travail à faire. Le DDD doit correspondre au stream et au contenu du travail. DDD consiste à rendre explicites tous les travaux qui doivent être faits complètement et de manière indépendante. Il est à espérer que l’UI facilite le travail aussi transparent que possible, aussi productif que possible.

Les interfaces concernent les entrées et les vues fournies pour le kernel fonctionnel correctement modélisé et invariant.

Bon sang, je jure cela dit persistance.

Quoi qu’il en soit, c’est un autre exemple de la même chose: la loi de Parnas dit qu’un module doit garder un secret et que le secret est une exigence qui peut changer. (Bob Martin a une règle qui en est une autre version.) Dans un système comme celui-ci, la présentation peut changer indépendamment du domaine . Comme, par exemple, une entreprise qui maintient les prix en euros et utilise le français dans les bureaux de l’entreprise, mais souhaite présenter les prix en dollars avec du texte en mandarin. Le domaine est le même la présentation peut changer. Donc, pour minimiser la fragilité du système – c’est-à-dire le nombre de choses à modifier pour mettre en œuvre un changement dans les exigences – vous séparez les préoccupations.

La réponse dépend de l’échelle de votre application.


Application CRUD simple (création, lecture, mise à jour, suppression)

Pour les applications de base crud, vous n’avez aucune fonctionnalité. Ajouter DTO au-dessus des entités serait une perte de temps. Cela augmenterait la complexité sans augmenter l’évolutivité.

entrer la description de l'image ici


Application modérément compliquée sans CRUD

Dans cette taille d’application, vous aurez peu d’entités qui ont un véritable cycle de vie et une logique métier associée.

Ajouter des DTO sur ce cas est une bonne idée pour deux raisons:

  • La couche de présentation ne peut voir qu’un sous-ensemble de champs dont l’entité dispose. Vous encapsulez des entités
  • Pas de couplage entre backend et frontent
  • Si vous avez des méthodes de gestion dans les entités, mais pas dans DTO, l’ajout de DTO signifie que le code externe ne peut pas détruire l’état de votre entité.

entrer la description de l'image ici


Application d’entreprise compliquée

Une entité unique peut nécessiter plusieurs modes de présentation. Chacun d’eux aura besoin de différents champs. Dans ce cas, vous rencontrez les mêmes problèmes que dans l’exemple précédent et vous devez également contrôler le nombre de champs visibles pour chaque client. Avoir un DTO séparé pour chaque client vous aidera à choisir ce qui devrait être visible.

entrer la description de l'image ici

Votre présentation peut faire référence à votre couche de domaine, mais votre interface utilisateur ne doit pas être directement liée à vos objects de domaine. Les objects de domaine ne sont pas destinés à une utilisation d’interface utilisateur car ils sont souvent, s’ils sont correctement conçus, basés sur des comportements et non sur des représentations de données. Il devrait y avoir une couche de mappage entre l’interface utilisateur et le domaine. MVVM, ou MVP, est un bon modèle pour cela. Si vous essayez de lier directement votre interface utilisateur au domaine, vous risquez de vous créer beaucoup de maux de tête. Ils ont deux objectives différents.

Peut-être que vous ne conceptualisez pas la couche d’interface utilisateur en termes suffisamment larges. Pensez en termes de multiples formes de réponses (pages Web, réponse vocale, lettres imprimées, etc.) et en termes de plusieurs langues (anglais, français, etc.).

Supposons maintenant que le moteur vocal du système d’appel téléphonique fonctionne sur un type d’ordinateur complètement différent (Mac par exemple) à partir de l’ordinateur qui exécute le site Web (Windows peut-être).

Bien sûr, il est facile de tomber dans le piège “Eh bien, dans mon entreprise, nous ne nous préoccupons que de l’anglais, nous exploitons notre site Web sur LAMP (Linux, Apache, MySQL et PHP) et chacun utilise la même version de Firefox”. Mais qu’en est-il dans 5 ou 10 ans?

Voir aussi la section “Propagation des données entre les couches” ci-après, qui, selon moi, présente des arguments convaincants:

http://galaxy.andromda.org/docs/andromda-documentation/andromda-getting-started-java/java/index.html

Avec l’aide d’un outil tel que « Value Injecter » et le concept de «Mappers» dans la couche de présentation, tout en travaillant avec des vues, il est beaucoup plus facile de comprendre chaque morceau de code. Si vous avez un peu de code, vous ne verrez pas les avantages immédiatement, mais lorsque votre projet augmentera de plus en plus, vous serez très heureux de travailler avec les vues pour ne pas avoir à entrer dans la logique des services. référentiels pour comprendre le modèle de vue. View Model est une autre garde dans le vaste monde de la couche anti-corruption et vaut son pesant d’or dans un projet à long terme.

La seule raison pour laquelle je ne vois aucun avantage à utiliser le modèle de vue est que votre projet soit suffisamment petit et simple pour que les vues soient directement liées à chaque propriété de votre modèle. Mais si à l’avenir, l’exigence change et que certains contrôles dans les vues ne sont pas liés au modèle et que vous n’avez pas de concept de modèle de vue, vous allez commencer à append des correctifs dans de nombreux endroits et vous commencerez à avoir un code hérité vous n’apprécierez pas. Bien sûr, vous pouvez effectuer quelques refactorisations pour transformer votre modèle de vue dans view-viewmodel et suivre le principe de YAGNI sans append de code si vous n’en avez pas besoin mais pour moi-même. couche de présentation exposant uniquement les objects de modèle de vue.

Voici un exemple concret expliquant pourquoi je trouve judicieux de séparer les entités du domaine de la vue.

Il y a quelques mois, j’ai créé une interface utilisateur simple pour montrer les valeurs de l’azote, du phosphore et du potassium dans un échantillon de sol à travers une série de 3 jauges. Chaque jauge avait une section rouge, verte et rouge, c.-à-d. Que vous pouviez avoir trop peu ou trop de chaque composant, mais il y avait un niveau vert sûr au milieu.

Sans trop réfléchir, j’ai modélisé ma logique métier pour fournir des données pour ces 3 composants chimiques et une fiche de données séparée, contenant des données sur les niveaux acceptés dans chacun des 3 cas (y compris l’unité de mesure utilisée, à savoir moles ou pourcentage). J’ai ensuite modélisé mon interface utilisateur pour utiliser un modèle très différent. Ce modèle concernait les étiquettes de jauge, les valeurs, les valeurs limites et les couleurs.

Cela signifiait que lorsque je devais montrer plus tard 12 composants, je mappais simplement les données supplémentaires dans 12 nouveaux modèles de vue de jauge et ils apparaissaient à l’écran. Cela signifiait également que je pouvais réutiliser facilement le contrôle de la jauge et leur faire afficher d’autres ensembles de données.

Si j’avais couplé ces jauges directement dans mes entités de domaine, je n’aurais aucune de ces possibilités et toute modification future serait un casse-tête. J’ai rencontré des problèmes très similaires lors de la modélisation de calendriers dans l’interface utilisateur. Si un rendez-vous de calendrier doit passer au rouge lorsqu’il y a plus de 10 participants, la logique métier à prendre en compte doit restr dans la couche de gestion et tout le calendrier dans l’interface utilisateur doit savoir: devient rouge, il ne devrait pas avoir besoin de savoir pourquoi.

La seule raison valable d’append un mappage supplémentaire entre la sémantique généralisée et la sémantique spécifique à un domaine est que vous avez (access à) un corps de code existant (et des outils) basé sur une sémantique généralisée (mais mappable) distincte de la sémantique de votre domaine.

Les conceptions pilotées par domaine fonctionnent mieux lorsqu’elles sont utilisées conjointement avec un ensemble orthogonal de structures de domaine fonctionnelles (telles que ORM, GUI, Workflow, etc.). Rappelez-vous toujours que seules les contiguïtés de la couche externe doivent être exposées. Il s’agit généralement du back-end (GUI) et du back-end persistant (RDBM, ORM). Toutes les couches intermédiaires effectivement conçues peuvent et doivent être invariantes par domaine.