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