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 Solution
Let's get back to basics:
Services
Services come in 3 flavours: Domain Services, Application Services, and Infrastructure Services
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: