The Anemic Domain: the silent antipattern Vaughn Vernon warns us to avoid
Why a domain model without behavior betrays the essence of DDD, and how to build rich entities following the principles of Implementing Domain-Driven Design.
Why a domain model without behavior betrays the essence of DDD, and how to build rich entities following the principles of Implementing Domain-Driven Design.
In far too many enterprise projects, domain classes are little more than data structures with getters and setters. All the business logic lives in "services" that manipulate those structures from the outside. Vaughn Vernon, in his book Implementing Domain-Driven Design (2013), devotes considerable effort to explaining why this is an antipattern — and how to build the correct alternative.
The term was originally coined by Martin Fowler, but Vernon takes it up forcefully in the context of DDD. An anemic domain model is one where:
// Anemic Model — the entity is just a bag of data
public class Order {
private String id;
private String status;
private BigDecimal total;
private List<OrderLine> lines;
// Only getters and setters...
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public void setTotal(BigDecimal total) { this.total = total; }
}// The service does ALL the work
public class OrderService {
public void confirmOrder(Order order) {
if (!order.getStatus().equals("PENDING")) {
throw new IllegalStateException("Cannot confirm");
}
BigDecimal total = BigDecimal.ZERO;
for (OrderLine line : order.getLines()) {
total = total.add(line.getPrice().multiply(
BigDecimal.valueOf(line.getQuantity())));
}
order.setTotal(total);
order.
The problem seems subtle, but it runs deep: the model does not protect its own invariants. Any code with access to the setter can put the entity in an invalid state.
Vernon argues that the anemic domain violates the fundamental principle of DDD: that the domain model should be the most faithful possible representation of business knowledge. When rules are scattered across services, you lose:
Ubiquitous Language — The code no longer speaks the language of the domain. order.confirm() is far more expressive than orderService.confirmOrder(order). When a business expert says "the order is confirmed," they are describing an action of the order, not of an external service.
Encapsulation of invariants — Business rules are exposed and duplicated. If two different services need to confirm an order, both reimplement (or copy) the validation. It is only a matter of time before they diverge.
Model expressiveness — A rich model communicates intent. An anemic model requires you to read the service to understand what an entity can do.
"If the domain objects have no behavior, then you don't have a domain model — you have a data model." — Vaughn Vernon, Implementing Domain-Driven Design
Vernon proposes that entities and aggregates should encapsulate their own behavior. The entity is the guardian of its invariants:
// Rich Model — the entity protects its invariants
public class Order {
private OrderId id;
private OrderStatus status;
private Money total;
private List<OrderLine> lines;
public Order(OrderId id, List<OrderLine> lines) {
if (lines == null || lines.isEmpty()) {
throw new IllegalArgumentException(
"An order must have at least one line");
}
this.id = id;
this.lines = List.copyOf(lines);
this.status
The key differences:
confirm(), cancel()).order.confirm() is exactly what a business expert would say.Vernon does not say that services disappear. Application services still exist, but their responsibility changes radically:
public class OrderApplicationService {
private final OrderRepository repository;
private final EventPublisher events;
public void confirmOrder(ConfirmOrderCommand cmd) {
Order order = repository.findById(cmd.orderId());
order.confirm(); // The logic lives in the domain
repository.save(order);
events.publish(new OrderConfirmed(order.id()));
}
}The application service orchestrates, it does not decide. Its job is to:
All the business logic is in order.confirm(), not in the service.
Vernon describes several symptoms that betray an anemic model in a real project:
It is tempting to wonder: if it is such an obvious antipattern, why is it so common? There are several practical reasons:
Frameworks that encourage it — many ORM frameworks require empty constructors and public setters for hydration. This pushes the developer to expose all state.
Misunderstood separation — "separating logic into services" sounds like good architecture, but it confuses separation of concerns with extracting behavior from the place where it belongs.
Data model inertia — teams that design the database first tend to create entities that mirror tables, not domain concepts.
Short-term convenience — a setter is quicker to write than thinking about the correct domain method. The debt is paid later.
If you already have an anemic model in production, Vernon suggests an incremental approach:
Identify the most critical aggregate — the one where business rules are most complex and the risk of inconsistency is greatest.
Move one behavior at a time — take a method from the service and turn it into a method on the entity. Start with validations and state transitions.
Eliminate the setters — replace them with intention-revealing methods. setStatus("CONFIRMED") becomes confirm().
Protect the constructor — ensure that an instance cannot be created in an invalid state.
Adjust the tests — unit tests should now cover the entity directly. If order.confirm() throws an exception when the state is not PENDING, that is a domain test.
You do not need to rewrite the entire system at once. Every setter you remove and every domain method you create is a step toward a more expressive and robust model.
The rich model does not exist in isolation. Vernon connects it with other DDD building blocks:
Value Objects — immutable types like Money, OrderId, or EmailAddress that replace primitives and encapsulate validation. In the example above, Money knows how to add amounts and OrderId guarantees a valid format.
Aggregates — Order with its OrderLine items form an aggregate. The aggregate root (Order) is the only entry point for modifying the set. No one accesses an OrderLine directly to change its price.
Domain Events — when order.confirm() executes, it can register an OrderConfirmed event that other bounded contexts will consume. The event originates in the domain, not in the service.
The anemic domain model is comfortable at the start and costly in the long run. Vernon reminds us that DDD is not just tactical or strategic patterns — it is a way of thinking about software where the domain model is the heart of the system, not a mere data container.
If your entities only have getters and setters, you are not doing DDD. You are doing procedural programming with objects.
The next time you are about to write entity.setStatus(newStatus), ask yourself: what business operation does this change represent? And give that operation a name that a domain expert would recognize.