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.