L'architecture propre d'oncle Bob - Une classe d'entités / de modèles pour chaque couche?

44

CONTEXTE :

J'essaie d'utiliser l'architecture épurée d'Oncle Bob dans mon application Android. J'ai étudié de nombreux projets open source qui essayent de montrer la bonne façon de le faire, et j'ai trouvé une implémentation intéressante basée sur RxAndroid.

CE QUE JE NOUS AVIS:

Dans chaque couche (présentation, domaine et données), il existe une classe de modèle pour la même entité (UML parlant). De plus, il existe des classes de mappeur qui prennent en charge la transformation de l'objet lorsque les données franchissent les limites (d'une couche à l'autre).

QUESTION:

Est-il nécessaire d'avoir des classes de modèle dans chaque couche lorsque je sais qu'elles se retrouveront toutes avec les mêmes attributs si toutes les opérations CRUD sont nécessaires? Ou est-ce une règle ou une meilleure pratique lors de l'utilisation de l'architecture propre?

Rami Jemli
la source

Réponses:

52

À mon avis, ce n'est absolument pas ce que cela veut dire. Et c'est une violation de DRY.

L'idée est que l'objet entité / domaine au milieu est modélisé pour représenter le domaine aussi bien et aussi facilement que possible. Il est au centre de tout et tout peut en dépendre puisque le domaine lui-même ne change pas la plupart du temps.

Si votre base de données à l'extérieur peut stocker ces objets directement, il est non seulement inutile de les mapper vers un autre format afin de séparer les calques, mais de créer des doublons du modèle et ce n'est pas l'intention.

Pour commencer, l'architecture propre a été conçue en tenant compte d'un environnement / scénario typique différent. Applications de serveur d'entreprise avec des couches externes gigantesques ayant besoin de leurs propres types d'objets spéciaux. Par exemple, les bases de données qui produisent des SQLRowobjets et doivent SQLTransactionsen retour mettre à jour des éléments. Si vous utilisiez celles du centre, vous violeriez le sens de la dépendance car votre noyau dépendrait de la base de données.

Avec des ORM légers qui chargent et stockent des objets d'entité, ce n'est pas le cas. Ils font le mappage entre leur domaine interne SQLRowet votre domaine. Même si vous devez mettre une @Entitiyannotation de l'ORM dans votre objet de domaine, je dirais que cela n'établit pas une "mention" de la couche externe. Les annotations n'étant que des métadonnées, aucun code qui ne les recherche pas ne les verra. Et plus important encore, rien ne doit changer si vous les supprimez ou les remplacez par une annotation de base de données différente.

En revanche, si vous changez de domaine et que vous créez tous ces mappeurs, vous devez beaucoup changer.


Amendement: ci-dessus est un peu trop simpliste et pourrait même être faux. Parce qu’une partie de l’architecture propre vous demande de créer une représentation par couche. Mais cela doit être vu dans le contexte de l'application.

Voici ce qui suit: https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

L'important est que des structures de données isolées et simples traversent les limites. Nous ne voulons pas tricher et passer des lignes Entities ou Database. Nous ne voulons pas que les structures de données aient un type de dépendance qui enfreint la règle de dépendance.

Passer des entités du centre vers les couches extérieures ne viole pas la règle de dépendance, pourtant elles sont mentionnées. Mais cela a une raison dans le contexte de l'application envisagée. Passer des entités déplacerait la logique d'application vers l'extérieur. Les couches externes doivent savoir comment interpréter les objets internes, elles doivent effectivement faire ce que les couches internes, comme le "cas d'utilisation", est censé faire.

En outre, il dissocie également les couches de sorte que les modifications apportées au cœur ne nécessitent pas nécessairement des modifications des couches externes (voir le commentaire de SteveCallender). Dans ce contexte, il est facile de voir comment les objets devraient représenter spécifiquement le but pour lequel ils sont utilisés. De plus, ces couches doivent communiquer entre elles en termes d’objets spécialement conçus pour les besoins de cette communication. Cela peut même signifier qu'il y a 3 représentations, 1 dans chaque couche, 1 pour le transport entre les couches.

Et il y a https://blog.8thlight.com/uncle-bob/2011/11/22/Clean-Architecture.html qui aborde ci-dessus:

D'autres personnes ont craint que le résultat final de mes conseils ne soit une grande quantité de code dupliqué et beaucoup de copies par cœur de données d'une structure de données à une autre à travers les couches du système. Certainement, je ne le veux pas non plus; et rien de ce que j'ai suggéré ne mènerait inévitablement à la répétition de structures de données et à une copie anormale de la copie sur le terrain.

Cette OMI implique que la copie simple d'objets 1: 1 est une odeur dans l'architecture, car vous n'utilisez pas les calques et / ou les abstractions appropriés.

Il explique ensuite comment il imagine toute la "copie"

Vous séparez l'interface utilisateur des règles de gestion en passant des structures de données simples entre les deux. Vous ne laissez pas vos contrôleurs connaître les règles commerciales. Au lieu de cela, les contrôleurs décompactent l'objet HttpRequest dans une structure de données simple vanilla, puis transmettent cette structure de données à un objet interactor qui implémente le cas d'utilisation en appelant des objets métier. L'interacteur rassemble ensuite les données de réponse dans une autre structure de données vanilla et les renvoie à l'interface utilisateur. Les vues ne connaissent pas les objets métier. Ils ne font que regarder dans cette structure de données et présenter la réponse.

Dans cette application, il y a une grande différence entre les représentations. Les données qui circulent ne sont pas simplement les entités. Et cela justifie et exige différentes classes.

Cependant, appliqué à une application Android simple, telle qu'une visionneuse de photos, où l' Photoentité a environ 0 règles commerciales et où le "cas d'utilisation" qui les traite est quasiment inexistant et se préoccupe davantage de la mise en cache et du téléchargement (ce processus plus explicitement), le point de faire des représentations séparées d’une photo commence à disparaître. J'ai même l'impression que la photo elle-même est un objet de transfert de données alors que la véritable couche métier-logique-cœur-couche manque.

Il existe une différence entre "séparer l'interface utilisateur des règles de gestion en passant des structures de données simples entre les deux" et "lorsque vous souhaitez afficher une photo, renommez-la 3 fois en cours de route" .

En outre, je vois que les applications de démonstration ne parviennent pas à représenter l’architecture propre, c’est qu’elles ajoutent un accent important sur la séparation des couches dans l’intérêt de les séparer, tout en masquant efficacement le travail de l’application. Cela contraste avec ce qui est dit dans https://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html - à savoir que

l'architecture d'une application logicielle crie sur les cas d'utilisation de l'application

Je ne vois pas cet accent mis sur la séparation des couches dans une architecture épurée. Il s’agit de l’orientation des dépendances et de la représentation du noyau de l’application - entités et cas d’utilisation - dans l’idéal java sans dépendances vers l’extérieur. Ce n'est pas tellement une question de dépendance envers ce noyau.

Par conséquent, si votre application comporte un noyau qui représente les règles métier et les cas d'utilisation, et / ou que différentes personnes travaillent sur des couches différentes, veuillez les séparer de la manière souhaitée. Si vous écrivez simplement une application simple par vous-même, n'en faites pas trop. 2 couches avec des limites fluides peuvent être plus que suffisantes. Et des couches peuvent également être ajoutées plus tard.

zapl
la source
1
@RamiJemli Idéalement, les entités sont les mêmes dans toutes les applications. C'est la différence entre les "règles métier à l'échelle de l'entreprise" et les "règles métier des applications" (parfois logique métier vs application). Le noyau est une représentation très abstraite de vos entités, suffisamment générique pour pouvoir être utilisée partout. Imaginez une banque dotée de nombreuses applications, une pour le support client, une autre fonctionnant sur des distributeurs automatiques de billets, une autre en tant qu'interface Web pour les clients eux-mêmes. Tous ceux qui pourraient utiliser la même chose, BankAccountmais avec des règles spécifiques à l’application, vous permettent de gérer ce compte.
4
Je pense qu'un point important dans l'architecture épurée est qu'en utilisant la couche d'adaptateur d'interface pour convertir (ou, comme vous le dites, mapper) entre les différentes couches de la représentation de l'entité, vous réduisez la dépendance à cette entité. En cas de modification des couches Usecase ou Entity (probablement non probable mais à mesure que les exigences changent, ces couches le seront), l'impact de la modification est alors contenu dans la couche adaptateur. Si vous choisissez d'utiliser la même représentation de l'entité tout au long de votre architecture, l'impact de ce changement serait bien plus important.
SteveCallender
1
@RamiJemli c'est bien d'utiliser des frameworks qui simplifient la vie, le fait est que vous devez faire attention lorsque votre architecture s'appuie sur eux et que vous commencez à les mettre au centre de tout. Voici même un article sur RxJava blog.8thlight.com/uncle-bob/2015/08/06/let-the-magic-die.html - cela ne dit pas que vous ne devriez pas l'utiliser. C'est plutôt comme: j'ai vu ça, ça va être différent dans un an et quand votre candidature est toujours là, vous êtes pris au dépourvu. Faites-en un détail et faites les choses les plus importantes en vieux java tout en appliquant les vieux principes SOLID.
mardi
1
@zapl Avez-vous le même sentiment à propos d'une couche de service Web? En d'autres termes, @SerializedNamemettriez-vous des annotations Gson sur un modèle de domaine? Ou créeriez-vous un nouvel objet chargé de mapper la réponse Web au modèle de domaine?
tir38
2
@ tir38 La séparation elle-même ne procure pas l'avantage, c'est le coût des modifications futures qui en découle. => Dépend de l'application. 1) Il vous faut du temps pour créer et maintenir l’étape ajoutée qui se transforme entre différentes représentations. Par exemple, ajouter un champ au domaine et oublier de l'ajouter ailleurs n'est pas rare. Cela ne peut pas arriver avec l'approche simple. 2) Il est coûteux de passer ultérieurement à une configuration plus complexe au cas où vous en auriez besoin. L'ajout de couches n'est pas facile, il est donc plus facile dans les grandes applications de justifier l'utilisation de couches
superflues
7

Vous avez vraiment tout compris. Et il n'y a pas de violation de DRY parce que vous acceptez SRP.

Par exemple: vous avez une méthode business createX (nom de chaîne), puis vous pouvez avoir une méthode createX (nom chaîne) dans la couche DAO, appelée dans la méthode métier. Ils peuvent avoir la même signature et peut-être qu'il n'y a qu'une délégation, mais ils ont des objectifs différents. Vous pouvez également avoir un createX (nom de chaîne) sur le UseCase. Même dans ce cas, ce n’est pas redondant. Ce que je veux dire par là est que: Les mêmes signatures ne signifient pas la même sémantique. Choisissez d'autres noms pour que la sémantique soit claire. Se nommer lui-même n'affecte pas du tout SRP.

Le UseCase est responsable de la logique spécifique à l'application, l'objet métier est responsable de la logique indépendante de l'application et le DAO est responsable du stockage.

En raison de la sémantique différente, toutes les couches peuvent avoir leur propre modèle de représentation et de communication. Souvent, vous voyez les «entités» comme des «objets métier» et souvent, vous ne voyez pas la nécessité de les séparer. Mais dans les projets "énormes", des efforts doivent être déployés pour séparer correctement les couches. Plus le projet est important, plus il est possible que vous ayez besoin des différentes sémantiques représentées dans différentes couches et classes.

Vous pouvez penser à différents aspects de la même sémantique. Un objet utilisateur doit être affiché à l'écran, il a des règles de cohérence internes et il doit être stocké quelque part. Chaque aspect doit être représenté dans une classe différente (SRP). La création des cartographes peut être pénible alors dans la plupart des projets sur lesquels j'ai travaillé, ces aspects sont fondus en une seule classe. C'est clairement une violation du PÉR, mais personne ne s'en soucie vraiment.

J'appelle l'application d'architecture propre et SOLID "non socialement acceptable". Je travaillerais avec si on me le permettait. Actuellement, je n'ai pas le droit de le faire. J'attends le moment où nous devons penser à prendre SOLID au sérieux.

utilisateur205407
la source
Je pense qu'aucune méthode de la couche de données ne devrait avoir la même signature que n'importe quelle méthode de la couche de domaine. Dans la couche Domaine, vous utilisez les conventions de dénomination liées aux entreprises, telles que SignUp ou Login, et dans la couche Données, vous utilisez les options save (si modèle DAO) ou add (si Repository car ce modèle utilise Collection comme métaphore). Enfin, je ne parle pas d'entités (Data) et de modèle (Domain), je souligne l'inutilité de UserModel et de son mappeur (couche de présentation). Vous pouvez appeler la classe User du domaine dans la présentation sans que cela ne contrevienne à la règle de dépendance.
Rami Jemli
Je suis d'accord avec Rami, le mappeur n'est pas nécessaire car vous pouvez effectuer le mappage directement dans l'implémentation de l'interacteur.
Christopher Perry
5

Non, vous n'avez pas besoin de créer des classes de modèle dans chaque couche.

Entity ( DATA_LAYER) - est une représentation complète ou partielle de l'objet Database.DATA_LAYER

Mapper ( DOMAIN_LAYER) - est en fait une classe qui convertit Entity en ModelClass, qui sera utilisée surDOMAIN_LAYER

Jetez un coup d'oeil: https://github.com/lifedemons/photoviewer

Deathember
la source
1
Bien sûr, je ne suis pas contre les entités de la couche de données, mais dans votre exemple, la classe PhotoModel de la couche de présentation possède les mêmes attributs que la classe Photo de la couche de domaine. C'est techniquement la même classe. est-ce nécessaire?
Je pense que quelque chose ne va pas dans votre exemple car la couche de domaine ne devrait pas dépendre d'autres couches, car dans votre exemple, vos mappeurs dépendent des entités de votre couche de données en tant
qu'OMI