Skip to content

Chapter 1: The Foundational Mistake - Anemic Domain Model

Excellent! This is an advanced and highly practical topic. We will begin a new series focused on software architecture with ASP.NET Core, using the powerful patterns of Domain-Driven Design (DDD) and CQRS.

The goal of this series is not just to optimize for speed, but to build systems that are easy to maintain, easy to scale, and accurately reflect complex business logic.


The Scenario: The Anemic Domain Model

This is the most common mistake teams make when they start adopting DDD. They think they are doing DDD, but they are actually doing the opposite.

  • System: An e-commerce application.
  • Business Rule: An Order can only be canceled if it has not yet been Shipped.

The Problematic Design: Anemic Domain Model

  • What is it?: This is an anti-pattern where domain objects (Order, Product, etc.) are just passive "bags" of data. They only have public get/set properties and contain no behavior or business logic. All business logic is placed externally, in Service or Manager classes.

  • Problematic Code:

    Order.cs (just a data bag)

    csharp
    public class Order
    {
        public int Id { get; set; }
        public string Status { get; set; } // Public get; set;
        public List<OrderItem> Items { get; set; }
    }

    OrderService.cs (contains all the logic)

    csharp
    public class OrderService
    {
        private readonly IOrderRepository _repo;
        // ...
    
        public void CancelOrder(int orderId)
        {
            var order = _repo.GetById(orderId);
    
            // BUSINESS LOGIC IS OUTSIDE THE DOMAIN OBJECT
            if (order.Status != "Shipped")
            {
                order.Status = "Cancelled"; // Directly changing the property
                _repo.Save(order);
            }
            else
            {
                throw new Exception("Cannot cancel a shipped order.");
            }
        }
    }

Analysis of the Problem 🧐

  1. No Encapsulation: The Order object cannot protect its own integrity. Any other service in the system can get an Order object and arbitrarily set order.Status = "Cancelled" without checking the business rule.
  2. Fragmented Logic: To understand all the rules related to an Order, you have to find and read the code of all the Services that can modify it.
  3. This is Not DDD: A core principle of DDD is to combine data and behavior into the same object. The above approach separates them.

The Solution: Build a Rich Domain Model ✅

  • The Logic: Move all business logic and rules inside the domain object itself. The object must be responsible for its own state.

  • Optimized Code:

    Order.cs (Rich Domain Model)

    csharp
    public class Order
    {
        public int Id { get; private set; }
        public string Status { get; private set; } // Setter is private!
        // ... other properties also have private setters
    
        // Behavior and business logic are encapsulated in a method
        public void Cancel()
        {
            if (this.Status == "Shipped")
            {
                throw new InvalidOperationException("Cannot cancel a shipped order.");
            }
            if (this.Status == "Cancelled")
            {
                return; // Do nothing if already cancelled
            }
    
            this.Status = "Cancelled";
    
            // A "Domain Event" could be raised here, e.g., OrderCancelledEvent
        }
    }

    OrderApplicationService.cs (The Service now only orchestrates)

    csharp
    public class OrderApplicationService
    {
        private readonly IOrderRepository _repo;
        // ...
    
        public void CancelOrder(int orderId)
        {
            var order = _repo.GetById(orderId);
    
            // Delegate the work to the domain object
            order.Cancel();
    
            _repo.Save(order);
        }
    }

Conclusion

  • An Anemic Domain Model is a procedural style of coding, not an object-oriented one. It leads to code that is hard to maintain and understand.
  • A Rich Domain Model, where data and behavior are encapsulated together, is the foundation of true Domain-Driven Design. It creates objects that are self-validating and truly represent the business domain.