The Anemic Domain Model is an anti-pattern where domain objects are just data containers with little or no behavior, and all business logic resides in separate service layers.

What It Looks Like

Anemic Model

// Just data - no behavior
public class Order
{
    public int Id { get; set; }
    public List<OrderLine> Lines { get; set; }
    public decimal Total { get; set; }
    public string Status { get; set; }
}
 
// All logic in services
public class OrderService
{
    public void AddItem(Order order, Product product, int quantity)
    {
        order.Lines.Add(new OrderLine
        {
            ProductId = product.Id,
            Quantity = quantity,
            Price = product.Price
        });
 
        order.Total = order.Lines.Sum(l => l.Price * l.Quantity);
    }
 
    public void Submit(Order order)
    {
        if (order.Lines.Count == 0)
            throw new InvalidOperationException("Cannot submit empty order");
 
        order.Status = "Submitted";
    }
}

Rich Domain Model (Correct)

// Behavior with data
public class Order
{
    public OrderId Id { get; private set; }
    private List<OrderLine> _lines = new();
    public IReadOnlyList<OrderLine> Lines => _lines;
    public OrderStatus Status { get; private set; }
 
    public void AddItem(Product product, int quantity)
    {
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("Cannot modify submitted order");
 
        _lines.Add(new OrderLine(product, quantity));
    }
 
    public Money CalculateTotal()
    {
        return _lines.Sum(l => l.Subtotal);
    }
 
    public void Submit()
    {
        if (!_lines.Any())
            throw new InvalidOperationException("Cannot submit empty order");
 
        Status = OrderStatus.Submitted;
        DomainEvents.Raise(new OrderSubmitted(Id));
    }
}

Problems with Anemic Domain Model

Loses Benefits of OOP

  • Breaks encapsulation
  • Data and behavior separated
  • No information hiding

Scattered Business Logic

  • Logic spread across multiple services
  • Hard to find where rules are enforced
  • Duplication of validation logic

Difficult to Maintain

  • Changes require updating multiple places
  • Easy to bypass business rules
  • No single source of truth

Poor Discoverability

  • Unclear what operations are valid
  • Have to read service code to understand behavior
  • Domain model doesn’t express capabilities

Why It Happens

Procedural Programming Mindset

// Procedural thinking in OOP clothing
public class OrderProcessor
{
    public void ProcessOrder(Order order)
    {
        // All logic here, order is just data
    }
}

Over-Separation of Concerns

Misunderstanding “separate layers” as “separate behavior from data”

Database-Centric Design

Designing domain objects to match database tables

Cargo Cult Programming

Copying patterns without understanding when to use them

How to Fix It

Move Behavior to Entities

// Before: Anemic
public class Account
{
    public decimal Balance { get; set; }
}
 
public class AccountService
{
    public void Withdraw(Account account, decimal amount)
    {
        if (account.Balance < amount)
            throw new Exception("Insufficient funds");
        account.Balance -= amount;
    }
}
 
// After: Rich
public class Account
{
    public Money Balance { get; private set; }
 
    public void Withdraw(Money amount)
    {
        if (Balance < amount)
            throw new InsufficientFundsException();
 
        Balance = Balance.Subtract(amount);
        DomainEvents.Raise(new MoneyWithdrawn(Id, amount));
    }
}

Use Value Objects

// Before: Primitives
public class Product
{
    public decimal Price { get; set; }
    public string Currency { get; set; }
}
 
// After: Value Object
public class Product
{
    public Money Price { get; private set; }
 
    public void ChangePrice(Money newPrice)
    {
        if (newPrice <= Money.Zero)
            throw new ArgumentException("Price must be positive");
 
        Price = newPrice;
        DomainEvents.Raise(new PriceChanged(Id, newPrice));
    }
}

Protect Invariants

// Before: No protection
public class ShoppingCart
{
    public List<CartItem> Items { get; set; }  // Can be modified directly!
}
 
// After: Encapsulated
public class ShoppingCart
{
    private readonly List<CartItem> _items = new();
    public IReadOnlyList<CartItem> Items => _items;
 
    public void AddItem(Product product, int quantity)
    {
        if (quantity <= 0)
            throw new ArgumentException("Quantity must be positive");
 
        var existing = _items.FirstOrDefault(i => i.ProductId == product.Id);
        if (existing != null)
            existing.IncreaseQuantity(quantity);
        else
            _items.Add(new CartItem(product, quantity));
    }
}

When Services Are Appropriate

Domain services are still valid for operations that don’t belong to a single entity:

// Good: Service for cross-aggregate operation
public class MoneyTransferService
{
    public void Transfer(Account from, Account to, Money amount)
    {
        from.Withdraw(amount);  // Entity method
        to.Deposit(amount);     // Entity method
 
        DomainEvents.Raise(new MoneyTransferred(from.Id, to.Id, amount));
    }
}

Detection

Signs you have an anemic domain model:

  • Entities with mostly getters/setters
  • Service classes with names like EntityNameService
  • Public setters everywhere
  • No methods on entities (except getters/setters)
  • Complex logic in application/service layer
  • Validation in multiple places

Resources

bliki: AnemicDomainModel

References