我担心 DDD 和 PropertyChanged/ColactionChanged 事件的组合现在可能是最好的主意。问题是,如果您将逻辑基于这些事件,则很难管理复杂性,因为一个 PropertyChanged 会导致另一个 PropertyChanged 导致另一个又一个,并且很快您就会失去控制。
ProportyChanged 事件和 DDD 不完全匹配的另一个原因是,在 DDD 中,每个业务操作都应该尽可能明确。请记住,DDD 应该将技术内容带入商业世界,而不是相反。并且基于 PropertyChanged/CollectionChanged 似乎不是很明确。
在 DDD 中,主要目标是保持聚合内部的一致性,换句话说,您需要以这样的方式对聚合进行建模,即您调用聚合的任何操作都是有效且一致的(当然,如果操作成功的话)。
如果您正确构建模型,则无需担心“构建”事务 - 聚合操作本身应该是事务。
我不知道您的模型是什么样子,但您可能会考虑将责任在聚合树中“向上”移动一级,很可能在流程中添加其他逻辑实体,而不是依赖 PropertyChanged 事件。
例子:
假设您有一组带有状态的付款,并且每当付款发生变化时,您都想重新计算客户订单的总余额。您可以执行以下操作,而不是订阅付款集合的更改并在集合更改时调用客户的方法:
public class CustomerOrder
{
public List<Payment> Payments { get; }
public Balance BalanceForOrder { get; }
public void SetPaymentAsReceived(Guid paymentId)
{
Payments.First(p => p.PaymentId == paymentId).Status = PaymentStatus.Received;
RecalculateBalance();
}
}
您可能已经注意到,我们重新计算了单个订单的余额,而不是整个客户的余额 - 在大多数情况下,这是可以的,因为客户属于另一个聚合,并且可以在需要时简单地查询其余额。这正是显示这种“仅在聚合内的一致性”的部分——我们此时不关心任何其他聚合,我们只处理单个订单。如果这对需求不合适,那么域的建模不正确。
我的观点是,在 DDD 中,没有适用于每个场景的单一好模型——您必须了解业务如何运作才能获得成功。
如果您看一下上面的示例,您会发现没有必要“构建”事务 - 整个事务位于SetPaymentAsReceived
方法中。在大多数情况下,一个用户操作应该导致实体上的一个特定方法具有聚合 - 此方法显式与业务操作相关(当然此方法可能会调用其他方法)。
至于 DDD 中的事件,有一个领域事件的概念,但这些与 PropertyChanged/CollectionChanged 技术事件没有直接关系。领域事件表示聚合完成的业务操作(事务)。
总体而言,在域模型中推动所有逻辑似乎使域模型相当复杂
当然,它应该用于具有复杂业务逻辑的场景。但是,如果对域进行了正确建模,那么很容易管理和控制这种复杂性,这就是 DDD 的优势之一。
提供示例后添加:
好的,那么创建一个名为 Project 的聚合根怎么样 - 当您从 Repository 构建聚合根时,您可以使用 NetworkData 填充它,操作可能如下所示:
public class Project
{
protected List<Network> networks;
protected List<NetworkData> networkDatas;
public void Mutate(string someKindOfNetworkId, object someParam)
{
var network = networks.First(n => n.Id == someKindOfNetworkId);
var someResult = network.DoSomething(someParam);
networkDatas.Where(d => d.NetworkId == someKindOfNetworkId)
.ToList()
.ForEach(d => d.DoSomething(someResult, someParam));
}
}
NetworkEditor 不会直接在 Network 上操作,而是通过 Project 使用 NetworkId。