2025-01-16

An anemic domain model usually does not contain methods but only getters and setters. Such models must not contain business logic rules. The Product class we had previously would look like this:

public class Product
{
    public int?
Id { get; set; }
    public required string Name { get; set; }
    public int QuantityInStock { get; set; }
}

In the preceding code, there is no method in the class anymore, only the three properties with public setters. We can also leverage a record class to add immutability to the mix. As for the logic, we must move it elsewhere, in other classes. One such pattern would be to move the logic to a service layer.A service layer in front of such an anemic model would take the input, mutate the domain object, and update the database. The difference is that the service owns the logic instead of the rich model.With the anemic model, separating the operations from the data can help us add flexibility to a system. However, enforcing the model’s state at any given time can be challenging since external actors (services) are modifying the model instead of the model managing itself.Encapsulating logic into smaller units makes it easier to manage each of them, and it is easier to inject those dependencies into the service classes than injecting them into the entities themselves. Having more smaller units of code can make a system more dreadful for a newcomer as it can be more complex to understand since it has more moving parts. On the other hand, if the system is built around well-defined abstractions, it can be easier to test each unit in isolation.However, the tests can be quite different. In the case of our rich model, we test the rules and the persistence separately. We call this persistence ignorance, which allows us to test business rules in isolation. Then we could create integration tests to cover the persistence aspect of the service layer and more unit and integration tests on the data and domain levels. With an anemic model, we test both the business rules and the persistence simultaneously with integration tests at the service layer level or test only the business rules in unit tests that mock the persistence part away. Since the model is just a data bag without logic, there is nothing to test there.All in all, if the same rigorous domain analysis process is followed, the business rules of an anemic model backed by a service layer should be as complex as a rich domain model. The biggest difference should be in which classes the methods are located.An anemic model is a good option for stateless systems, such as RESTful APIs. Since you have to recreate the model’s state for every request, an anemic model can offer you a way to independently recreate a smaller portion of the model with smaller classes optimized for each use case. Stateless systems require a more procedural type of thinking than a purely object-oriented approach, leaving the anemic models as excellent candidates for that.

I personally love anemic models behind a service layer, but some people would not agree with me. I recommend choosing what you think is best for the system you are building instead of doing something based on what someone else did in another system.

Another good tip is to let the refactoring flow top-down to the right location. For example, if you feel that a method is bound to an entity, nothing stops you from moving that piece of logic into that entity instead of a service class. If a service is more appropriate, move the logic to a service class.

Next, let’s go back to the domain layer and explore a pattern that emerged over the years to shield the domain model using a service layer, splitting the domain layer into two distinct pieces.

Leave a Reply

Your email address will not be published. Required fields are marked *