Domain driven design 领域驱动设计:避免贫血领域和模拟现实世界角色

Domain driven design 领域驱动设计:避免贫血领域和模拟现实世界角色,domain-driven-design,domain-model,anemic-domain-model,Domain Driven Design,Domain Model,Anemic Domain Model,我在寻找一些关于我应该如何避免贫血领域模型的建议。我们刚刚开始DDD,正在与简单设计决策的分析瘫痪作斗争。我们坚持的最新一点是某些业务逻辑所属的位置,例如,我们有一个Order对象,该对象具有Status等属性。现在,我必须执行一个类似UndoLastStatus的命令,因为有人在订单上犯了错误,这并不像更改状态那样简单,因为必须记录其他信息并更改属性。在现实世界中,这是一项纯粹的管理任务。在我看来,我有两个选择: 选项1:将该方法添加到order中,使其类似于order.UndoLastSt

我在寻找一些关于我应该如何避免贫血领域模型的建议。我们刚刚开始DDD,正在与简单设计决策的分析瘫痪作斗争。我们坚持的最新一点是某些业务逻辑所属的位置,例如,我们有一个
Order
对象,该对象具有
Status
等属性。现在,我必须执行一个类似
UndoLastStatus
的命令,因为有人在订单上犯了错误,这并不像更改
状态那样简单,因为必须记录其他信息并更改属性。在现实世界中,这是一项纯粹的管理任务。在我看来,我有两个选择:

  • 选项1:将该方法添加到order中,使其类似于
    order.UndoLastStatus()
    ,虽然这有点道理,但它并不真正反映域。另外,
    Order
    是系统中的主要对象,如果涉及Order的所有内容都放在Order类中,那么事情可能会失控

  • 选项2:创建一个
    Shop
    对象,并使用该对象具有代表不同角色的不同服务。所以我可能有
    Shop.AdminService
    Shop.DispatchService
    Shop.InventoryService
    。所以在本例中,我将使用
    Shop.AdminService.UndoLastStatus(Order)

现在第二个选择是,我们有一些更能反映领域的东西,允许开发人员与业务专家讨论实际存在的类似角色。但它也正走向贫血模型。一般来说,哪种方式更好

我将遵循这些原则。应用信息专家的设计原则,即您应该将责任分配给自然拥有完成更改所需的最多信息的类

在本例中,由于更改订单状态涉及其他实体,因此我将使这些低级域对象中的每一个都支持将更改应用于自身的方法。然后还使用一个域服务层,如选项2中所述,它抽象了整个操作,根据需要跨越多个域对象


还可以看到模式。

我认为在Order类上使用类似UndoLastStatus的方法感觉有点错误,因为它存在的原因在某种意义上超出了Order的范围。另一方面,拥有一个负责更改订单状态的方法order.ChangeStatus非常适合作为域模型。订单的状态是一个适当的域概念,应该通过订单类来更改该状态,因为它拥有与订单状态相关联的数据-订单类有责任保持自身的一致性并处于适当的状态

另一种考虑方法是,Order对象是持久化到数据库的对象,它是应用于订单的所有更改的“最后一站”。更容易从订单的角度而不是从外部组件的角度来推断订单的有效状态。这就是DDD和OOP的全部内容,使人们更容易对代码进行推理。此外,可能需要访问私有或受保护的成员才能执行状态更改,在这种情况下,让方法位于order类上是更好的选择。这就是为什么贫血的域模型不受欢迎的原因之一——它们将保持状态一致的责任从拥有的类转移出去,从而打破了封装

实现更具体的操作(如UndoLastStatus)的一种方法是创建一个公开域的OrderService,即外部组件如何在域上操作。然后可以创建一个简单的命令对象,如下所示:

class UndoLastStatusCommand {
  public Guid OrderId { get; set; }
}
OrderService将具有处理该命令的方法:

public void Process(UndoLastStatusCommand command) {
  using (var unitOfWork = UowManager.Start()) {
    var order = this.orderRepository.Get(command.OrderId);
    if (order == null)
      throw some exception

    // operate on domain to undo last status

    unitOfWork.Commit();
  }
}
因此,现在Order的域模型公开了与订单对应的所有数据和行为,但OrderService和服务层通常声明了对订单执行的不同类型的操作,并公开了域以供外部组件(如表示层)使用


还考虑了考虑贫血领域模型的概念和改进它们的方法。

< P>选项2将导致程序代码的确定。 可能更容易开发,但更难维护

在现实世界中,这是一项纯粹的管理任务

“管理”任务应该是私有的,并通过完全“域”的公共操作调用。最好-仍然使用从域驱动的易于理解的代码编写

在我看来,问题在于,
UndoLastStatus
对领域专家来说意义不大。
更有可能的是,他们正在谈论制定、取消和填写订单

沿着这些思路的东西可能更适合:

class Order{
  void CancelOrder(){
    Status=Status.Canceled;
  }
  void FillOrder(){
    if(Status==Status.Canceled)
      throw Exception();
    Status=Status.Filled;
  }
  static void Make(){
    return new Order();
  }
  void Order(){
    Status=Status.Pending;
  }
}
我个人不喜欢使用“状态”,它们会自动分享给所有使用它们的人——我认为这是错误的

所以我会有这样的东西:

class Order{
  void CancelOrder(){
    IsCanceled=true;
  }
  void FillOrder(){
    if(IsCanceled) throw Exception();
    IsFilled=true;
  }
  static Order Make(){
    return new Order();
  }
  void Order(){
    IsPending=true;
  }
}
若要在订单状态更改时更改相关内容,最好使用所谓的。
我的代码大致如下:

class Order{
  void CancelOrder(){
    IsCanceled=true;
    Raise(new Canceled(this));
  }
  //usage of nested classes for events is my homemade convention
  class Canceled:Event<Order>{
    void Canceled(Order order):base(order){}
  }     
}

class Customer{
  private void BeHappy(){
    Console.WriteLine("hooraay!");
  }
  //nb: nested class can see privates of Customer
  class OnOrderCanceled:IEventHandler<Order.Canceled>{
   void Handle(Order.Canceled e){
    //caveat: this approach needs order->customer association
    var order=e.Source;
    order.Customer.BeHappy();
   }
  }
}
但通常情况下,足够多的域本身避免了复杂性,并且如果你仔细听的话,它很容易分解。例如,您可能会从专家那里听到OrderLifeCycle、OrderHistory、OrderDescription等术语,这些术语可以作为分解的锚

注意:请记住,我对您的域名一无所知。

很可能我使用的那些动词对它来说是完全陌生的。

听起来你不是在测试中驾驶这个领域。看看他的工作,特别是他在探索性建模、时间反演和主动-被动方面的工作。

C
namespace Shopping{
 class Order{
  //association with shopping cart
  //might be vital for shopping but completely irrelevant for accounting
  ShoppingCart Cart;
 }
}
namespace Accounting{
 class Order{
  //something specific only to accounting
 }
}