Anemic objects vs rich domain objects

Anemic Domain Model vs. Rich Domain Model | by Matthias Schenk | Medium

  • There are mainly 3 arguments against the anemic domain model approach, which leads to recommending the rich domain model.
    • Separation of data and behavior leads to lack of discoverability. This means when looking on the domain model it is not clear which functionality it provides, because this may be distributed over different services/components.
    • The lack of discoverability itself leads to risk of duplication. The functionality is spread across multiple places and therefore existing methods are overlooked and again implemented (in the worst case in a slightly different version).
    • Missing encapsulation. The service working on the domain model needs to know about internal of the domain model in order to implement the use-cases.
  • https://github.com/PoisonedYouth/kotlin-domain-driven-design
  • Not convincing everyone, e.g. this comment:

Quote

Separating the domain this way makes no sense at all. If your infra layer depends on the domain knowledge, your infra is part of your domain, so why would you split it in the first place?

Review

His arguments are not quite convincing and some comments (maybe pro-anemic?) also questions the fact that this approach pollutes the domain layer with infrastructure stuff.

Anemic vs. Rich Domain Objects—Finding the Balance - DZone

  • We need Services/Managers to orchestrate our domain objects, which involves asking DAO layers to fetch us the external state of our domain data so we can have their representation in JVM memory as domain objects; then our services/managers can ask these domain objects the right questions to provide input into other DAOs and services that are either internal or external. Services/Managers are orchestrates, not domain experts.
  • Domain Objects should only be responsible for mutation and providing all behavior related to their own in hosting JVM memory state.
  • These are different responsibilities, and this argument should never have existed in the first place. My opinion of Martin’s article is that it serves as a reminder that the responsibilities of Domain Objects and Services/Managers are different, and each should play its own role to the full.

Review

This is in phase with what I implemented in my projects.

Anemic Domain Model vs Rich Domain Model with Examples

One of the property of OOP is to group data and behavior together, and hide the data from the outside of the object. This is known as encapsulation and it’s one the most important feature OOP languages make available to us, developers.

By using the Anemic Domain Model, you break encapsulation. Even if you have classes and instances of them, your code become procedural.

In short if you use the Anemic Domain Model:

  • It’s more difficult to have a mental model of your application: should I use the domain object directly? Should I use the domain object via a service? Where do we use the first solution? The second?
  • It’s easy to have domain objects with inconsistent states. A shipment without warehouse, for example.
  • Since the data of the domain objects can be used everywhere, there are no central place where you can look how your data is mutated.

One comment that highlights what I think:

Quote

The only real con that I see in an Anemic model and this is really the only argument, is that a Rich Model can protect the state of the object from bad code outside of the entity, accidently corrupting the entity’s state. In other words, if you pass the entity to a Domain Service and the service mucks around with the setter properties of the model, then it can accidently violate the state of the model. However, what stops the Rich Entity itself from not screwing up the state of the entity as well? Domain business logic vs. Entity business logic is all the same as long as they are kept close to each other and within the same Bounded Context.

How to avoid the anemic domain model with Spring and Hibernate | by nikosk | Medium

Review

Not a good article. The author suggest the anemic approach is suggested / recommended because of spring framework. Their alternative solution is not convincing enough and looks more complex (the comments are also telling this).

Anemic vs. Rich Domain Objects | Baeldung

Review

There’s not much on this article. Only suggesting alternatives to getters and setters, which I concur totally. However, there’s nothing about all the infrastructure accesses, etc…

Rich Domain Model with Hibernate - GeeksforGeeks

Review

Sample that uses spring-jpa to manipulate the entities, with some “complex” methods on the entity. However, the entity still has some getters and setters. There’s no “service” object in their example, so some logic are then present in the controller… Not much to say about this example…

Anemic Domain Model vs Rich Domain Model in Java | Devdiaries

Review

Same as the other examples: moving the service logic into the entities. Some logic can be completely moved to the entities, which I concur totally. Moreover, I’m also all for no getter setters. However, no concrete / real world example with API and SPI / infrastructure… So I still can’t know how this rich domain model approach tackle this issue.

Anemic vs. Rich Domain Objects: A Comparison - Techkluster

The choice between Anemic Domain Model and Rich Domain Model depends on various factors such as project complexity, team expertise, and specific domain requirements. Anemic Domain Model offers simplicity and separation of concerns, while Rich Domain Model emphasizes encapsulating behavior within domain objects and improved domain understanding.

Review

Good conclusion. The rich domain model is closer to DDD than the anemic approach. However, no concrete example / mitigation on how to orchestrate all the layers.

Rich Domain Model with Spring Boot and Hibernate - DEV Community

Anemic Domain Model requires the service layer to possess all business logic. While entities act as dummy data structures. But entities are not static. They develop during the time. We may add some fields and delete others. Or we can combine existing fields in Embeddable object.

Here, services have to know every minor detail of the entity they are working with. Because any operation may require access to different fields. Meaning that even a slight change in an entity may lead to major restructuring in many services. Actually, that breaks Open-Closed principle. The code becomes not object-oriented but procedural. We don’t use benefits of OOP paradigm. Instead, we bring additional difficulties.

  • No-args constructor allows inconsistent object instantiation.
  • Setters break encapsulation.
  • Getters break encapsulation.

Is rich domain model always worth it? There is no ultimate decision in software development. Every approach is just a compromise. Rich Domain Model pattern is no exception.

Review

The author is using some dump service objects that leverage the dependency injection (injecting the persistence layer objects) as well as the transactional aspect. This document really highlights how rich domain models coupled with persistence annotations can be used. The author still stay pragmatic and does not swear completely on the rich domain object. For example, to check if the tamagotchi (example used in their article) name exists, this check is performed by the service object and not by the entity, because it’s way more performant.

TECH | Stop using JPA/Hibernate · Blog de Laurent Stemmer

Review

I share the author’s view to avoid any infrastructure pollution in the domain layer. He introduces an additional abstraction layer BankAccounts instead of BankRepository, as it adds more semantic. This BankAccounts is an interface where the implementation injects the JPA instance, so it’s used as a proxy. I’m also in line of all the other points.

Hexagonal Architecture with Java and Spring

Review

The author is using the same approach as I do when designing hexagonal architecture.

My own understanding

  • A mutable aggregate root where it will have complex methods to interact with it.
    • Mutable or immutable object is debatable.
    • The definition of an entity where the equality is not based on its fields is appealing / understanding.
  • May contain or not the javax.persistence annotations.
  • If needed, also contain the ports, which will be injected by the repositories.
  • The controller calls directly the repositories and have the aggregate perform the logic operations.
  • If some logic is complex, we can use a service object.

Attempts

I tried to migrate an existing project using rich domain objects.

Here are the issue I encountered:

  • 🚧 how can we add the access control?
    • I added the class that checks the authorization directly in the model by injecting from the repository/mapper… Then call it when interacting with the entity. Not sure if it’s the right move.
    • How about when fetching one element of the aggregate? When/Where should we perform the check?
  • 🚧 how can we add transactional aspect?
    • maybe directly in the controller?
  • ✅ I have an entity which has a @OneToMany relationship with another entity, and I’m not manipulating the whole entity like some examples above, but using immutable objects. When I tried to add another element in the list and save the aggregate root, I got the following error:
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find FoobarEntity with id ce1ad46c-95fb-4f04-a6e2-0c5aa45e795b
	at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:376)
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:246)
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:550)
	at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
...

This article suggests adding the property @OneToMany(cascade = CascadeType.PERSIST), but I still get the same error.

In fact, this stackoverflow answer suggests using @OneToMany(cascade = CascadeType.ALL) instead, and it’s working!

Resources