如果您被迫使用Anemic域模型,那么您将业务逻辑和计算字段放在何处?

Bee*_*eep 18 c# architecture dns domain-driven-design anemic-domain-model

我们当前的O/RM工具并不真正允许丰富的域模型,因此我们不得不在各地使用贫血(DTO)实体.这工作正常,但我仍然在努力放置基于对象的基本业务逻辑和计算字段.

当前图层:

  • 介绍
  • 服务
  • 知识库
  • 数据/实体

我们的存储库层具有大多数基本的提取/验证/保存逻辑,尽管服务层执行了许多更复杂的验证和保存(因为保存操作也执行日志记录,权限检查等).问题是在哪里放置这样的代码:

Decimal CalculateTotal(LineItemEntity li)
{
  return li.Quantity * li.Price;
}
Run Code Online (Sandbox Code Playgroud)

要么

Decimal CalculateOrderTotal(OrderEntity order)
{
  Decimal orderTotal = 0;
  foreach (LineItemEntity li in order.LineItems)
  {
    orderTotal += CalculateTotal(li);
  }
  return orderTotal;
}
Run Code Online (Sandbox Code Playgroud)

有什么想法吗?

Vij*_*tel 24

让我们回到基础:

服务

服务有三种形式:域服务,应用服务基础设施服务

  • 域服务:封装不自然适合域对象的业务逻辑.在您的情况下,您的所有业务逻辑.
  • 应用程序服务:由外部使用者用于与您的系统通信
  • 基础设施服务:用于抽象技术问题(例如MSMQ,电子邮件提供商等)

知识库

这是您的数据访问和一致性检查的地方.在纯DDD中,您的Aggregate Roots将负责检查一致性(在持久化任何对象之前).在您的情况下,您将使用域服务层中的检查.


建议的解决方案:将现有服务拆分开来

使用新的域服务层来封装DTO的所有逻辑,以及您的一致性检查(使用规范,可能?).

使用Application Service公开必要的fetch方法(FetchOpenOrdersWithLines),它将请求转发到您的Repository(并使用泛型,如Jeremy建议的那样).您还可以考虑使用" 查询规范"来包装查询.

从你的仓库,使用规格在你的域服务层坚持你的对象之前检查对象的一致性等.

您可以在埃文斯的书中找到支持信息:

  • "服务和隔离域层" (第106页)
  • "规格" (第224页)
  • "查询规格" (第229页)


Mar*_*ann 11

我很想回答,但我想详细说明.总结:不要让您选择的ORM决定您如何定义域模型.

域模型的目的是成为一个丰富的面向对象的API,用于对域进行建模.要遵循真正的域驱动设计,必须定义域模型不受技术限制.

换句话说,域模型首先出现,所有特定于技术的实现随后由映射器解决,映射器在域模型和所讨论的技术之间进行映射.这通常包括两种方式:对ORM可能引入约束的数据访问层,以及UI技术强加额外要求的UI层.

如果实施距离域模型非常远,我们将讨论反腐败层.

在您的情况下,您所谓的贫血领域模型实际上是数据访问层.您最好的办法是定义存储库,以技术中立的方式模拟对您的实体的访问.

例如,让我们看一下您的订单实体.对技术不受约束的建模可能会导致我们这样的事情:

public class Order
{
    // constructors and properties

    public decimal CalculateTotal()
    {
        return (from li in this.LineItems
                select li.CalculateTotal()).Sum();
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,这是一个普通的旧CLR对象(POCO),因此不受技术限制.现在问题是你如何进出数据存储?

这应该通过抽象的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
}
Run Code Online (Sandbox Code Playgroud)

您现在可以使用您选择的ORM实现IOrderRepository.但是,某些ORM(例如Microsoft的Entity Framework)要求您从某些基类派生数据类,因此这与域对象作为POCO完全不适合.因此,需要映射.

要认识到的重要一点是,您可能拥有与您的域实体在语义上类似的强类型数据类.但是,这是一个纯粹的实现细节,所以不要对此感到困惑.从例如EntityObject 派生的Order类不是域类 - 它是一个实现细节,因此当您实现IOrderRepository时,您需要将Order Data Class映射到Order Doman类.

这可能是一项繁琐的工作,但您可以使用AutoMapper为您完成.

以下是SelectSingle方法的实现可能如下所示:

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);
}
Run Code Online (Sandbox Code Playgroud)

  • 那么最易维护的代码库是一个monlithic,紧密耦合的程序应用程序?我不同意. (19认同)
  • 严重地?我不认为最初的发布者打算将代码量乘以 3。只要模型发生变化,他就需要在至少 3 个地方更改代码(和测试)。将所有内容强行放入域模型中并不是最明智的做法。在许多情况下,不选择领域模型,因为实体需要跨远程边界持久存在。DDD 的支持者通常会说“只需创建另一个 DTO 层并映射它”,好吧,所以现在我们正在讨论 2 层映射器……这样我们就可以遵循 DDD(无论如何,这很少适用于大型项目) (2认同)
  • 认真.这不是重要的代码量,而是可维护性的程度.总是要付出代价,如果您想要遵循DDD但是拥有笨拙的持久性技术,这就是您必须支付的税.你当然有资格认为这个价格太高了,但你会留下贫血. (2认同)
  • 标记 - 您添加的图层越多,项目的可维护性就越低.添加额外的抽象层应该有一个非常好的理由,可维护性几乎绝不是它.DDD对于大多数大型企业项目来说是一个糟糕的选择,特别是那些具有交叉问题或远程需求的项目.我用DDD纯粹主义者对此进行了辩论,其答案总是"添加层"(规范层,DTO对象,服务层,转换管理层,映射类等).它失控了,使得该项目几乎无法维护IMO.我说,只需使用服务层. (2认同)

Jer*_*Gee 5

这正是服务层的用途 - 我也看到过称为BusinessLogic层的应用程序.

这些是您希望花费大部分时间进行测试的例程,如果它们位于自己的层中,那么模拟存储库层应该是直截了当的.

存储库层应尽可能通用化,因此它不适合业务逻辑,而是特定类的个体.