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:
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?
The anemic domain model is a programming anti-pattern where the domain objects contain little or no business logic like validations, calculations, rules, and so forth. The business logic is thus baked into the architecture of the program itself, making refactoring and maintenance more difficult and time-consuming.
An anemic model. In an anemic model you invoke a method and pass it the anemic model to bring the anemic model to a legal state. Therefore the anemic model's state management is placed outside the anemic model and this fact makes it an anti-pattern from an object oriented perspective.
Domain Modeling is a way to describe and model real world entities and the relationships between them, which collectively describe the problem domain space.
You only need a Domain Service if the piece of domain logic you are modelling does not naturally fit on any Domain Object. A common scenario for this is when the responsibility of the action does not naturally fit on any particular object or if it requires multiple Domain Objects in order to co-ordinate the action.
Let's get back to basics:
Services come in 3 flavours: Domain Services, Application Services, and Infrastructure Services
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:
I'm tempted to answer Mu, but I'd like to elaborate. In summary: Don't let your choice of ORM dictate how you define your Domain Model.
The purpose of the Domain Model is to be a rich object-oriented API that models the domain. To follow true Domain-Driven Design, the Domain Model must be defined unconstrained by technology.
In other words, the Domain Model comes first, and all technology-specific implementations are subsequently addressed by mappers that map between the Domain Model and the technology in question. This will often include both ways: to the Data Access Layer where the choice of ORM may introduce constraints, and to the UI layer where the UI technology imposes additional requirements.
If the implementation is extraordinarily far from the Domain Model, we talk about an Anti-Corruption Layer.
In your case, what you call an Anemic Domain Model is actually the Data Access Layer. Your best recourse would be to define Repositories that model access to your Entities in a technology-neutral way.
As an example, let's look at your Order Entity. Modeling an Order unconstrained by technology might lead us to something like this:
public class Order
{
// constructors and properties
public decimal CalculateTotal()
{
return (from li in this.LineItems
select li.CalculateTotal()).Sum();
}
}
Notice that this a Plain Old CLR Object ( POCO ) and is thus unconstrained by technology. Now the question is how you get this in and out of your data store?
This should be done via an abstract IOrderRepository:
public interface IOrderRepository
{
Order SelectSingle(int id);
void Insert(Order order);
void Update(Order order);
void Delete(int id);
// more, specialized methods can go here if need be
}
You can now implement IOrderRepository using your ORM of choice. However, some ORMs (such as Microsoft's Entity Framework) requires you to derive the data classes from certain base classes, so this doesn't fit at all with Domain Objects as POCOs. Therefor, mapping is required.
The important thing to realize is that you may have strongly typed data classes that semantically resemble your Domain Entities. However, this is a pure implementation detail, so don't get confused by that. An Order class that derives from e.g. EntityObject is not a Domain Class - it's an implementation detail, so when you implement IOrderRepository, you need to map the Order Data Class to the Order Doman Class.
This may be tedious work, but you can use AutoMapper to do it for you.
Here's how an implementation of the SelectSingle method might look:
public Order SelectSinge(int id)
{
var oe = (from o in this.objectContext.Orders
where o.Id == id
select o).First();
return this.mapper.Map<OrderEntity, Order>(oe);
}
This is exactly what the service layer is for - I've also seen applications where it's called the BusinessLogic layer.
These are the routines you'll want to spend most of your time testing, and if they're in their own layer then mocking out the repository layer should be straightforward.
The repository layer should be genericized as much as possible, so it's not an appropriate place for business logic that's individual to particular classes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With