3

I'm struggling with the separation of logic between entities and interactors or Use Cases. If I design the entities with DDD principles, each entity would have methods corresponding to use cases, instead of setters and getters. In that case, I would case, I would have roughly a one-on-one mapping of interactor classes and entity methods (perhaps with some interactors spanning multiple entities, and orchestrating more complex scenarios).

For example, I may have the following entity class:

Sale (entity)
+createSale()
+ammendSale()
+cancelSale() 
+shipSale()
+collectSale()

And the following command classes:

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)

What do you think of this approach? I feel it may lead to a multiplication of artifacts without much benefit, with most commands being anemic classes that just pass requests to entities and return responses. Although they do take care of encapsulating the logic to access repositories and external services, while allowing the entities to focus exclusively on domain logic (their methods representing relevant business actions and events, and their private data representing business concepts and categories).

  • This seems exactly right to me. As you note, although it may seem like a lot of one-to-one mapping right now, not all commands will correspond to a single domain method. As things become more complex in terms of domain orchestration this will help keep the public API of your application simple to understand. At some point, there must be a message piped into your application that contains the relevant data. This is unavoidable. Is there an alternative approach you are thinking about? You could group commands into an application service ( eg SalesService), but it's just semantics. – king-side-slide Jun 12 '18 at 21:16
  • @FedericoG the answer lies in your question: "they do take care of encapsulating the logic to access repositories and external services". – guillaume31 Jun 13 '18 at 11:58
4

You don't need a one-to-one mapping of interactors and entities. I believe such a design would be harmful.

DDD is all about context boundaries and the ubiquitous language within those boundaries. When you really focus on creating objects to represent the language of the business, you'll find everything begins to shape itself a bit differently.

It seems odd to me that a Sale would create or ship itself. In the real world, I could picture a sales person making a sale. Then, a shipping department may manage the logistics to ship it. Perhaps, a sale would be created via an on-line shopping cart and shipped via email. Using this language as an example, lets explore a possible alternative design.

SalesPerson
+createSale(Customer, Product, Price)
+amendSale(Sale, Amendment)

ShippingDepartment
+shipSale(Sale)

Wait a second!? Don't we usually ship Orders? Maybe it should go like this:

SalesPerson
+createOrder(Customer, Product, Price)
+amendOrder(Order, Amendment)

ShippingDepartment
+shipOrder(Order)

Order
+constructor(Customer, Product, Price)
+applyDiscount(Discount)
+updateShippingAddress(Address)

Both are getting closer, but really our sales team doesn't call them orders and our shipping department only calls them orders. Also, orders are always created from the existence of a Sale, so we should reflect that as well.

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)

And so, it continues to develop.

The way I deal with the separation of logic is to try and visualize that object in real life. Then, think about the properties and actions it has that are relevant to the business-need/use-case/feature.


Edit: How do you interact with the methods on an Entity in DDD?

Instead of using the Command pattern or other interactors, you simply use them directly:

salesPerson = new SalesPerson()
sale = salesPerson.createSale(...)

... 

shippingDept.shipOrder(new Order(sale))

Keep in mind that the two are not mutually exclusive. You can still leverage a Command pattern or Use Case/interactors if there's a need for it. You might have something like this (1 interactor to many entities and methods):

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))

    ...

Alternatively, there may be a simple script that's run once daily with nothing more than this (0 interactors):

orderRepo = new OrderRepository()
shippingDept = new ShippingDepartment()
for each order in orderRepo.getOrdersToShip()
    shippingDept.shipOrder(order)

Ultimately though, if you are creating a one-to-one mapping of classes to methods, you are creating unnecessary complexity.

  • I see that your answers improves the design of the model domain. But the question still remains the same: Would you have one Command Handler (in CQRS terms) or Use Case/Interactor (in Clean Architecture terms) class for each method in an entity? For example, a ShipOrderCommandHandler class, a PlaceOnHoldCommandHanlder class, etc. – FedericoG Jun 14 '18 at 14:36
  • lol I guess I did just give a quick "No" to that and failed to tie the improvements back to how it's different. I'm updating the answer now to try to provide more clarity into how the two approaches differ. – bitsoflogic Jun 14 '18 at 17:58
3

The first thing that I thought of when I saw your sample architecture is "Can a sale create itself? Can a sale cancel itself? More importantly, can a sale ship itself or collect its own money?"

The words "Anemic data model" are only considered dirty words because someone somewhere decided that "true object orientation always marries data and logic together." But Data Transfer Objects are used all the time to pass data across boundaries, and they're the very definition of an anemic object.

Fundamentally, "clean architecture" is just one more flavor of layered architectures. The governing principle in all layered architectures is that dependencies should only flow in one direction. For example, the Business Logic Layer (BLL) needs to know about the Data Access Layer (DAL), but the DAL should know nothing about the BLL. This arrangement allows you to, for example, attach different BLL's (e.g. one for each department in the company) to the DAL without having to change the DAL itself.

Design concepts like this are difficult to discuss in isolation, so let's make this more concrete.

enter image description here

This is an architectural diagram from a sample trading application demonstrating best practices in software development called ArchFirst. Notice that it indicates two bounded contexts: the Order Management System (OMS) and the Exchange.

Now take a look at this diagram:

enter image description here

Notice that the methods on an order depend on the context in which it is used.

Also, I'm not necessarily averse to those methods being somewhere else other than the order object, perhaps in an OrderManager object.

  • This elaborates on the domain modeling aspect of my question. But my point was to the relationship between the application layer and the domain. If you have a Web interface for the Order Management System, you will taker request and translate it into an application requests. This could be Commands passed to Command Handlers in CQRS, or requests objects passed to Interactors in Clean Architecture. The Handler/Interactor would then collaborate with the Order object of the appropriate Bounded Context. Would you agree with a mapping of roughly one to one in entity methods and handlers/interactors? – FedericoG Jun 14 '18 at 14:49
  • Yes, I would. .. – Robert Harvey Jun 14 '18 at 20:10
1

Does not feel right to me.

There are two principles at least that agree with me. The first is: Keep-It-Simple. Why introduce any purely technical layer if there is no benefit? I think your gut feeling is right, it's just duplication of stuff that may or may not solve some future problem. The Keep-It-Simple approach tells us, if it doesn't solve a problem now, get rid of it.

The second is just concentrating on the business domain. The problem I have with "Commands" is, that they are purely technical. In other words, they do not belong to the business, to the ubiquitous language or the domain. It's cruft.

Also, if you are directly referring to Uncle Bob's Clean Architecture, here is an analysis of the Clean Architecture from an Object-Oriented point of view, in which I describe in detail why those ideas are fundamentally incompatible with object-orientation.

  • That's a pretty long blog post. If you were to summarize it in two or three sentences, what would those sentences be? – Robert Harvey Jun 13 '18 at 14:57
  • 1
    @RobertHarvey "Clean Architecture" is an attempt to strictly separate different technologies from each other in a software. It's like someone opening a pack of m&ms and proceeding to separate them by color. It looks "cleaner", but misses the point entirely. – Robert Bräutigam Jun 13 '18 at 18:25
  • Yeah, I'd say that's a pretty fair assessment. – Robert Harvey Jun 13 '18 at 18:26
0

Interactors (I suppose that's what you mean by Commands) are similar to Application Services in DDD/Onion Architecture. They provide a hosting environment for the use case, or business transaction, to run, and they orchestrate calls to the domain and to external components.

For instance, you can have the following sequence

Start unit of work
Get entity from repository
Call entity method
Send email notification
Commit unit of work

This is in no way anemic or as trivial as "call entity method and return the response". It can be viewed as a responsibility of its own - you have to put this logic somewhere and if you put it in a Presenter, it could become bloated and incoherent.

  • Yes, Interactors in Clean Architecture are analogous to Command Handlers in CQRS. So you will agree that such a close correspondence between interactors and entity methods is not a smell? For example, you would PlaceOrderInteractor with all the responsibilities that you list, and then a placeOrder method in the Order entity (or whatever entity, but the method would be still named like the Interactor class). – FedericoG Jun 14 '18 at 14:40
  • If this repeats across all of your code, it's a smell that your application is just CRUD. In this case, I agree with you that it could be simplified, but then the thing I would remove are the entity methods, not the command handlers. – guillaume31 Jun 14 '18 at 21:17
  • Would you use anemic models in that case (i. e., a mostly CRUD application), and keep the business logic in the handlers? – FedericoG Jun 14 '18 at 23:46
  • Yes, there's no reason to use the DDD tactical patterns in a CRUD context. – guillaume31 Jun 15 '18 at 7:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.