Domain driven design 如何为';在一个上下文中是聚合根,但在另一个上下文中不是?

Domain driven design 如何为';在一个上下文中是聚合根,但在另一个上下文中不是?,domain-driven-design,cqrs,command-pattern,Domain Driven Design,Cqrs,Command Pattern,我正在为一家公司做一个项目,该公司寻找供应商为员工调动提供服务。这些服务是搬运工没有专业知识去做的事情,比如准备一架钢琴,或者为运输或为贵重物品建造板条箱 在此域中,订单有1:多个位置 在移动行业中,订单经常在不断变化,直到供应商执行其要求的服务为止。因此,在我们的模型中,我们有一些适用于订单和地点的状态(例如提交、取消、暂停) 这里有一些非常简单的业务规则。以下是一个样本: 订单被搁置时,所有位置都被搁置 如果某个位置的父订单处于保留状态,则该位置无法取消保留 等等。从这些规则中,我觉得很明显

我正在为一家公司做一个项目,该公司寻找供应商为员工调动提供服务。这些服务是搬运工没有专业知识去做的事情,比如准备一架钢琴,或者为运输或为贵重物品建造板条箱

在此域中,订单有1:多个位置

在移动行业中,订单经常在不断变化,直到供应商执行其要求的服务为止。因此,在我们的模型中,我们有一些适用于订单和地点的状态(例如提交、取消、暂停)

这里有一些非常简单的业务规则。以下是一个样本:

  • 订单被搁置时,所有位置都被搁置
  • 如果某个位置的父订单处于保留状态,则该位置无法取消保留
  • 等等。从这些规则中,我觉得很明显,这形成了一个聚合根边界。因此,我有一个
    MyClient.Statuses.Order
    aggregate,其中
    Statuses
    是上下文/服务/你想叫它什么的名称:

    public class Order {
        private Guid _id;
        private OrderStatus _status;
    
        public void PlaceOnHold() {
            if (_status == OrderStatus.Cancelled)
                // throw exception
    
            _status = OrderStatus.OnHold;
            Locations.ForEach(loc => loc.PlaceOnHold());
        } 
    
        public void PlaceLocationOnHold(Guid id) {
            if (_status == OrderStatus.Cancelled)
                // throw exception
            Locations.Single(loc => loc.Id == id).PlaceOnHold();
        }
    
        // etc...
    
            private Location[] Locations;
    }
    
    internal class Location { 
        public Guid Id;
        public LocationStatus Status;
    
        public void PlaceOnHold() {
            // It's ok for a cancelled location on a non-cancelled order,
            // but a Location cannot be placed On Hold if it's Cancelled so 
            // just ignore it
            if (Status == LocationStatus.Cancelled)
                return; 
    
            Status = LocationStatus.OnHold;
        }
    }
    
    这两个对象(顺序、位置)在其他上下文中都有GUID ID(例如,对于没有状态转换的基于CRUD的属性)。现在我们终于来回答我的问题了:

    如何编写命令和处理程序以将位置置于暂挂状态? 为了最小化耦合,我想保持这件事干净利落,面向服务,但要在一个地方保持两个实体之间的父子关系确实很困难

    选项1-单个位置ID: 选项2-订单ID和位置ID: 选项3-带有封装“属于订单的位置”类的单个参数
    查看沃恩·弗农的文章。具体来说,有一些关于相互通信的聚合建模的好信息

    您的设计中缺少的主要一点是,正如您已经提到的,这两个都是AR,它们是全局可识别的。所以它们应该通过ID相互引用。Order不应该包含位置的子集合

    因此,Order类将有一个LocationID集合,而Location将有一个OrderId

    public class Order
    {
        private Guid _id;
        private OrderStatus _status;
        private Guid[] _locationIds;
        //...
    }
    
    public class Location
    {
        private Guid _id;
        private Guid _orderId;
        //...
    }
    
    一旦你正确地解决了这个问题,选项1就有意义了。由于Location本身就是一个AR,所以您可以实例化它并直接在其上调用PlaceOnHold,而无需通过订单AR

    对于一个AR中的更改逐渐进入其他AR的情况(即,搁置订单也会使所有位置都搁置),您可以使用域事件或最终一致性

    public class Order
    {
        //... private instance variables
    
        public void PlaceOnHold()
        {
            if (_status == OrderStatus.Cancelled)
              // throw exception
    
            _status == Orderstatus.OnHold;
    
            DomainEvents.Handle(new OrderPlacedOnHold(_id)); // handle this, look up the related locations and call PlaceOnHold on each of them)
        }
    }
    
    对于您可能试图移除某个位置上的保留,但订单处于保留状态使操作非法的情况,您可以在命令处理程序中实例化Order对象,并将其传递给该位置的RemoveFromHold方法。Vernon提到了这一点,并重申了一点,即仅仅因为每个事务只能更改一个AR,并不意味着不能在事务中实例化多个AR

    public class RemoveHoldFromLocation : IHandler<RemoveHoldFromLocationCommand>
    {
        public void Execute(RemoveHoldFromLocationCommand cmd)
        {
            var location = locationRepo.Get(cmd.LocationId);
            var order = orderRepo.Get(location.GetOrderId());
    
            location.RemoveHold(order.GetStatus());
        }
    }
    
    public class Location
    {
        //... private instance variables, etc.
    
        public void RemoveHold(OrderStatus orderStatus)
        {
            if (orderStatus == OrderStatus.OnHold)
                // throw Exception
    
            _status == LocationStatus.OnHold;
        }
    }
    
    公共类RemoveHoldFromLocation:IHandler
    {
    public void Execute(RemoveHoldFromLocationCommand cmd)
    {
    var location=locationRepo.Get(cmd.LocationId);
    var order=orderepo.Get(location.GetOrderId());
    location.RemoveHold(order.GetStatus());
    }
    }
    公共类位置
    {
    //…私有实例变量等。
    public void RemoveHold(OrderStatus OrderStatus)
    {
    if(orderStatus==orderStatus.OnHold)
    //抛出异常
    _status==LocationStatus.OnHold;
    }
    }
    

    这只是伪代码,所以请原谅拼写错误等。类似的代码示例在Vernon PDF中。

    位置也有子项(供应商作业),它们有自己的一致性规则(如果位置处于保留状态,则不能取消供应商作业),但我忽略了它们,因为这个问题已经是一个新问题。我同意Dan,但我还想补充一点,这里可能还有另一个实体。。顺序通常是不变的,它发生在某个时间点。。一位客户订购了一些东西,在那个时间点之前,它不是真正的订单,而是预订单之类的。。预订单是可变的,而订单是不变的。。预订单和订单的行为是不同的,预订单可能被搁置和取消,而订单本身更像是历史数据。罗杰,这个领域有点不同。订单在整个过程中都是不断变化的,因为供应商在到达住所之前并不知道他们需要为什么买单。有时会有一些惊喜,比如保险箱需要4人而不是2人。这是一个非常好的答案。我只想做一点小小的改变,你不需要整个订单。只是订单状态。
    public class Order
    {
        private Guid _id;
        private OrderStatus _status;
        private Guid[] _locationIds;
        //...
    }
    
    public class Location
    {
        private Guid _id;
        private Guid _orderId;
        //...
    }
    
    public class Order
    {
        //... private instance variables
    
        public void PlaceOnHold()
        {
            if (_status == OrderStatus.Cancelled)
              // throw exception
    
            _status == Orderstatus.OnHold;
    
            DomainEvents.Handle(new OrderPlacedOnHold(_id)); // handle this, look up the related locations and call PlaceOnHold on each of them)
        }
    }
    
    public class RemoveHoldFromLocation : IHandler<RemoveHoldFromLocationCommand>
    {
        public void Execute(RemoveHoldFromLocationCommand cmd)
        {
            var location = locationRepo.Get(cmd.LocationId);
            var order = orderRepo.Get(location.GetOrderId());
    
            location.RemoveHold(order.GetStatus());
        }
    }
    
    public class Location
    {
        //... private instance variables, etc.
    
        public void RemoveHold(OrderStatus orderStatus)
        {
            if (orderStatus == OrderStatus.OnHold)
                // throw Exception
    
            _status == LocationStatus.OnHold;
        }
    }