Entities are the most reusable artifacts of a domain model. Hence an entity should be extremely minimalistic in design and should encapsulate only the state that is required to support the persistence model in the Aggregate to which it belongs. Regarding the abstraction of the entity's behavior, it should contain only business logic and rules that model its own behavior and interaction with its collaborating entities.
Have a look at this simple domain entity ..
class Account {
private String accountNo;
private Customer customer;
private Set<Address> addresses;
private BigDecimal currentBalance;
private Date date;
//.. other attributes
//.. constructors etc.
public Account addAddress(final Address address) {
addresses.add(address);
return this;
}
public Collection<Address> getAddresses() {
return Collections.unmodifiableSet(addresses);
}
public void debit(final BigDecimal amount) {
//..
}
public void credit(final BigDecimal amount) {
//..
}
//.. other methods
}
Looks ok ? It has a minimalistic behavior and encapsulates the business functionalities that it does in the domain.
Question : Suppose I want to do a transfer of funds from one account to another. Will
transfer()be a behavior of
Account? Let's find out ..
class Account {
//..
//.. as above
// transfer from this account to another
public void transfer(Account to, BigDecimal amount) {
this.debit(amount);
to.credit(amount);
}
//.. other methods
}
Looks cool for now. We have supposedly made the domain entity richer by adding more behaviors. But at the same time we need to worry about transactional semantics for
transfer()use case. Do we implant transactional behavior also within the entity model ? Hold on to that thought for a moment, while we have some fresh requirements from the domain expert.
In the meantime the domain expert tells us that every transfer needs an authorization and logging process through corporate authorization service. This is part of the statutory regulations and need to be enforced as part of the business rules. How does that impact our model ? Let us continue adding to the richness of the entity in the same spirit as above ..
class Account {
//.. as above
// dependency injected
private AuthorizationService auth;
//..
public void transfer(Account to, BigDecimal amount) {
auth.authorize(this, to, amount);
this.debit(amount);
to.credit(amount);
}
//.. other methods
}
Aha! .. so now we start loading up our entity with services that needs to be injected from outside. If we use third party dependency injection for this, we can make use of
@Configurableof Spring and have DI in entities which are not instantiated by Spring.
import org.springframework.beans.factory.annotation.Configurable;
class Account {
//.. as above
// dependency injected
@Configurable
private AuthorizationService auth;
//..
}
How rich is my entity now ?
Is the above
Accountmodel still a POJO ? There have already been lots of flamebaits over this, I am not going into this debate. But immediately certain issues crop up with the above injection :
- The class
Accountbecomes compile-time dependent on a third party jar. That
importlying out there is a red herring.
- The class loses some degree of unit-testability. Of course, you can inject mocks through Spring DI and do unit testing without going into wiring the hoops of an actual authorization service. But still, the moment you make your class depend on a third party framework, both reusability and unit testability get compromised.
- Using
@Configurablemakes you introduce aspect weaving - load time or compile time. The former has performance implications, the latter is messy.
Does this really make my domain model richer ?
The first question you should ask yourself is whether you followed the minimalistic principle of class design. A class should contain *only* what it requires to encapsulate its own behavior and nothing else. Often it is said that making an abstraction design better depends on how much code you can remove from it, rather than how much code you add to it.
In the above case,
transfer()is not an innate behavior of the
Accountentity per se, it is a use case which involves multiple accounts and maybe, usage of external services like authorization, logging and various operational semantics like transaction behavior.
transfer()should not be part of the entity
Account. It should be designed as a domain service that uses the relationship with the entity
Account.
class AccountTransferService {
// dependency injected
private AuthorizationService auth;
void transfer(Account from, Account to, BigDecimal amount) {
auth.authorize(from, to, amount);
from.debit(amount);
to.credit(amount);
}
//..
}
Another important benefit that you get out of making
transfer()a service is that you have a much cleaner transactional semantics. Now you can make the service method transactional by adding an annotation to it. There are enough reasons to justify that transactions should always be handled at the service layer, and not at the entity layer.
So, this takes some meat out of your entity
Accountbut once again gives it back the POJO semantics. Taking out
transfer()from
Account, also makes
Accountdecoupled from third party services and dependency injection issues.
What about
Account.debit()and
Account.credit()?
In case
debit()and
credit()need to be designed as independent use cases under separate transaction cover, then it definitely makes sense to have service wrappers on these methods. Here they are ..
class AccountManagementService {
// dependency injected
private AuthorizationService auth;
@Transactional
public void debit(Account from, BigDecimal amount) {
from.debit(amount);
}
@Transactional
public void credit(Account to, BigDecimal amount) {
to.credit(amount);
}
@Transactional
public void transfer(Account from, Account to, BigDecimal amount) {
//..
}
//..
}
Now the
Accountentity is minimalistic and just rich enough ..
Injection into Entities - Is it a good idea ?
I don't think there is a definite yes/no answer, just like there is no definite good or bad about a particular design. A design is a compromise of all the constraints in the best possible manner and the goodness of a design depends very much on the context in which it is used. However, with my experience of JPA based modeling of rich domain models, I prefer to consider this as my last resort. I try to approach modeling an entity with a clean POJO based approach, because this provides me the holy grail of complete unit-testability that I consider to be one of the most important trademarks of good design. In most of the cases where I initially considered using
@Configurable, I could come up with alternate designs to make the entity decoupled from the gymnastics of third party weaving and wiring. In your specific design there may be cases where possibly you need to use
@Configurableto make rich POJOs, but make a judgement call by considering other options as well before jumping on to the conclusion. Some of the other options to consider are :
- using Hibernate interceptors that does not compromise with the POJO model
- instead of injection, use the service as an argument to the entity method. This way you keep the entity still a pure POJO, yet open up an option to inject mocks during unit testing of the entity
Another point to consider is that
@Configurablemakes a constructor interception, which means that construction of every instance of that particular entity will be intercepted for injection. I do not have any figures, but that can be a performance overhead for entities which are created in huge numbers. A useful compromise in such cases may be to use a getter injection on the service, which means that the service will be injected only when it is accessed within the entity.
Having said all these,
@Configurablehas some advantages over Hibernate interceptors regarding handling of serialization and automatic reconstruction of the service object during de-serialization.
For more on domain modeling using JPA, have a look at the mini series which I wrote sometime back. And don't miss the comments too, there are some interesting feedbacks and suggestions ..
16 comments:
Good post, good example. However I would argue that the knowledge of how to transfer an amount should be inside the domain (I consider domain services to be outside the domain). After all, that is domain logic. So your AccountTransferService.transfer would call from.creditTo(to, amount). The service layer should contain a minimal amount of code, and certainly no domain code; that should be delegated onwards to your domain classes. If you need to transfer funds from within another domain service, your domain class already knows how to do that and you're not duplicating code.
(All IMO of course ;))
Great post.
It would be very interesting to see how you implement the logic of the services as well.
Oh excellent. I like domain driven design discussion!
As someone who is only just getting into object oriented programming, I have to say that this post is a very clear and easy to understand explanation. Thanks for putting this up there. It is really helping to formulate my opinions on OOP.
You can't consider the scope of your domain model without a context. In other words: this example sucks.
Why should the transfer() method be in the Account class? And if including the transfer() method does impact your "entities", could it be that the reason to put it there in the first place bring about advantages that outweigh the disadvantages by a considerable amount?
"You can't consider the scope of your domain model without a context."
Exactly! That is the point. And we should try to keep context out of reusable entities and into domain services. The entities should always be self-contained and free of any dependencies with the context services.
"Why should the transfer() method be in the Account class? "
It should not! And that is also the precise point of the post.
""You can't consider the scope of your domain model without a context."
Exactly! That is the point. And we should try to keep context out of reusable entities and into domain services. The entities should always be self-contained and free of any dependencies with the context services."
Wrong again.
You mean with context state. I mean with context the sum of all constraints and obligations.
If you try to cram your single silly table-to-class mapping everywhere in your application you will indeed suffer and pay the price.
If you however reconsider you state requirements and try to fit your data access requirements into specific situations you're free.
@Danny
I agree with you that it is not a service. I would probably have called it an AccountTransferCommand that is used by the service layer but is still a part of the domain.
@Anonymous
So you would inject the authorization service into the domain objects? And the Auditing service? And the ReceiptPrinterService? And whatever else needs to be done as part of the transfer?
@Danny:
"However I would argue that the knowledge of how to transfer an amount should be inside the domain (I consider domain services to be outside the domain)"
Why do you consider domain services to be outside the domain ? Domain services, according to Eric Evans' DDD book are also very much part of the domain. Refer to Chapter 5 in his book. Entities are the most reusable, self contained elements of your architecture and *must* be designed as ones which are easily unit testable. When we talk about rich domain models, it is not only the entities but the domain services as well - the richness is all about making the *domain layer* rich without allowing domain logic to leak out of domain layer into others like presentation layers.
@Anonymous:
I usually use the terminologies of DDD as evangelized by Eric Evans. Hence I talked about transfer() being part of a service and NOT a command.
@Anonymous
So you would inject the authorization service into the domain objects? And the Auditing service? And the ReceiptPrinterService? And whatever else needs to be done as part of the transfer?
This example is so crappy that no sane person can figure out what's going one. Hence it's impossible to predict what one would or should do.
It's unfortunately common practice to try to explain DDD with crappy examples. I suggest to all self-proclaimed DDD educators to give us a break, work for a couple of months on a really complicated application and come back.
Debasish,
please don't confuse the concept of a 'Rich Domain Model' with a 'Distilled Domain Modell'.
A rich domain model isn't per se a concept from DDD. Instead it's the alternative draft to an anemic domain model, where entities are nothing more than dump data containers and business logic is centralized around that 'data structures' in a big service layer (often encountered with Entity Beans under 2.1). In contrast to that rather procedural style of organizing a domain model, there's a more object oriented style, where the domain is modeled by applying all offered features of OO (i.e. inheritance, polymorphic associations and objects that have state AND behaviour). This kind of domain model would be rich in respect to the mentioned features and true object oriented at it's core.
In your post you rather describe the process of distilling the core domain, finding the right boundaries, abstractions and concepts of the business that will lead to a deep or distilled domain model.
Greetings
Mario
@debasish:
"Why do you consider domain services to be outside the domain?"
Because they are -- literally -- not domain objects. They do not represent any tangible objects in the actual problem domain. Services are the way for the outside world to communicate with the domain; which is why they'll contain usecase-like methods. You're right though, that is not the implementation Evans describes. There are other DDD-gurus out there...
So I see domain services as a very thin layer that should delegate as much as possible to the actual domain classes. As domain classes have no knowledge of domain services, only a service can call another service (like the AuthorizationService in your example). Also, I think that putting too much (or any) domain logic into your domain services encourages a procedural programming style.
I wouldn't introduce an additional method ("transfere") that is just a combination of the existing ones (credit & debit). Due to this argue, i'll also use a kind of additional (operational-like) API, if any. ) The example shows certain disadventage of such overloaded interface, but doesn't demonstrate something else.
@Danny:
They do not represent any tangible objects in the actual problem domain
Why would everything have to be objects/nouns in the domain ? There are enough scopes for some verbs. Rich behavior that involve collaboration between multiple objects are often modeled better as services.
Note that the domain layer, not the application layer(service objects), is responsible for fundamental business rules—in this case, the rule is "Every credit has a matching debit."
- Domain Drive Design: Chapter