It is possibly an understatement to emphasize the usefulness of making your domain model rich. It has been reiterated many times that a rich domain model is the cornerstone of building a scalable application. It places business rules in proper perspective to wherever they belong, instead of piling stuffs in the form of a fat service layer. But domain services are also part of the domain model - hence when we talk about the richness of a domain model, we need to ensure that the richness is distributed in a balanced way between all the artifacts of the domain model, like entities, value objects, services and factories. In course of designing a rich domain model, enough care should be taken to avoid the much dreaded bloat in your class design. Richer is not necessarily fatter and a class should
only have the responsibilities that encapsulates its interaction in the domain with other
related entities. By related, I mean, related by the
Law of Demeter. I have seen instances of developers trying to overdo the richness thing and ultimately land up with bloated class structures in the domain entities. People tend to think of a domain entity as the sole embodiment of all the richness and often end up with an entity design that locks itself up in the context of its execution at the expense of reusability and unit testability. One very important perspective of architecting reusable domain models is to appreciate the philosophical difference in design forces amongst the various types of domain artifacts. Designing an entity is different from designing a domain service, you need to focus on reusability and a clean POJO based model while designing a domain entity. OTOH a domain service has to interact a lot with the context of execution - hence it is very likely that a domain service needs to have wiring with infrastructure services and other third party functionalities. A value object has different lifecycle considerations than entities and we need not worry about its identity. Hence when we talk of richness, it should always be dealt with the perspective of application. This post discusses some of the common pitfalls in entity design that developers face while trying to achieve rich domain models.
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;
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) {
}
}
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 {
public void transfer(Account to, BigDecimal amount) {
this.debit(amount);
to.credit(amount);
}
}
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 {
private AuthorizationService auth;
public void transfer(Account to, BigDecimal amount) {
auth.authorize(this, to, amount);
this.debit(amount);
to.credit(amount);
}
}
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
@Configurable
of Spring and have DI in entities which are not instantiated by Spring.
import org.springframework.beans.factory.annotation.Configurable;
class Account {
@Configurable
private AuthorizationService auth;
}
How rich is my entity now ?Is the above
Account
model 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
Account
becomes compile-time dependent on a third party jar. That import
lying 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
@Configurable
makes 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
Account
entity 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 {
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
Account
but once again gives it back the POJO semantics. Taking out
transfer()
from
Account
, also makes
Account
decoupled 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 {
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
Account
entity 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
@Configurable
to 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
@Configurable
makes 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,
@Configurable
has 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 ..