Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 403
Beep beep Avatar asked Dec 19 '09 16:12

Beep beep


People also ask

What is an anemic domain model?

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.

Why are anemic domain model considered an anti-pattern?

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.

What is the main purpose for doing a domain model?

Domain Modeling is a way to describe and model real world entities and the relationships between them, which collectively describe the problem domain space.

When to use a domain Service?

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.


3 Answers

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)
like image 81
Vijay Patel Avatar answered Oct 17 '22 13:10

Vijay Patel


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);
}
like image 42
Mark Seemann Avatar answered Oct 17 '22 11:10

Mark Seemann


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.

like image 5
Jeremy McGee Avatar answered Oct 17 '22 12:10

Jeremy McGee