{{announcement.body}}
{{announcement.title}}

Anemic Domain Model in Typical Spring Projects (Part 1)

DZone 's Guide to

Anemic Domain Model in Typical Spring Projects (Part 1)

One developer talks about how design patterns hold up in the real world as they examine layered architecture and domains models.

· Java Zone ·
Free Resource

When 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:

Java
 




x
51


 
1
@RestController
2
@RequiredArgsConstructor
3
@RequestMapping("api/expense")
4
public class ExpenseController {
5
 
          
6
    private final ExpenseService expenseService;
7
    ...
8
    // other injected classes
9
 
          
10
    @PostMapping(value = "")
11
    @ResponseStatus(value = HttpStatus.OK)
12
    public void addNewExpense(@RequestBody @Valid ExpenseDTO expenseDTO) {
13
        expenseService.addNewExpense(expenseDTO);
14
    }
15
    ...
16
    // other methods
17
 
          
18
}
19
 
          
20
@Service
21
@RequiredArgsConstructor
22
public class ExpenseBOImpl implements ExpenseBO {
23
 
          
24
    private final ExpenseRepo expenseRepo;
25
    private final AuthenticationBO authenticationBO;
26
    ...
27
    // other injected classes
28
 
          
29
    @Override
30
    @Transactional
31
    public void createExpense(ExpenseDTO expenseDTO) throws ValidationException {
32
        validateExpense(expenseDTO);
33
        ExpenseEntity expenseEntity = new ExpenseEntity();
34
        initExpenseEntity(expenseEntity, expenseDTO);
35
        expenseRepo.save(expenseEntity);
36
    }
37
    ...
38
    // other methods
39
 
          
40
    private void initExpenseEntity(ExpenseEntity expenseEntity, ExpenseDTO expenseDTO) {
41
        UserEntity userEntity = authenticationBO.getLoggedUser();
42
 
          
43
        expenseEntity.setUser(userEntity);
44
        expenseEntity.setPrice(expenseDTO.getPrice());
45
        expenseEntity.setComment(expenseDTO.getComment());
46
        expenseEntity.setDate(LocalDateTime.now());
47
        initExpenseTypes(expenseEntity, expenseDTO.getTypes());
48
    }
49
    ...
50
    // other methods
51
}


 

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:

Java
 




xxxxxxxxxx
1
31


1
@Data
2
@Entity
3
@Table(name = "expense", schema = "expenses")
4
public class ExpenseEntity {
5
 
          
6
    @Id
7
    @GeneratedValue(strategy = GenerationType.IDENTITY)
8
    @Column(name = "id")
9
    private Integer id;
10
 
          
11
    @Column(name = "price")
12
    private Integer price;
13
 
          
14
    @Column(name = "comment")
15
    private String comment;
16
 
          
17
    @Column(name = "date")
18
    private LocalDateTime date;
19
    
20
    @ManyToOne(fetch = FetchType.LAZY)
21
    @JoinColumn(name = "user_id")
22
    private UserEntity user;
23
 
          
24
    @ManyToMany(fetch = FetchType.LAZY)
25
    @JoinTable(name = "expense_to_expense_type_dict",
26
            joinColumns = @JoinColumn(name = "expense_id"),
27
            inverseJoinColumns = @JoinColumn(name = "expense_type_id"))
28
    @Fetch(FetchMode.SUBSELECT)
29
    private List<ExpenseTypeDictEntity> expenseTypeDict;
30
 
          
31
}



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:

Java
 




xxxxxxxxxx
1


1
// A can create B
2
class A {
3
    B b;
4
}
5
 
          
6
// B can't create A
7
class B {
8
    
9
}



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:

Java
 




xxxxxxxxxx
1
22


 
1
@Service
2
public class ExpenseServiceImpl implements ExpenseService {
3
 
          
4
    @Autowired
5
    ...
6
    // services and infrastructure
7
 
          
8
 
          
9
    @Override
10
    @Transactional
11
    public void addNewExpense(ExpenseDTO expenseDTO) {
12
        ExpenseEntity expenseEntity = new ExpenseEntity();
13
        businessLogicMethod(expenseEntity);
14
    }
15
 
          
16
    private void businessLogicMethod(ExpenseEntity expenseEntity) {
17
        // do nothing with expenseEntity
18
        // violates the principle of encapsulation, GRASP patterns
19
        anotherBusinessLogicMethod(expenseEntity); // only next method needs expenseEntity 
20
    }
21
 
          
22
}


 

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:

Java
 




xxxxxxxxxx
1
32


1
// Before
2
 
          
3
@Override
4
@Transactional
5
public void createExpense(ExpenseDTO expenseDTO) {
6
    ExpenseEntity expenseEntity = new ExpenseEntity();
7
    expenseEntity.setUser(userEntity);
8
    expenseEntity.setPrice(expenseDTO.getPrice());
9
    expenseEntity.setComment(expenseDTO.getComment());
10
    ...
11
    // other setters
12
    
13
    expenseRepo.save(expenseEntity);
14
}
15
 
          
16
 
          
17
// After
18
 
          
19
@Override
20
@Transactional
21
public void createExpense(ExpenseDTO expenseDTO) {
22
    ExpenseEntity expenseEntity = createExpenseEntity(expenseDTO);
23
    expenseRepo.save(expenseEntity);
24
}
25
 
          
26
private createInitializedExpenseEntity(expenseDTO) {
27
    ExpenseEntity expenseEntity = new ExpenseEntity();
28
    expenseEntity.setUser(userEntity);
29
    expenseEntity.setPrice(expenseDTO.getPrice());
30
    ...
31
    // other setters
32
}


 

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:

Java
 




xxxxxxxxxx
1
52


1
// Before
2
 
          
3
class ExpenseEntity {
4
  // ...
5
  Integer getPrice() {
6
    switch (type) {
7
      case BASIC:
8
        return getBasicPrice();
9
      case SALE:
10
        return getBasicPrice() * calculateCasualDiscount();
11
      case WHOLESALE:
12
        return getBasicPrice() * getAmount() * calculateWholesaleDiscount();
13
    }
14
    throw new RuntimeException("Should be unreachable");
15
  }
16
}
17
 
          
18
 
          
19
// After
20
 
          
21
// contains all shared fields and methods
22
abstract class ExpenseEntity {
23
  // ...
24
  public abstract Integer getPrice();
25
  protected Integer getBasicPrice() {
26
    // get the basic price
27
  }
28
}
29
 
          
30
// Casual expense, which does not have any specific fields or methods
31
class BasicExpenseEntity extends ExpenseEntity {
32
  @Override
33
  public Integer getPrice() {
34
    // get the price for a casual expense
35
  }
36
}
37
 
          
38
// Expense that user made with the discount
39
class SaleExpenseEntity extends ExpenseEntity {
40
  @Override
41
  public Integer getPrice() {
42
    // get price with the discount
43
  }
44
}
45
 
          
46
// Goods that user has bought in large amount
47
class WholesaleExpenseEntity extends ExpenseEntity {
48
  @Override
49
  public Integer getPrice() {
50
    // get the wholesale price
51
  }
52
}


 

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:

Java
 




xxxxxxxxxx
1
64


 
1
@Entity
2
public class ExpenseEntity {
3
  @Id
4
  @GeneratedValue(strategy = GenerationType.IDENTITY)
5
  @Column(name = "id")
6
  private Integer id;
7
 
8
  @Column(name = "price")
9
  private Integer price;
10
  ...
11
 
12
 
13
  @Autowired // model has dependency from other layers and from infrastructure
14
  private final ExpenseRepo expenseRepo;
15
  @Autowired
16
  private final AuthenticationBO authenticationBO;
17
  @Autowired
18
  private final UserRepo userRepo;
19
  ...
20
 
21
 
22
    @Transactional
23
    public void createExpense(ExpenseDTO expenseDTO) {
24
        // model does too much!
25
        validateExpense(expenseDTO);
26
        ExpenseEntity expenseEntity = new ExpenseEntity();
27
        initExpenseEntity(expenseEntity, expenseDTO);
28
        expenseRepo.save(expenseEntity);
29
    }
30
 
31
    private void validateExpense(ExpenseDTO expenseDTO) {
32
        ...
33
    }
34
 
35
    private void initExpenseEntity(ExpenseEntity expenseEntity, ExpenseDTO expenseDTO) {
36
        UserEntity userEntity = authenticationBO.getLoggedUser();
37
 
38
        expenseEntity.setUser(userEntity);
39
        expenseEntity.setPrice(expenseDTO.getPrice());
40
        expenseEntity.setComment(expenseDTO.getComment());
41
        expenseEntity.setDate(LocalDateTime.now());
42
        initExpenseTypes(expenseEntity, expenseDTO.getTypes());
43
    }
44
 
45
    private void initExpenseTypes(ExpenseEntity expenseEntity, List<String> types) {
46
        // model persists to a DB other objects!
47
        List<ExpenseTypeDictEntity> expenseTypes = new ArrayList<>();
48
 
49
        types.forEach(x -> {
50
            ExpenseTypeDictEntity typeDict = expenseTypeDictRepo
51
                    .findByNameIgnoreCase(x.trim())
52
                    .orElseGet(() -> createExpenseTypeDict(x));
53
            typeDict.setUsedCount(typeDict.getUsedCount() + 1);
54
            typeDict = expenseTypeDictRepo.save(typeDict);
55
            expenseTypes.add(typeDict);
56
        });
57
 
58
        expenseEntity.setExpenseTypeDict(expenseTypes);
59
    }
60
 
61
    private ExpenseTypeDictEntity createExpenseTypeDict(String name) {
62
        ...
63
    }
64
}



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.

Part 2 continued here.

Topics:
anemic objects ,archiecture ,java ,layered architecture ,mvc ,rich domain objects ,spring

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}