Learn how to identify the problematic anemic model and transform it into a rich model that exposes your intentions and is aligned with the principles of domain-driven design.
Domain-driven design (DDD) is an approach to creating complex software that is at the same time cohesive, efficient and easy to maintain. DDD explores several areas and relevant subjects to help developers achieve their goals when developing sustainable applications. Some of the main topics include anemic domains and their opposite, rich domains.
In this post, we will look at the concept of anemic domain and understand why this model, despite being seemingly simple, can lead to a fragile and difficult-to-maintain architecture. Next, we will see how to identify an anemic domain in practice and how to refactor it, transforming it into a rich domain, where the business rules are encapsulated in the entities themselves, making the model in accordance with the precepts of DDD.
Martin Fowler proposed the concept of an anemic domain model and is available on his blog: Anemic Domain Model—Martin Fowler.
According to Fowler, the anemic domain model is where domain objects (usually referred to as entities or model objects) contain only data (attributes) and no relevant business logic or behavior.
In a common anemic domain structure, entities are just data structures with getters and setters. As a result, business logic is spread across services, whether static or not. It is common to find service classes with names like OrderService
, CustomerService
, etc. that use entity classes only as Data Transfer Objects (DTOs), without any encapsulation or business rules.
Fowler considers an anemic domain an antipattern: a common solution to a recurring problem, but which, despite seeming useful or convenient at first, ends up bringing negative consequences or more problems than it solves. This is because it breaks object-oriented encapsulation, since the logic is not where the data is. Furthermore, an anemic domain uses a procedural style disguised as object orientation, because, despite using classes, its programming style is imperative.
An anemic domain can cause several problems that compromise the quality and maintenance of the software. The main one is the violation of encapsulation, since entities expose their data through public getters and setters, allowing any part of the system to change their state without any restrictions. Another aspect is that the use of anemic domains makes it more difficult to guarantee the consistency and integrity of business rules, since these rules are spread across external services, generally with little cohesion.
In addition, this model leads to an architecture with strong coupling between the data and the processes that manipulate it, favoring a programming style that is more procedural than object-oriented. As a result, the expressiveness of the domain is lost, making communication with business specialists difficult and compromising the effective use of ubiquitous language, which is the central principle of DDD.
Another relevant problem is the duplication of business logic, since the lack of encapsulation often leads different services to repeat validations or calculations. In the long run, this negatively affects the maintenance, testing and scalability of the application, in addition to increasing the risk of errors and inconsistencies in the data and in the behavior of the system as a whole.
Below is a representation of a common example of an anemic model:
In anemic domains, it’s common to find behaviors and business rules in classes known as “domain services” or “application services.” In the example above, the Subscription entity acts only as a data container, while all the business logic is outside of it, which breaks encapsulation and violates DDD principles, which reinforce the idea of a domain that exposes its intentions.
Thus, the service class in an anemic domain could be represented as follows:
A rich model is the opposite of an anemic model, it concentrates the business logic within the entities and value objects themselves, instead of leaving it scattered in external services or utility classes.
We can say define that a rich model has the following characteristics:
Order
object knows how to validate itself, calculate the total, add items, cancel, etc.Now that we know what an anemic model and a rich model are, let’s transform the anemic domain example explored previously. To do this, we will create a new application using the same example, only changing what needs to be changed to reflect a rich model.
❗Important: Some implementation details will not be covered in the post to keep the focus on the main topic, but you can check out the full application code in this GitHub repository: SubTrack - Source Code.
To create the base application, you can use the commands below in the terminal. This example uses .NET 9, which is the latest stable version at the time this post was written.
These commands will create an API project, some class libraries and a solution. Add the projects to the solution, and add all its dependencies.
# Create the projects
dotnet new web -o SubTrack.API
dotnet new classlib -o SubTrack.Domain
dotnet new classlib -o SubTrack.Application
dotnet new classlib -o SubTrack.Infrastructure
# Create the solution
dotnet new sln -n SubTrack
# Add all projects to the solution
dotnet sln SubTrack.sln add SubTrack.API/SubTrack.API.csproj
dotnet sln SubTrack.sln add SubTrack.Domain/SubTrack.Domain.csproj
dotnet sln SubTrack.sln add SubTrack.Application/SubTrack.Application.csproj
dotnet sln SubTrack.sln add SubTrack.Infrastructure/SubTrack.Infrastructure.csproj
# Define the references between projects (dependencies)
dotnet add SubTrack.API/SubTrack.API.csproj reference SubTrack.Application/SubTrack.Application.csproj
dotnet add SubTrack.Application/SubTrack.Application.csproj reference SubTrack.Domain/SubTrack.Domain.csproj
dotnet add SubTrack.Infrastructure/SubTrack.Infrastructure.csproj reference SubTrack.Domain/SubTrack.Domain.csproj
At the end of the post, the complete project will have the following structure:
SubTrack.sln
│
├── SubTrack.API/
│ ├── Program.cs 📌Endpoint minimal API
│
├── SubTrack.Application/
│ └── Services/
│ └── SubscriptionService.cs 📌 Application Service
│
├── SubTrack.Domain/
│ └── Entities/
│ └── Subscription.cs 📌 Domain entity
│
└── SubTrack.Infrastructure/
└── Persistence/
└── SubscriptionDbContext.cs 📌 Persistence
Open the project and let’s start creating the folders and classes.
The domain entity class will be the Subscription
class, but now it is a rich model and no longer anemic. So, in the SubTrack.Domain
project, create a new folder called “Entities” and add the following class to it:
namespace SubTrack.Domain.Entities;
public class Subscription
{
public Guid Id { get; private set; }
public string? ServiceName { get; private set; }
public DateTime StartDate { get; private set; }
public DateTime? ExpirationDate { get; private set; }
public decimal MonthlyPrice { get; private set; }
public int MonthsInPlan { get; private set; }
public bool IsCancelled { get; private set; }
private Subscription() { }
public Subscription(Guid id, string serviceName, DateTime startDate, decimal monthlyPrice, int monthsInPlan)
{
if (monthlyPrice <= 0)
throw new ArgumentException("Price must be greater than zero.", nameof(monthlyPrice));
if (monthsInPlan <= 0)
throw new ArgumentException("Months must be greater than zero.", nameof(monthsInPlan));
Id = id;
ServiceName = serviceName;
StartDate = startDate;
MonthlyPrice = monthlyPrice;
MonthsInPlan = monthsInPlan;
ExpirationDate = startDate.AddMonths(monthsInPlan);
IsCancelled = false;
}
public bool IsActive(DateTime? referenceDate = null)
{
var date = referenceDate ?? DateTime.UtcNow;
return !IsCancelled && (!ExpirationDate.HasValue || ExpirationDate > date);
}
public void ExtendPlan(int additionalMonths)
{
if (additionalMonths <= 0)
throw new ArgumentException("Months must be greater than zero.", nameof(additionalMonths));
MonthsInPlan += additionalMonths;
ExpirationDate = StartDate.AddMonths(MonthsInPlan);
}
}
Now the Subscription
class is quite different. The first thing we can notice is that the properties have private setters, which means that they are not exposed outside the domain; instead, only the domain can change their values.
💡 Declaring properties with private setters forces the domain to take responsibility for the actions that belong to it.
Secondly, we can notice that the constructor public Subscription(Guid id, string serviceName, DateTime startDate, decimal monthlyPrice, int monthsInPlan)
now has some validations, which establish certain criteria that modifications must respect. For example, preventing the creation of a subscription with zero or negative monthly value, or with an invalid number of months.
💡 It is a good practice to add validations in the class constructor, as they prevent the creation of invalid instances and unexpected behaviors.
Finally, we can notice the methods present in the class. The IsActive
method encapsulates useful domain logic that responds whether the subscription is active on a given date, respecting the cancellation status and expiration date. In addition, the ExtendPlan
method also has clear validation rules, which facilitate the evolution of the entity over time.
💡 Creating methods in domain classes makes the model more faithful to the business reality, reinforces encapsulation, maintains data integrity and makes the code clearer, more maintainable and aligned with the concept of rich models.
Even though our entity class now represents a rich model (with private setters and behaviors), we still need to have a service class—after all, some rules and behaviors do not belong to the domain. Furthermore, it is important to keep each class with its own responsibility.
So, inside the SubTrack.Application
project, create a new folder called “Services” and add the following class to it:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SubTrack.Infrastructure.Repositories;
namespace SubTrack.Domain.Services;
public class SubscriptionService
{
private readonly SubscriptionRepository _repository;
private readonly ILogger<SubscriptionService> _logger;
public SubscriptionService(SubscriptionRepository repository, ILogger<SubscriptionService> logger)
{
_repository = repository;
_logger = logger;
}
public async Task<string> ExtendSubscriptionPlan(Guid subscriptionId, int months)
{
if (months <= 0)
{
return "Months must be greater than 0.";
}
var subscription = await _repository.Subscriptions
.FirstOrDefaultAsync(s => s.Id == subscriptionId);
if (subscription is null)
{
return "Subscription not found.";
}
if (!subscription.IsActive())
{
return "Cannot extend an inactive subscription.";
}
subscription.ExtendPlan(months);
_repository.Subscriptions.Update(subscription);
await _repository.SaveChangesAsync();
return $"Subscription extended by {months} months.";
}
}
Note that the SubscriptionService
class now only has responsibilities that do not belong to the domain, such as flow control validations.
Another aspect attributed to the service class that does not belong to the model are the error messages and return strings that are part of the interface with the application. The domain model should work with specific exceptions, result types or domain events, while the handling and translation of these failures into messages for the user should be the responsibility of a higher layer.
In addition, the responsibility for accessing the repository (querying the database) does not lie with the entity, but with the application or domain service. Here, the method performs the query and validates whether the subscription exists. This type of data orchestration and retrieval logic is commonly assigned to an Application Service, not to domain entities.
💡 Keep in mind that domain entities should focus on behavior and business rules, such as encapsulating what it means to be active, how to extend the expiration date, whether extension is allowed, etc. The application service should focus on orchestrating these behaviors, validating input data and communicating results (as messages or DTOs) to the outside world.
DDD provides principles and practices that help build more sustainable systems. However, this does not mean that systems that do not follow DDD are wrong. In many scenarios, adopting rich models may be unnecessary or even inappropriate, and simpler approaches may better meet the project requirements.
In this sense, an anemic model may be more suitable in contexts that do not require great complexity. Below, we will check the main scenarios where an anemic model is acceptable or even recommended:
✅ Simple CRUD applications: When the premise is basically to create, read, update and delete data without complex business rules, an anemic model may be sufficient and faster to implement.
Example: An internal system for registering contacts where you only save and display data, with little or no validation.
✅ Prototypes or MVPs: When creating a prototype or a minimum viable product (MVP) or just validating an idea, an anemic model enables faster delivery.
✅ Database-oriented projects: Systems that follow the database-first approach, where the application directly reflects the structure of the database, usually result in anemic models. And, in these cases, this is acceptable because many business rules are present in the databases themselves.
✅ Temporary code or administrative scripts: For maintenance jobs, scripts and simple scheduled tasks, the use of rich models may be unnecessary.
Applications that use or intend to use DDD principles must have good domain entity models. To do so, it is necessary to apply some concepts, one of which is the concept of rich models.
In this post, we understood how to identify an anemic entity model, its main characteristics and what problems it can bring to projects that involve a certain level of complexity. In addition, we saw in practice how to transform an anemic model into a rich model.
So, whenever you create something that fits the characteristics discussed in the post, consider using rich models. They will certainly help you create systems that are easier to maintain and that express the intention and complexities of the domain well.