domain-driven-designclean-architecturesingle-responsibility-principlerich-domain-model

How to properly build rich domain models


Imagine rich domain model, called User with methods (omitting parameters):

  1. TransferMoney()
  2. SendFriendInvitation
  3. AcceptFriendInvitation

The TransferMoney will be used to transfer money from user A to user B and. It involves adding money to account B and substracting from account A.

The SendFriendInvitation will be used to send friend invitation to user B.

The AcceptFriendInvitation will be used to accpet friend invitation from user A.

The User will have private properties like _accountBalance, _friendInvitations.

And see that every of above methods will just change the private properties, it won't affect "external world". We could affect it by injecting some services into User by constructor, but everywhere I have read - the rich domain model should be independant of services.

So how can we affect e.g. data in database? I think we can use something like event sourcing, so every method creates some event and it's handled by appropriate handlers in outer layers (e.g. Application layer).

But this approach means that our rich model depends on event broadcaster. I hope now you see. In reality, our model DOESN'T depend on event emitter or services we inject into it. They are used to inform outer layers - but the core, domain can work without them (but without affecting world).

So I have read many articles and now I'm consufed. Because they say that we cannot inject anything into rich domain model. So how should I interact with something cooler than private properties? And isn't the rich domain model against Single Resposibility Principle?


Solution

  • Isn't the rich domain model against Single Resposibility Principle?

    The rich domain model as you described it is against the SRP.

    One misunderstanding of rich domain models is that "rich" means that the domain object does nearly everything. This is not the case. As you correctly recognized it would violate the SRP. "Rich" means that the domain model may contain business rules, but only business rules that apply to domain object itselfs. If you want to understand the different terms you should read anemic domain model, published on November 25th by Martin Fowler. You might also want to take a look at the answer that I gave to the question "Concrete examples on why the 'Anemic Domain Model' is considered an anti-pattern"

    But let's go back to the rich domain model. In a rich domain model a User e.g. has a property birthday. One might implement a method getAge which calculates the current age. In other words: the method getAge adds logic to the User that only applies to a User object. This is what uncle bob means with application agnostic business rules. These rules do not belong to one application. They are more general. You should design you domain objects in a way that they can potentially used in another application.

    Uncle Bob says: Entities encapsulate Enterprise wide business rules. An entity can be an object with methods, or it can be a set of data structures and functions. It doesn’t matter so long as the entities could be used by many different applications in the enterprise.

    So put application specific business rules in use cases and application agnostic in domain objects.

    So how can we affect e.g. data in database?

    What you described as TransferMoney or SendFriendInvitation are use cases or at least domain services. When I talk about domain services I mean domain services as described by Voughn Vernon in his book Implementing Domain-Driven-Desing, Chapter 7 Services. A domain service encapsulates business rules that apply to multiple domain objects that can not be implemented in one of these objects. A domain service is used by a use case. Use cases are also often called applications services.

    So you should create a use case for the TransferMoney or SendFriendInvitation and keep that stuff away from the domain object. The use case itself might then define an interface, e.g. a Repository or maybe an InvitationService. This interface can then be implemented in different ways. You usually have 2 implementations. One for the live system and one for tests.

    EDIT

    So how should we say to database that user changed age? In my use case I will execute setAge on User and another me thod from Repository to change the age in database? It's a bit weird for me that I would have to repeat some things (do the same on instance and later on record living in database)

    It might look weird to you, because your mindset is different. You don't repeat things. We are talking about differnt things. So let me try to explain it.

    If you put the database access into the domain object it means that the domain object has a dependency to the database access. So you might change the domain object whenever database related stuff changes. The domain object also implements domain logic, so you also change it when the domain logic changes. This are two different reasons for the domain object to change. Thus, it violates the SRP.

    But there is more to consider. If you have multiple responsibilities in one object, it also means that this object accumulates all dependencies of each responsibility. This usually makes it harder to test, because you need to mock more and it increases the immobility. Immobility means that you can not easily reuse it in another context, because the depencencies it has, must also be available in another context (or application) and these dependencies often collide with the dependencies of that other context. And if one object might change for different reasons, it is likely that you and your colleague have to change the same object (class), even you are not working on the same task. Thus, merge conflicts occur more often.

    These are reasons why developers separate responsibilities like the database access from the domain logic.