Je me débats avec la séparation de la logique entre les entités et les interacteurs ou les cas d'utilisation. Si je concevons les entités avec les principes DDD, chaque entité aurait des méthodes correspondant à des cas d'utilisation, au lieu de setters et de getters. Dans ce cas, j'aurais une affaire, j'aurais grossièrement une cartographie individuelle de classes d'interacteur et de méthodes d'entité (peut-être avec certains interacteurs couvrant plusieurs entités et orchestrant des scénarios plus complexes).
Par exemple, je peux avoir la classe d'entité suivante:
Sale (entity)
+createSale()
+ammendSale()
+cancelSale()
+shipSale()
+collectSale()
Et les classes de commande suivantes:
CreateSaleCommand
AmmendSaleCommand
CancelSaleCommand
ShipSaleCommand # (this command may interact with the inventory service in a microservices context, or with the ProductStock entity in a monolithic context)
CollectSaleCommand # (this command may interact with payment and accounting services, or with the corresponding entities)
Que pensez-vous de cette approche? Je pense que cela peut conduire à une multiplication d'artefacts sans grande avantage, la plupart des commandes étant des classes anémiques qui viennent de passer des demandes aux entités et de répondre aux réponses. Bien qu'ils s'occupent de l'encapsulation de la logique pour accéder aux référentiels et aux services externes, tout en permettant aux entités de se concentrer exclusivement sur la logique de domaine (leurs méthodes représentant des actions et des événements commerciaux pertinents, ainsi que leurs données privées représentant des concepts et des catégories d'entreprises).
Vous n'avez pas besoin d'une cartographie unique d'interacteurs et d'entités. Je crois que ce design serait nocif.
DDD est tout à propos des limites de contexte et de la langue omniprésente dans ces limites. Lorsque vous vous concentrez réellement sur la création d'objets pour représenter la langue de l'entreprise, vous trouverez que tout commence à vous façonner un peu différemment.
Il me semble étrange que une vente créerait ou expédierait elle-même. Dans le monde réel, je pouvais imaginer une personne commerciale faisant une vente. Ensuite, un service d'expédition peut gérer la logistique à l'expédier. Peut-être qu'une vente serait créée via un panier d'achat en ligne et expédié par courrier électronique. Utilisation de cette langue à titre d'exemple, explorez une possible conception alternative.
SalesPerson
+createSale(Customer, Product, Price)
+amendSale(Sale, Amendment)
ShippingDepartment
+shipSale(Sale)
Attends une seconde!? N'envoyez-nous pas habituellement des commandes? Peut-être que ça devrait aller comme ça:
SalesPerson
+createOrder(Customer, Product, Price)
+amendOrder(Order, Amendment)
ShippingDepartment
+shipOrder(Order)
Order
+constructor(Customer, Product, Price)
+applyDiscount(Discount)
+updateShippingAddress(Address)
Les deux se rapprochent, mais vraiment notre équipe de vente ne les appelle pas les commandes et notre département d'expédition ne les appelle que des commandes. En outre, les commandes sont toujours créées à partir de l'existence d'une vente, afin que nous devions également le refléter.
SalesPerson
+createSale(Customer, Product, Price)
+amendSale(Sale, Amendment)
Sale
+constructor(Customer, Product, Price)
+applyDiscount(Discount)
+updateShippingAddress(Address)
ShippingDepartment
+shipOrder(Order)
Order
+constructor(Sale)
+placeOnHold()
+shipped(TrackingNumber)
Et ainsi, cela continue de se développer.
La façon dont je traite avec la séparation de la logique consiste à essayer de visualiser cet objet dans la vie réelle. Ensuite, pensez aux propriétés et aux actions que cela est pertinente pour le besoin/cas d'utilisation/la caractéristique/fonctionnalité.
EDIT: Comment interagissez-vous avec les méthodes d'une entité dans DDD?
Au lieu d'utiliser le modèle de commande ou d'autres interacteurs, vous les utilisez simplement directement:
salesPerson = new SalesPerson()
sale = salesPerson.createSale(...)
...
shippingDept.shipOrder(new Order(sale))
Gardez à l'esprit que les deux ne sont pas mutuellement exclusifs. Vous pouvez toujours exploiter un modèle de commande ou utiliser des cas/des interacteurs s'il y a un besoin pour cela. Vous pourriez avoir quelque chose comme ça (1 interacteur à de nombreuses entités et méthodes):
PartyUseCase(ShippingDepartment)
+prepareParty(SalesPerson, Customer)
plates = ProductRepo.findByName('plates')
forks = ProductRepo.findByName('forks')
platesSale = salesPerson.createSale(Customer, plates)
forksSale = salesPerson.createSale(Customer, forks)
ShippingDepartment.shipOrder(new Order(platesSale))
ShippingDepartment.shipOrder(new Order(forksSale))
...
Sinon, il peut y avoir un script simple qui est exécuté une fois par jour avec rien de plus que ceci (0 interacteurs):
orderRepo = new OrderRepository()
shippingDept = new ShippingDepartment()
for each order in orderRepo.getOrdersToShip()
shippingDept.shipOrder(order)
En fin de compte, si vous créez une cartographie unique de classes à des méthodes, vous créez une complexité inutile.
La première chose à laquelle j'ai pensé quand j'ai vu votre échantillon architecture est "peut-être une vente se créer elle-même? Une vente peut-elle s'annuler elle-même? Plus important encore, un navire de vente peut-il lui-même ou collecter son propre argent?"
Les mots "modèle de données anémique" ne sont considérés que des mots sales car quelqu'un a décidé que "la véritable orientation de l'objet épouse toujours des données et une logique ensemble". Mais les objets de transfert de données sont utilisés tout le temps pour réussir les données sur les limites et la définition même d'un objet anémique.
Fondamentalement, "architecture propre" n'est qu'une autre saveur de Architectures en couches. Le principe directeur dans toutes les architectures en couches est que Les dépendances ne doivent s'écouler que dans une direction. Par exemple, la couche logique commerciale (BLL) doit savoir sur la couche d'accès aux données (DAL), mais le DAL devrait savoir Rien À propos de la BLL. Cet arrangement vous permet, par exemple, de joindre différents BLL (par exemple une pour chaque département de l'entreprise) à la DAL sans avoir à changer le DAL-lui-même.
Des concepts de conception comme celui-ci sont difficiles à discuter de manière isolée, alors rendons cela plus concret.
Ceci est un diagramme architectural d'une demande d'échantillon de négociation démontrant les meilleures pratiques dans le développement logiciel appelé Archfirst . Notez qu'il indique deux contextes délimités: le système de gestion des commandes (OMS) et l'échange.
Maintenant, jetez un coup d'œil à ce diagramme:
Notez que Les méthodes sur une commande dépendent du contexte dans lequel elle est utilisée.
De plus, je ne suis pas nécessairement opposé à ces méthodes d'ailleurs autres que l'objet de commande, peut-être dans un objet OrderManager
.
Ne se sent pas juste pour moi.
Il y a deux principes au moins qui sont d'accord avec moi. Le premier est: Garder-it-simple. Pourquoi introduire une couche purement technique s'il n'y a pas d'avantage? Je pense que votre sentiment d'intestin est juste, c'est juste une duplication de choses que peut ou non Résoudre un problème futur. L'approche Simple It-It-It-It-Simple nous dit, si cela ne résout pas un problème maintenant, éliminez-le.
La seconde se concentre simplement sur le domaine commercial. Le problème que j'ai avec des "commandes" est qu'ils sont purement techniques. En d'autres termes, ils n'appartiennent pas à l'entreprise, à la langue omniprésente ou au domaine. C'est cruft.
En outre, si vous vous référez directement à l'architecture propre de l'oncle Bob, voici une Analyse de l'architecture propre d'un point de vue orienté objet , dans laquelle je décris en détail pourquoi ces idées sont fondamentalement incompatibles avec Orientation d'objet.
Interacteurs (je suppose que c'est ce que vous voulez dire par commandes) sont similaires aux services d'application dans l'architecture DDD/oignon. Ils fournissent un environnement d'hébergement pour le cas d'utilisation, ou une transaction commerciale, et ils orchestrent des appels vers le domaine et des composants externes.
Par exemple, vous pouvez avoir la séquence suivante
Start unit of work
Get entity from repository
Call entity method
Send email notification
Commit unit of work
Ceci n'est en aucun cas anémique ou aussi trivial que "méthode d'entité d'appel et renvoyer la réponse". Il peut être considéré comme une responsabilité de son propre - vous devez mettre cette logique quelque part et si vous le mettez dans un présentateur, cela pourrait devenir gonflé et incohérent.