Anemic Domain Model in Typical Spring Projects (Part 1)
Join the DZone community and get the full member experience.
Join For FreeWhen I just started my career as a Java developer, I knew only a few basic design patterns. I tried to implement them everywhere, even when I shouldn't have, and I thought that more experienced people use them on a daily basis. With time, I have changed several different teams and companies, but I have never seen real usage of “real” patterns. Of course, I am not talking about Builder, Singleton, or Abstract Fabric, but rather about more complicated and less common ones like Bridge, Observer, etc. Familiar situation, isn’t it?
Basics
For a full understanding of the problem, we should begin with the basics.
Low coupling and high cohesion tell us that every class shouldn't have a lot of dependencies from other classes (low coupling) but every class has to contain only those methods and fields that it requires and it should solve only one specific problem (high cohesion, single responsibility). If you put all your code into only one class, that is low coupling but not high cohesion.
What does a typical project written with the Spring framework look like? A classical three-tier application implements an MVC pattern. A controller has some endpoints and receives requests with them. Next, it calls a method. The method contains all of our business logic, creates domain entities, and persists it to the database (or get/delete it).
Example of layered architecture:
"api/expense") (
public class ExpenseController {
private final ExpenseService expenseService;
...
// other injected classes
value = "") (
value = HttpStatus.OK) (
public void addNewExpense( ExpenseDTO expenseDTO) {
expenseService.addNewExpense(expenseDTO);
}
...
// other methods
}
public class ExpenseBOImpl implements ExpenseBO {
private final ExpenseRepo expenseRepo;
private final AuthenticationBO authenticationBO;
...
// other injected classes
public void createExpense(ExpenseDTO expenseDTO) throws ValidationException {
validateExpense(expenseDTO);
ExpenseEntity expenseEntity = new ExpenseEntity();
initExpenseEntity(expenseEntity, expenseDTO);
expenseRepo.save(expenseEntity);
}
...
// other methods
private void initExpenseEntity(ExpenseEntity expenseEntity, ExpenseDTO expenseDTO) {
UserEntity userEntity = authenticationBO.getLoggedUser();
expenseEntity.setUser(userEntity);
expenseEntity.setPrice(expenseDTO.getPrice());
expenseEntity.setComment(expenseDTO.getComment());
expenseEntity.setDate(LocalDateTime.now());
initExpenseTypes(expenseEntity, expenseDTO.getTypes());
}
...
// other methods
}
But MVC has only three layers, so where should Services be in this pattern? Spring encourages us to split the Model layer to Model and Service. In this variation of the pattern, the model contains information and service behavior.
Official Spring tutorials teach us that domain objects shouldn’t have any methods except getters and setters and they should be POJOs. Many authors (like Martin Fowler) consider it an antipattern and call it the Anemic Domain Model.
With Anemic Domain Design, all a program’s logic is kept in the business logic layer (classes with Service or BO suffixes). Then domain objects don’t have methods that operate with class fields, hence the object doesn’t have behavior. That breaks OOP principles, GRASP patterns, and prevent us from implementing design patterns.
Example of the anemic model:
xxxxxxxxxx
name = "expense", schema = "expenses") (
public class ExpenseEntity {
strategy = GenerationType.IDENTITY) (
name = "id") (
private Integer id;
name = "price") (
private Integer price;
name = "comment") (
private String comment;
name = "date") (
private LocalDateTime date;
fetch = FetchType.LAZY) (
name = "user_id") (
private UserEntity user;
fetch = FetchType.LAZY) (
name = "expense_to_expense_type_dict", (
joinColumns = (name = "expense_id"),
inverseJoinColumns = (name = "expense_type_id"))
FetchMode.SUBSELECT) (
private List<ExpenseTypeDictEntity> expenseTypeDict;
}
GRASP
Information Expert tells us that method should belong to an object whose fields it uses. Or fields should belong to an object that uses them.
Creator says that only a class A that uses class B or has dependencies from A should create instances of B.
Example:
xxxxxxxxxx
// A can create B
class A {
B b;
}
// B can't create A
class B {
}
If there is a method chain that passes the same object down to other methods in different objects, it violates the Creator pattern because the object should be used in the same place where it has been created. However, often we need to transfer an object between layers or call a method in a remote machine or pass data from the front end/a microservice. In that case, we use DTO, which is a violation of OOP because DTO is an object without behavior. We can’t transfer the object’s methods through the network. Still, we do this consciously since there is no other way.
A constructor is a method. Therefore Creator is a sub-case of Information expert.
Adherence to these principles leads to low coupling. Not sticking to those rules contradicts the principle of encapsulation — one piece of code should not touch another piece with its greasy fingers. Even if an object passes information by a getter, it is still information. It is WRONG to pass and process information in other places. Over time, the system will be very difficult to change.
Example:
xxxxxxxxxx
public class ExpenseServiceImpl implements ExpenseService {
...
// services and infrastructure
public void addNewExpense(ExpenseDTO expenseDTO) {
ExpenseEntity expenseEntity = new ExpenseEntity();
businessLogicMethod(expenseEntity);
}
private void businessLogicMethod(ExpenseEntity expenseEntity) {
// do nothing with expenseEntity
// violates the principle of encapsulation, GRASP patterns
anotherBusinessLogicMethod(expenseEntity); // only next method needs expenseEntity
}
}
What do we have in a typical Spring application? All logic is located in services, which are singletons. It is a variation of the Singletonism antipattern and leads to a procedural style of programming. Therefore we have a lot of duplicated code, methods with too many parameters (4+), and local variables that, in turn, makes an Extract Method impossible.
Example of Extract Method:
xxxxxxxxxx
// Before
public void createExpense(ExpenseDTO expenseDTO) {
ExpenseEntity expenseEntity = new ExpenseEntity();
expenseEntity.setUser(userEntity);
expenseEntity.setPrice(expenseDTO.getPrice());
expenseEntity.setComment(expenseDTO.getComment());
...
// other setters
expenseRepo.save(expenseEntity);
}
// After
public void createExpense(ExpenseDTO expenseDTO) {
ExpenseEntity expenseEntity = createExpenseEntity(expenseDTO);
expenseRepo.save(expenseEntity);
}
private createInitializedExpenseEntity(expenseDTO) {
ExpenseEntity expenseEntity = new ExpenseEntity();
expenseEntity.setUser(userEntity);
expenseEntity.setPrice(expenseDTO.getPrice());
...
// other setters
}
Protected Variations — the problem of system requirement changes is real in enterprise development. It is often possible to identify the instability points of the system that will most likely be subject to changes and modifications. The essence of the Protected Variations pattern is to eliminate the instability points by defining them as interfaces and using polymorphism to create various implementations with different behaviors of this interface.
Protected Variations solve an issue when changing one element of the system causes changes in other elements.
In practice, it looks like this: there is a method with a zillion nested IF clauses. If you change one inner clause, it causes changes in the outer clause's behavior. You can use the Replace Conditional with Polymorphism to avoid this. Instead of hundreds of IF clauses, it's best to create hundreds of polymorphic classes that encapsulate logic, which is encouraged by Information Expert. However, in an anemic model, this logic will be held in services. Therefore there is no place to put those methods and create new classes.
Many inexperienced programmers are afraid to create additional classes. You can often hear the phrase "too many classes". But if the big ones are not divided into smaller ones, it will violate the high cohesion principle.
The number of errors per line of code is a constant. For example, a programmer makes 1 error per 1000 lines. In practice, it looks like this: a 1000 line-long method always has bugs that are hard to find. You fix one but after a while, a new one appears. However, if you split the method into small ones and extract them to separate classes, there will only be a few lines of code in each, free from errors, and you won’t open it without needing to. If there was a bug, it would be easy to find and fix because we have the class with only a few lines. After the fix, we will forget about that class.
Ideally, you should work with classes only through interfaces. I would recommend borrowing an idea from Test Driven Development, i.e, to create a user-friendly interface first and then implement it. This will improve the quality of the code, assuming we make ourselves clients of our code and immediately see how convenient it is to use the new method.
Example of Protected Variations with Replace Conditional With Polymorphism:
xxxxxxxxxx
// Before
class ExpenseEntity {
// ...
Integer getPrice() {
switch (type) {
case BASIC:
return getBasicPrice();
case SALE:
return getBasicPrice() * calculateCasualDiscount();
case WHOLESALE:
return getBasicPrice() * getAmount() * calculateWholesaleDiscount();
}
throw new RuntimeException("Should be unreachable");
}
}
// After
// contains all shared fields and methods
abstract class ExpenseEntity {
// ...
public abstract Integer getPrice();
protected Integer getBasicPrice() {
// get the basic price
}
}
// Casual expense, which does not have any specific fields or methods
class BasicExpenseEntity extends ExpenseEntity {
public Integer getPrice() {
// get the price for a casual expense
}
}
// Expense that user made with the discount
class SaleExpenseEntity extends ExpenseEntity {
public Integer getPrice() {
// get price with the discount
}
}
// Goods that user has bought in large amount
class WholesaleExpenseEntity extends ExpenseEntity {
public Integer getPrice() {
// get the wholesale price
}
}
The same rules can apply to all refactorings and patterns that use polymorphism: Bridge, State/Strategy, Replace Type Code with Subclasses, etc. I have never seen polymorphic domain objects in my practice, hence I have not seen the usage of polymorphism.
Rich Domain Model
What if we try to move our business logic directly to the model, as OOP requires? There are different variations of the Rich Model.
Example of domain model written in Rich Domain Model Extreme style:
xxxxxxxxxx
public class ExpenseEntity {
strategy = GenerationType.IDENTITY) (
name = "id") (
private Integer id;
name = "price") (
private Integer price;
...
// model has dependency from other layers and from infrastructure
private final ExpenseRepo expenseRepo;
private final AuthenticationBO authenticationBO;
private final UserRepo userRepo;
...
public void createExpense(ExpenseDTO expenseDTO) {
// model does too much!
validateExpense(expenseDTO);
ExpenseEntity expenseEntity = new ExpenseEntity();
initExpenseEntity(expenseEntity, expenseDTO);
expenseRepo.save(expenseEntity);
}
private void validateExpense(ExpenseDTO expenseDTO) {
...
}
private void initExpenseEntity(ExpenseEntity expenseEntity, ExpenseDTO expenseDTO) {
UserEntity userEntity = authenticationBO.getLoggedUser();
expenseEntity.setUser(userEntity);
expenseEntity.setPrice(expenseDTO.getPrice());
expenseEntity.setComment(expenseDTO.getComment());
expenseEntity.setDate(LocalDateTime.now());
initExpenseTypes(expenseEntity, expenseDTO.getTypes());
}
private void initExpenseTypes(ExpenseEntity expenseEntity, List<String> types) {
// model persists to a DB other objects!
List<ExpenseTypeDictEntity> expenseTypes = new ArrayList<>();
types.forEach(x -> {
ExpenseTypeDictEntity typeDict = expenseTypeDictRepo
.findByNameIgnoreCase(x.trim())
.orElseGet(() -> createExpenseTypeDict(x));
typeDict.setUsedCount(typeDict.getUsedCount() + 1);
typeDict = expenseTypeDictRepo.save(typeDict);
expenseTypes.add(typeDict);
});
expenseEntity.setExpenseTypeDict(expenseTypes);
}
private ExpenseTypeDictEntity createExpenseTypeDict(String name) {
...
}
}
As we can see, we introduced the model layer to DI and other auto-magic, which ruins layered architecture. In addition, there may be too many methods and fields in this object that have nothing to do with the business logic.
This is it for the first part. In the second part, we will discuss the advantages and disadvantages of both domain models, then try to find a balance between them.
Opinions expressed by DZone contributors are their own.
Comments