Logic Belongs to Entities

Encapsulating the business logic into entities is especially difficult when the logic involves something more. Something more could be a different module, external system, database, …

Example

Logic in Service

We can use Doctrine, our favorite ORM or a different database layer instead of PDO, but the code will be similar.

As we can see, we collect data from Cart entity and then do the business logic. This approach is difficult to test, breaks encapsulation, leads to an anemic model with a logic outside of entities. Not so good outcome.

Think About Use-Case Only

  • a Price of each Product
  • Amount of each of Products

That’s all we need. It doesn’t matter if the use-case is implemented in service or entity, we need all this information in both implementation.

Moving Logic to Cart Entity

We already know the amount of each of the products in the Cart, so there is no problem.

But we also need to know the price of each Product in the Cart. We don’t need to know prices in the whole Cart, we need to know prices only during the calculation of the total price. So if we pass Product Prices just to the calculating method, we solve the problem.

And we can achieve Product Prices by an interface

Now we can pass this interface into the calculation method that sits in the Cart

As we can see, the method is logically as same as it was in CartTotalPriceService. But there are a couple of important differences

  • The logic sits together with data in the Cart entity, this leads to a rich model
  • The Cart doesn’t expose data (Items), Items are therefore properly encapsulated
  • This code is testable as we can mock the interface in tests to produce predictable prices
  • By the way, this was our goal — to have the logic in the entity

Interface Implementation

Do you remember CartTotalPriceService? The code is the same.

And again we can use our favorite ORM, or database layer to implement the interface. We can do even more, the implementation can use different system module or even API to a different system. We are not limited by technology, all thanks to the interface.

Only One Implementation

We are using interface to separate layers in this case. We separate the business logic layer and infrastructure (database) layer. And that is a valid interface usage.

Moving Logic … even further

An Item is probably a value object an moving the logic into value objects is even greater than moving the logic to entities. That’s because value objects are immutable and the logic is, therefore, super easily testable and super separated.

So let’s move some logic into the Item

Even the Item has behavior now. The Item doesn’t expose the data and contains the logic that directly uses the item itself.

Maybe this paragraph isn’t only for maniacs, but programmers are usually shocked just by the interface, so I decided to calm down a bit.

Conclusion

Contact

Developer interested in Domain-Driven Design & Modeling