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

一曲冷凌霜 提交于 2019-11-29 19:38:00

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)

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.

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.

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);
   }

}

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.

The service layer.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!