Separation of application logic and domain logic in Clean Architecture

business-logicclean codedomain-driven-designseparation-of-concernsuse-case

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

Best Answer

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.

Related Topic