How to avoid the anemic domain model with Spring and Hibernate
Have you ever had the suspicion that there’s a better way to write your code but you just can’t picture it? Have you ever written testable, reusable code that was KISS, DRY, SOLID that others would be proud of but you just. can’t. shake. the. feeling. that there MUST be a better way? I know I have and I still do.
But I’ve had a revelation. It’s called Domain Driven Design. It’s a term coined by Eric Evans in his seminal book by the same name. In it he describes guidelines and examples of how the core of a software product should be built. In a nutshell he proposes that every piece of software is built to solve a problem and therefore the software should reflect the problem domain, jargon and all, so that developers can communicate and be guided by domain experts and their insights.
I’ve been developing Spring based applications for more than 6years now and although I’ve been looking and playing around for/with alternatives I’ve always went back because as far as “batteries included” is concerned the Spring ecosystem is unparalleled, the community and support ecosystem is in my experience second to none and the ease of development and stability is unmatched. Even when something didn’t work as advertised it was so easy to report it, fix it and have the fix included in the next version and the experience was so pleasant that it should be advertised as a framework feature.
Nevertheless, I’ve always had the feeling that my Spring applications were not architected or designed as best as they could. No matter what I did some of my code ended up being large lumps of procedural methods, shoved into service classes.
To illustrate my concerns let’s say you’re building a standard modern web app where the UI code sends JSON data to some HTTP endpoint backed by a Spring MVC controller, the controller does some stuff, usually passing the data to a JPA repository and returns data back to the UI code.
Sometimes the above scenario is simple and it looks something like this:
@ResponseBody
@RequestMapping(value = "/api/entity", method = RequestMethod.POST)
public Entity save(@Valid @RequestBody Entity entity) {
return repository.save(entity);
}
But sometimes there’s a lot more to be done before and after the handler is executed. For example you might want to check if this particular user is allowed to perform an action to this particular entity (e.g.: a Contract entity where only an employee of the organizations that are counter parties to the contract is allowed to modify it) and then send an email once the action is performed succesfully etc etc.
@ResponseBody
@RequestMapping(value = "/api/entity", method = RequestMethod.POST)
public Entity save(@Valid @RequestBody Entity entity) {
return service.doSomethingWithThisEntity(entity);
}
public class Service {
public Entity doSomethingWithThisEntity(Entity entity) {
validate(entity);
authorize(entity);
doSomething(entity);
persist(entity);
sendEmails();
...
}
}The code above is typical in Spring applications (recommended even) and although it might look tidy (as in simple to read and follow) it has huge problems:
- It tries to do too much (validation, authorization, email,persistence etc).
- There is no other way to relay failure other than exceptions leaving the onus on the user to handle the error cases.
- It often is tightly coupled to other services.
- Code re-use can only be achieved by moving code to a parent class or static methods.
- And most importantly: It mixes domain logic with implementation details making it hard to test business logic.
All the above issues arise from the fact that Spring enforces an anaemic domain model, forcing the developer to shove domain logic to auxiliary classes. Because domain entities are usually retrieved via JPA or HTTP it’s cumbersome to put any logic in them because you’d need to pass dependencies to them every time, take care of transactions etc.
In order to solve the above problems you could use some dark magic (i.e.: Aspectj and @Configurable) to inject everything that’s needed which looks like this:
@ResponseBody
@RequestMapping(value = "/api/entity", method = RequestMethod.POST)
public Entity save(@Valid @RequestBody Entity entity) {
return entity.doSomething();
}
public class Entity {
@Autowired EntityRepository repository;
@Autowired EmailService emailService;
public Entity doSomething() {
validate();
authorize();
doSomething();
persist();
sendEmails();
...
return this;
}
}
Admittedly, the Entity now has the freedom to perform much more domain actions and contain any logic required but the problem is that the code is still procedural and it doesn’t really solve the issues from the first attempt. You still need lot’s of plumbing to test the logic, there’s still tight coupling etc. And also now you need DTO classes, otherwise you’ll need to remember to add @JsonIgnore/transient on all the fields that hold dependencies (so that they don’t get serialized etc).
Another approach
In my opinion the problem lies with the way REST puts the focus on the entity itself. If you architect your app in a way that the backend acts as a database frontend and let the client handle the logic it makes it difficult to ensure integrity and a lot of logic gets dispersed and replicated.
Another thing that is affected is the ability to reason about the application and model business procedures in meaningful ways. When the client performs a POST request to the path /orders it’s not immediately clear (especially for non-developers) that this is the business process “new purchase order”. HTTP is an implementation detail and it should not creep into the “ubiquitous language” that should be built between domain experts and developers.
Wouldn’t it be better to represent the business process “new purchase order” as a concrete entity that both developers and and domain experts understand? What would happen if we modelled business procedures as concrete classes?
— — — — — — — — —
So you sit down and think long and hard and you ultimately boil everything down to the following requirements:
- A clean separation of business logic and implementation details.
- Any data required to perform the action must be easy to retrieve (via JSON, JPA or whatever). No special handling must be required (no dark magic).
- It needs to be difficult to do something bad (like forget to check if the user is authorized before performing the action).
- The solution must fit the domain model.
- Testing must be made easy with minimum plumbing.
You also notice a pattern in previous attempts. It looks like business logic is always sandwiched between code that handles crosscutting concerns like security, validation, persistence etc so what if you took each step and made it executable and only if execution is successful then proceed with the next step?
So you open your IDE and start coding. You start with creating a class that maps action steps and you call it Command. Each command needs some data to work on so you put a field called value to hold it and an abstract method called execute where subclasses put their logic. But that’s not enough, you still need to satisfy requirement 3 so that other developers don’t call execute out of place.
What if there’s a before() method that returns an ExecutableCommand and you move execute() there? That way there is no way to call execute() before before() because there is no such method in the command. That sounds smart so you end up with something like this:
// Command super class
public abstract class CommandObject {
@JsonProperty
protected T value;
public T getValue() {
return value;
} public void setValue(T value) {
this.value = value;
}public abstract ExecutableCommand isAllowed(Account account);
}
// Interface for executable commands
public interface ExecutableCommand {
ExecutedCommand execute();
}
// Interface for succesfully executed commands
public interface ExecutedCommand {
void andThen(Callback... callbacks);
}
// A command implementation
public class SomeCommand extends CommandObject implements ExecutedCommand {
public static final String ENDPOINT = "/api/entity";
@Autowired
@JsonIgnore
private Repository repository;
public ExecutableCommand ifAllowed(Account account) {
// authorize
return this::doExecute;
} private ExecutedCommand doExecute() {
value = repository.save(value);
return this;
} public void andThen(Callback... callbacks) {
// possibly execute your own callbacks here
// then execute the callbacks of your user
if (callbacks != null) {
for (Callback callback : callbacks) {
callback.run(value);
}
}
}
}@RequestMapping(value = SomeCommand.ENDPOINT, method = RequestMethod.POST)
public ResponseEntity doCommand(@AuthenticationPrincipal AccountDetails accountDetails, @RequestBody @Valid SomeCommand command) {
command.ifAllowed(accountDetails.getAccount()).execute().andThen(
(Callback) entity -> emailService.notify(entity)
);
return new ResponseEntity<>(command, HttpStatus.OK);
}
// A bit of white magic is needed to supply dependencies
@Aspect
public class AutowireAspect {
@Autowired
private AutowireCapableBeanFactory beanFactory;
@Before(value = "execution(* com.*.Controller.*(..))")
public void autowire(JoinPoint joinPoint) throws Throwable {
CommandObject commmand = getCommand(joinPoint.getArgs());
if (commmand != null) {
beanFactory.autowireBean(commmand);
}
}
}
That’s much better, right ? You can now test the steps in isolation with minimal mocks and without a spring context (requirement 5). You can send commands down the wire easily and you can access and manipulate your entity from the command value field (requirement 2). Requirement 1 is also met because the business logic is contained in the execute() methods and any implementation detail goes before or after it. Also, there is no way a junior dev or a sleepy you can mess this up because the order is now enforced by the interfaces and the compiler (requirement 3). And moreover you can now have domain vocabulary that everyone understands e.g.: you can have a SignContractCommand that you can discuss with domain experts where before you would have a HTTP POST that takes Contract. In addition, you can easily support more complex commands by creating holder objects with multiple fields and creating commands for them.
BUT…
Although you can handle complex data, it’s cumbersome to support complex actions. In the above example I cheated and only demonstrated the authorization step before execution. If I was to support validation I would then need a ValidatedCommand interface to return in the ifAllowed() method and so on:
// Interface for validated commands
public interface ValidatableCommand {
ExecutableCommand isValid();
}
public ValidatableCommand ifAllowed(Account account) {
return this::doValidate();
}That means that every time you need to add a step you potentially need to refactor EVERY command in the project where some commands might not need the extra step.
All in all I believe this is a better approach. Not perfect, but better.