领域模型应该使用事件保持一致吗?

时间:2011-08-10 06:57:19

标签: domain-driven-design domain-model

我正在开发一个我们尝试使用域模型的应用程序。我们的想法是将业务逻辑保留在域模型中的对象中。现在很多是由订阅相关对象的对象完成的,以对它们的变化作出反应。这是通过PropertyChanged和CollectionChanged完成的。这项工作正常,除了以下内容:

复杂的操作:应该将大量更改作为一个组进行处理(而不是单个属性/集合更改)。我应该如何'建立'交易?

持久性:我使用NHibernate来保持持久性,这也使用类的公共属性设置器。当NHibernate命中属性时,会完成许多商业逻辑(这似乎是不必要的)。我应该为NHibernate使用自定义设置器吗?

似乎推动域模型中的所有逻辑使得域模型相当复杂。任何想法???

这是一个“样本”问题(对于我使用的糟糕工具而言很抱歉):

enter image description here

你可以看到项目我的容器和它下面的对象通过订阅相互作用。现在,通过NetworkEditor完成对网络的更改,但此编辑器不了解NetworkData。这些数据有时甚至可能在另一个程序集中定义。该流程来自用户 - > NetworkEditor-> Network-> NetworkData以及所有其他感兴趣的对象。这似乎没有规模。

1 个答案:

答案 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运行。