C# – If you are forced to use an Anemic domain model, where do you put your business logic and calculated fields

anemic-domain-modelArchitecturecdnsdomain-driven-design

Our current O/RM tool does not really allow for rich domain models, so we are forced to utilize anemic (DTO) entities everywhere. This has worked fine, but I continue to struggle with where to put basic object-based business logic and calculated fields.

Current layers:

  • Presentation
  • Service
  • Repository
  • Data/Entity

Our repository layer has most of the basic fetch/validate/save logic, although the service layer does a lot of the more complex validation & saving (since save operations also do logging, checking of permissions, etc). The problem is where to put code like this:

Decimal CalculateTotal(LineItemEntity li)
{
  return li.Quantity * li.Price;
}

or

Decimal CalculateOrderTotal(OrderEntity order)
{
  Decimal orderTotal = 0;
  foreach (LineItemEntity li in order.LineItems)
  {
    orderTotal += CalculateTotal(li);
  }
  return orderTotal;
}

Any thoughts?

Best Answer

Let's get back to basics:

Services

Services come in 3 flavours: Domain Services, Application Services, and Infrastructure Services

  • Domain Services : Encapsulates business logic that doesn't naturally fit within a domain object. In your case, all of your business logic.
  • Application Services : Used by external consumers to talk to your system
  • Infrastructure Services : Used to abstract technical concerns (e.g. MSMQ, email provider, etc)

Repository

This is where your data-access and consistency checks go. In pure DDD, your Aggregate Roots would be responsible for checking consistency (before persisting any objects). In your case, you would use checks from your Domain Services layer.


Proposed solution: Split your existing services apart

Use a new Domain Services layer to encapsulate all logic for your DTOs, and your consistency checks too (using Specifications, maybe?).

Use the Application Service to expose the necessary fetch methods (FetchOpenOrdersWithLines), which forward the requests to your Repository (and use generics, as Jeremy suggested). You might also consider using Query Specifications to wrap your queries.

From your Repository, use Specifications in your Domain Services layer to check object consistency etc before persisting your objects.

You can find supporting info in Evans' book:

  • "Services and the Isolated Domain Layer" (pg 106)
  • "Specifications" (pg 224)
  • "Query Specifications" (pg 229)
Related Topic