Anemic Domain Model for Anemic Java Developers?
I like discussions about architecture and domain implementation because they often come down to the famous “it depends...”. So far in my career, I have encountered ADM on numerous occasions, which is why I would like to share my thoughts with you. Starting with Martin Flower's blogpost: Anemic Domain Model (2003) [1] where in the first sentence we read that “this is one of those anti-patterns that has been around for quite a long time, yet seems to be having a particular spurt at the moment”. This is quite a strong opening for today's times where it is still widely used in 2023.
Anemic Domain Model
An application domain model can be called Anemic when the domain models have little or no business logic. Binary logic is implemented by means of stateless so-called services, which implement state changes of domain objects. Does it sound familiar? Let's take the entity class from your project. In it, we will find nothing but getters and setters. Can you find stateless classes where the only purpose of the methods is to change the state of the object (provided by the parameter) through just these getters and setters ? Yes, you are working with the Anemic Domain Model
Why is it popular?
There are at least a few reasons for its wide application. To begin with, It is arguable that this is the simplest model for CRUD (Create, Read, Update, Delete) operations. Another of its advantages is Its ability of clearly separating between the model and the business. It also maps the database tables 1:1 so there is no need for an additional model mapping layer. It is obvious that this is being teased out for the “new super fancy groundbreaking framework” tutorial as an example. Personally, I have rarely heard voices of doubt about the model or simply thought that “this is how we do it”. I would also “blame” the market and the large number of MVPs (Minimum Value Product). The client orders an MVP to win investors by presenting the idea. After an impressive demo, the money starts flowing in and the investors expect further development and no one wants to hear “Hey, that was an MVP, so we need to implement it with a different approach and in architecture tailored to our goals”. If it looks nice and works, let's add fizz.
Object Oriented Programming
We Java programmers are object-oriented programmers; at least, that's what we think of ourselves. We use classes and objects, inheritance, polymorphism, and even encapsulate – we use private fields! But what do I find essential in OOP (Object Oriented Programming)? The methods. A method is a set of procedures that changes the internal state of an object. A method is part of the object. Methods form the so-called public interface of the object and define its behaviour. Encapsulation means that we do not have direct access to the state of the object; we have to use methods to change this state.
Functional programming
In functional programming, we focus on functions. If I were to define a pure function, it would go something like this: A function is described exactly as in mathematics – it takes one or more arguments and always returns a result. The arguments never change. For the same arguments, the result is always the same (idempotence). If the result of a function is not used, the function can be removed without affecting the operation of the system. Functions do not belong to any object, they are free existing.
Anemic Domain Model problematic
Having understood the basic differences between object-oriented and functional programming, let us discuss the service layer of many ADM applications. It is common practice to use an entity in the service layer, change its state using setters, and then execute further logic contained in the business requirements. An example requirement would be to change the status of an order and then inform the recipient by email.
OrderEntity.class:
commit, push, merge, done, coffee break.
The problematic thing about this solution is that we are still able to change the status in an entity without notification – and we should not. This is possible by simply calling order.setStatus() anytime this entity is present. This creates inconsistencies because the implementation does not reflect the business. Simply the Order object interface continues to allow a change of status without notification. This is not pure OOP.
Knowing a bit about functional programming, we will use its principles and apply a more “functional” approach.
Firstly, functions always return a value:
Great! Now let us add functionality so that an exception is thrown when the status has not been changed and remains the same.
We can now see that the references do not mean exactly what they describe in the name, because it is the same object and consequently further comparison is incorrect. We are unable to create and return a new entity to solve this problem because one entity represents exactly one entry in a database table. To solve this problem we would have to extract the status before the update and compare it with that after the update, but this is not how it should be done in the object-oriented world. This is the problem of introducing a functional approach in the OOP world.
Awareness
When working in a large system with very complex logic, it is very difficult to know all the business logic in detail, although we may know some areas really well. Service classes grow in numbers having to do with a lot of business logic. Implementation under these conditions is time-consuming and promotes defects. In my experience of being given a task in the form of a User Story, I often found the description quite abbreviated: If a payment is 3 days late, change the status of these orders to “CANCELLED”. You look at the task and implement:
commit, push, merge, done, coffee break, defects++.
This implementation does not ensure that emails are sent after a status change.
Monoliths
Addressing such challenges in maintaining business logic in stateless services, requires a lot of intra-team communication and business knowledge of the project, as the code does not reflect reality. The monolith is now the night-time fear of most developers, lurking under the bed. But, as we finally fall asleep after a hard day's work, we dream of a new clean project and how great it will be. Of course, it will start with basic entities, services, controllers because why not? It's just an MVP :) The monoliths exerted pressure to find a solution to these problems. And there are a couple of solutions.
Micro services
To liberate developers not only from the problems of ADM and large applications come microservices. In the context of architecture, I think of them as the hero we do not deserve. In my mind, they are an army of little teddy bears on our bed, defending us against the monolith hiding under our bed, while we dream in our sleep. But do they really solve the discussed problem? It certainly reduces the complexity of one project/service/application by limiting the amount of logic in services by delegating further calculations and tasks to other applications. Unfortunately, without changing our approach in terms of domain implementation and architecture, we will continue to be sweet-sleeping anaemic Java developers.
Conclusion
Okay, I have just called ourselves disorderly Anemic Programmers. What should we do to be proudly called Java Developers? Be intentional in asking your project architects why you are implementing in a certain way, what are the goals and objectives of the architecture, consult your solutions earlier than during the Coder Review. The purpose of this article is to poke you and pull you away from mechanical programming and draw your attention to the major subject of implementation architecture. At no point did I intend to tell you that you were doing something wrong. As long as you do something consciously, keep it that way, but keep the limitations of the solutions in mind. At the bottom of the article, in the “read more” section, you will find more interesting content to explore. Allow me to leave you with a motto: “Frameworks are just tools and architecture is a lifestyle”
Idea
I would like to leave you with something specific. I gather solutions from different domain implementations and architectural ideas, and match them to business requirements, team experience, costs, and whatnot. So I will propose domain objects to solve this problem and separate them by layer from entities. Entities will remain an anaemic simple table representation. And a domain object with the same name will contain the business logic. We only have one constructor, we control how the object is created – only on an entity basis; the rest of the constructors remain private. This is a so-called Rich Object because it has a logic that changes its state method: updateStatus() I gave it a slight touch of a functional style.
The feature previously implemented in the system would now look like that:
The repository never reveals the real entities; instead, they are automatically mapped to domain objects. When we change the status, we get a new domain object, so we can do comparisons and every reference corresponds to the truth; we have the object before and after the change. We then pass the object to be fixed in the database to the repository.
Further read
- Rich Domain Model – using object oriented implementation of your domain.
- Domain-Driven Design – Eric Evans.
What I used to solve some topics:
- EBC - structural pattern to package domain components
- Facade – design pattern – to define interfaces with clear definition of process using small services
- definition of domain – how to layer the application or define business logic core
- Hexagonal architecture / ports & adapters – for separation and decoupling layers