我正在开发一个我们尝试使用域模型的应用程序。我们的想法是将业务逻辑保留在域模型中的对象中。现在很多是由订阅相关对象的对象完成的,以对它们的变化作出反应。这是通过PropertyChanged和CollectionChanged完成的。这项工作正常,除了以下内容:
复杂的操作:应该将大量更改作为一个组进行处理(而不是单个属性/集合更改)。我应该如何'建立'交易?
持久性:我使用NHibernate来保持持久性,这也使用类的公共属性设置器。当NHibernate命中属性时,会完成许多商业逻辑(这似乎是不必要的)。我应该为NHibernate使用自定义设置器吗?
似乎推动域模型中的所有逻辑使得域模型相当复杂。任何想法???
这是一个“样本”问题(对于我使用的糟糕工具而言很抱歉):
你可以看到项目我的容器和它下面的对象通过订阅相互作用。现在,通过NetworkEditor完成对网络的更改,但此编辑器不了解NetworkData。这些数据有时甚至可能在另一个程序集中定义。该流程来自用户 - > NetworkEditor-> Network-> NetworkData以及所有其他感兴趣的对象。这似乎没有规模。
答案 0 :(得分:2)
我担心DDD和PropertyChanged / CollactionChanged事件的组合现在可能是最好的主意。问题是,如果你围绕这些事件建立逻辑,那么管理复杂性是非常困难的,因为一个PropertyChanged导致另一个和另一个很快就会失去控制。
ProportyChanged事件和DDD不完全适合的另一个原因是在DDD中,每个业务操作都应尽可能明确。请记住,DDD应该将技术内容带入商业世界,而不是相反。并且基于PropertyChanged / CollectionChanged似乎不太明确。
在DDD中,主要目标是保持聚合内部的一致性,换句话说,您需要以这种方式对聚合进行建模,即调用聚合的任何操作都是有效且一致的(如果操作当然成功)。
如果您正确构建模型,则无需担心“构建”事务 - 聚合操作本身应该是一个事务。
我不知道你的模型是怎样的,但是你可以考虑在聚合树中将职责提升一级,很可能在这个过程中添加额外的逻辑实体,而不是依赖于PropertyChanged事件。 / p>
示例:
让我们假设您有一系列具有状态的付款,每当付款发生变化时,您都希望重新计算客户订单的总余额。您可以执行以下操作,而不是在集合更改时订阅对付款集合的更改并在客户上调用方法,您可以执行以下操作:
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不会直接在网络上运行,而是使用NetworkId通过Project运行。