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

8

20

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?

Sized answered 19/12, 2009 at 16:33 Comment(1)
Hope it's not late :) But a not is that we'd been using this technique for ages before DDD invention: a standard Misrosoft approach with endless providers, datasources, managers, etc.etc.Kicker
C
28

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)
Colony answered 22/12, 2009 at 9:3 Comment(0)
H
11

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);
}
Hammerhead answered 25/12, 2009 at 13:4 Comment(4)
Seriously? I don't think the original poster was looking at multiplying the amount of code by a factor of 3. Any time the model changes, he would then need to change code (and tests) in at least 3 places. Forcing everything into a domain model is not the smartest thing to do. In many cases, domain models are not chosen because entities need to persist across remote boundaries. Proponents of DDD will usually say "Just create another DTO layer and map it", OK, so now we're talking about 2 layers of mappers ... simply so we can follow DDD (which rarely works well for massive projects anyway)Multiflorous
Seriously. It's not the amount of code that mattes, but the degree of maintainability. There's always a price to pay, and this is the tax you must pay if you want to follow DDD but have an unwieldy persistence technology. You are certainly entitled to think that this is too hight a price to pay, but then you will stay anemic.Hammerhead
Mark - the more layers you add, the less maintainable a project becomes. There should be a REALLY good reason for adding extra layers of abstraction, and maintainability is almost NEVER it. DDD is a poor choice for most large enterprise projects, particularly those that have cross cutting concerns or remoting requirements. I've debated this with DDD purists, whose answer is always 'add layers' (specification layer, DTO objects, service layer, tran mgmt layer, mapping classes, etc). It gets out of hand, and makes the project virtually unmaintainable IMO. I say, just use a service layer.Multiflorous
So the most maintainable code base is a monlithic, tightly coupled, procedural application? I disagree.Hammerhead
V
5

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.

Viewy answered 19/12, 2009 at 17:31 Comment(2)
Our current repository is where all of our queries reside ... i.e. FetchOrders(), FetchOrdersWithLineItems(), FetchOrdersForDate(date), etc. This makes it easy to mock the repository, but also creates a whole bunch of pass-through methods in the service layer (i.e. servicelayer.FetchOrdersForDate(date) returns repository.FetchOrdersForDate(date)).Sized
That's cool, but you might want to consider methods in the repository e.g. Fetch<Order>(), Fetch<Order, LineItem>() and suchlike. These might then be called by FetchOrders() in the service tier. I bet there's a bunch of repeated code you could lose in the repository that way.Viewy
C
4

From what you say it may be that you’re thinking too rigidly about your Service and Repository layers. It sounds like you don’t want your Presentation layer to have a direct dependency on the Repository layer and to achieve this you are duplicating methods from your Repositories (your pass-through methods) in the Service layer.

I would question that. You could relax that and allow both to be used within your Presentation layer and make your life simpler for a start. Maybe ask your self what your achieving by hiding the Repositories like that. You’re already abstracting persistence and querying IMPLEMENTATION with them. This is great and what they are designed for. It seems as though you’re trying to create a service layer that hides the fact your entities are persisted at all. I’d ask why?

As for calculating Order totals etc. Your Service layer would be the natural home. A SalesOrderCalculator class with LineTotal(LineItem lineItem) and OrderTotal(Order order) methods would be fine. You may also wish to consider creating an appropriate Factory e.g. OrderServices.CreateOrderCalculator() to switch the implementation if required (tax on order discount has country specific rules for instance). This could also form a single entry point to Order services and make finding things easy through IntelliSense.

If all this sounds unworkable it may be you need to think more deeply about what your abstractions are achieving, how they relate to each other and the Single Responsibility Principle. A Repository is an infrastructure abstraction (hiding HOW entities are saved and retrieved). Services abstract away the implementation of business actions or rules and allow a better structure for versioning or variance. They are not generally layered in the way you describe. If you have complex security rules in your Services, your Repositories may be the better home. In a typical DDD style model, Repositories, Entities, Value Objects and Services would all be used along side each other in the same layer and as part of the same model. Layers above (typically presentation) would therefore be insulated by these abstractions. Within the model the implementation of one service may use the abstraction of another. A further refinement adds rules to who holds references to which entities or value objects enforcing a more formal lifecycle context. For more information on this I would recommend studying the Eric Evans book or Domain Driven Design Quickly.

Calefactory answered 22/12, 2009 at 15:53 Comment(3)
Thanks for spending the time to write this, great info! This really all stemmed from the fact that we have a Save method in the repository, but a SaveAndValidate method in the Service layer. The developers continually use Save in the repository instead of the full SaveAndValidate, and I was tired of reviewing all their code. They were accustomed to behaviors being encapsulated in the domain objects, instead of looking through service/repository classes for the correct method.Sized
Shouldn't that be ValidateAndSave :)Bobine
Haha, already is. I got it mixed up while typing the comment =)Sized
C
4

If your ORM technology only handles DTO objects well, that doesn't mean you have to throw out rich entity objects. You can still wrap your DTO objects with entity objects:

public class MonkeyData
{
   public string Name { get; set; }
   public List<Food> FavoriteFood { get; set; }
}

public interface IMonkeyRepository
{
   Monkey GetMonkey(string name) // fetches DTO, uses entity constructor
   void SaveMonkey(Monkey monkey) // uses entity GetData(), stores DTO
}


public class Monkey
{
   private readonly MonkeyData monkeyData;

   public Monkey(MonkeyData monkeyData)
   {
      this.monkeyData = monkeyData;
   }

   public Name { get { return this.monkeyData.Name; } }

   public bool IsYummy(Food food)
   {
      return this.monkeyData.FavoriteFood.Contains(food);
   }

   public MonkeyData GetData()
   {
      // CLONE the DTO here to avoid giving write access to the
      // entity innards without business rule enforcement
      return CloneData(this.monkeyData);
   }

}
Clamant answered 22/12, 2009 at 17:29 Comment(0)
L
3

I've found Dino Esposito's new book Microsoft® .NET: Architecting Applications for the Enterprise to be a great repository of knowledge for these types of questions and issues.

Lummox answered 24/12, 2009 at 4:43 Comment(0)
P
1

The service layer.

Pointdevice answered 19/12, 2009 at 16:35 Comment(3)
Would you also put pass-through fetch routines in the service layer then? Meaning you might have a FetchOpenOrdersWithLines() in both the service and repository, but any higher layer would always call the Service layer?Sized
This is what we do. Our presentation layer which consumes the service objects do not know about the underlying Repository. It shouldn't be that much more code to wrap the fetch methods in the service layer unless you have a ton of tables. In that case I think it would be worth it to look at some code generation.Bobine
@LuckyLindy - the problem with accessing the repository layer directly is that you'll often have complex data consolidation that requires access from multiple tables or aggregate roots. If this is a rarity, just manage those rare consolidations in service classes. Otherwise, build you quick pass-through methods. It's really a matter of choice, but I don't foresee you getting much benefit from forcing your current architecture to a DDD model.Multiflorous
R
1

If you want to add a bit of behavior to your entities, but can't modify your entities, give extension methods a try. I'd only do this for simple scenarios like in your example though. Anything more complex or that coordinates between several entities and/or services, layers, or whatever should be in a domain service as suggested already.

For example (from your examples):

public static class LineItemEntityExtensions
{
  public static decimal CalculateTotal(this LineItemEntity li)
  {
    return li.Quantity * li.Price;
  }
}

public static class OrderEntityExtensions
{
  public static decimal CalculateOrderTotal(this OrderEntity order)
  {
    decimal orderTotal = 0;
    foreach (LineItemEntity li in order.LineItems)
      orderTotal += li.CalculateTotal();
    return orderTotal;
  }
}

public class SomewhereElse
{
  public void DoSomething(OrderEntity order)
  {
    decimal total = order.CalculateOrderTotal();
    ...
  }
}

If there are very few of these additions that you want, you can just put them all in a "DomainExtensions" class, but I'd otherwise suggest treating them with regular respect and keep all of an entity's extensions in one class in its own file.

FYI: The only time I've done this is when I had a L2S solution and didn't want to mess with the partials. I also didn't have many extensions because the solution was small. I like the idea of going with a full blown domain services layer better.

Reid answered 28/12, 2009 at 21:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.