4

我正在为一家寻找供应商为员工搬迁提供服务的公司开展一个项目。这些服务是搬运工不具备专业知识的事情,例如准备钢琴或运输或为贵重物品建造板条箱。

在这个域中,一个订单有 1:many Locations。

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

有一些非常简单的业务规则适用于此。这是一个示例:

  1. 当订单被搁置时,所有位置都被搁置。
  2. 如果其父订单处于暂停状态,则不能取消暂停位置。

等等。从这些规则中,对我来说很明显这形成了一个聚合根边界。因此,我有一个MyClient.Statuses.Order聚合,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 的属性)。所以现在我们终于解决了我的问题:

如何编写命令和处理程序以暂停位置?

我想保持这个东西 DRY 和面向服务以最小化耦合,但是在一个地方保持两个实体之间的父子关系真的很困难。

选项 1 - 单一位置 ID:

public class PlaceLocationOnHold_V1 {
    public readonly Guid Id;
}

public class PlaceLocationOnHold_V1Handler {
    public void Handle(PlaceLocationOnHold_V1 command) {
        // This is typically a no-no.  Should only fetch by OrderId:
        var aggregate = _repository.GetByLocationId(command.Id);  

        aggregate.PlaceLocationOnHold(command.Id);
        _repository.Save();
    }
}

选项 2 - 订单 ID 和位置 ID:

public class PlaceLocationOnHold_V2 {
    public readonly Guid OrderId; // This feels redundant
    public readonly Guid LocationId;
}

public class PlaceLocationOnHold_V2Handler {
    public void Handle(PlaceLocationOnHold_V2 command) {
        var aggregate = _repository.GetById(command.OrderId);
        aggregate.PlaceLocationOnHold(command.LocationId);
        _repository.Save();
    }
}

选项 3 - 具有封装“一个位置,属于一个订单”的类的单个参数

public class LocationIdentity {
    public Guid Id;
    public Guid OrderId;
}

public class PlaceLocationOnHold_V3 {
    public readonly LocationIdentity Location;
}

public class PlaceLocationOnHold_V3Handler {
    public void Handle(PlaceLocationOnHold_V3 command) {
        var aggregate = _repository.GetById(command.Location.OrderId);  
        aggregate.PlaceLocationOnHold(command.Location.Id);
        _repository.Save();
    }
}
4

1 回答 1

8

查看有关有效聚合设计的 Vaughn Vernon 文章。具体来说,第 2 部分- 有一些关于建模相互通信的聚合的好信息。

您的设计中缺少的要点是,正如您已经提到的,它们都是 AR——它们是全球可识别的。所以他们应该通过ID相互引用。订单不应包含位置的子集合。

因此,您的 Order 类将有一个 LocationIds 的集合,而您的 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,而无需通过 Order 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 提到了这一点并重申了这一点,即每笔交易只能 ALTER 一个 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;
    }
}

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

于 2012-08-07T18:48:05.310 回答