2011-01-10 106 views
11

经过大量的阅读和思考发生,因为我开始我的头缠着DDD,我有点困惑了聚合下复杂的层次处理的最佳实践根。我认为这是一个常见问题,但在阅读了无数的例子和讨论之后,没有人会谈论我所看到的问题。请说明如何创建/更新对一个聚合根的子实体

如果我与DDD思想一致,总根下的实体应该是一成不变的。这是我麻烦的症结,所以如果那是不正确的,那就是我迷失的原因。

这是一个捏造的例子......希望它拥有足够的水来讨论。

考虑汽车保险单(我没有保险,但这个手机上,当我听到的语言相匹配瓦特/我的保险公司)。

政策显然是一个实体。在政策中,假设我们有Auto。自动,为了这个例子,只存在于一个策略中(也许你可以将一个自动转移到另一个策略,所以这也是一个聚合的潜力,它改变了策略......但假设它比现在简单) 。由于Auto没有策略就无法存在,我认为它应该是一个实体而不是根。所以在这种情况下的策略是一个聚合根。

现在,为了创建策略,让我们假设它必须至少有一个auto。这是我感到沮丧的地方。假设汽车相当复杂,包括许多领域,也可能是一个孩子在哪里被保存(一个位置)。如果我理解正确,“创建策略”构造函数/工厂必须将输入作为自动或通过构建器进行限制,以便在没有此自动构建器的情况下不会创建。而汽车的创造,因为它是一个实体,不能事先做(因为它是不可变的,也许这只是一个不正确的解释)。所以你不必说新的Auto,然后setX,setY,add(Z)。

如果汽车超过有点微不足道,你最终不得不建立建设者一个巨大的层次结构,这样,试图管理政策的框架内建立一个汽车。

还有一个转折,这是后来,在创建策略后,一个希望添加其他自动...或更新现有的汽车。很显然,策略控制着这个......很好......但是Policy.addAuto()不会飞得太高,因为不能只传递一个新的Auto(right !?)。示例中提到了诸如Policy.addAuto(VIN,make,model等),但都很简单,看起来很合理。但是,如果这种工厂方法崩溃的参数太多(整个自动接口,可以想象)我需要一个解决方案。

从我的思想这一点上,我意识到,有一个短暂的参考实体的确定。所以,也许这是很好有一个短暂的环境聚集内其父之外创建一个实体,所以也许它是确定这样说:

自动= AutoFactory.createAuto(); auto.setX auto.setY

,或者坚持不变性,AutoBuilder.new()。setX的()。塞蒂()建()

再有,当你说政策得到它整理出来.addAuto(自动)

这个保险例如,如果您添加活动,如用其PolicyReports或RepairEstimates ...一定的价值,但对象是所有的政策外的真正意义的大多数实体事故变得更有趣......在至少我的简单例子。

策略的生命周期随着时间的推移逐渐增长,似乎是我真正开始挖掘之前必须绘制的基本图景......它更多的是工厂概念或者子实体如何构建/附加到聚合根我还没有看到一个坚实的例子。

我想我很近。希望这是明确的,而不仅仅是一个重复的常见问题解答,它可以解答所有问题。

+1

基督徒,我认为聚合的主要问题是有界的一致性,或者我会说的事务一致性。问这个问题。您需要制定政策的实际最低限度信息是什么?只是创造。所有你可能需要的东西很少,以使其“有效”。然后,你的ASSIGN,它是一个汽车,(政策不建立AUTOS)。得到它了?我为一家保险公司工作。我不会太担心政策。它首先从一个报价开始,然后是一个领先。因此,您所要做的就是通过从收集的数据中推断出身份,将所有信息“绑定”到某项政策。 – 2012-12-17 18:16:32

回答

1

聚合根下的实体应该是不可变的。

不是。值对象应该是不可变的。 实体可以更改其状态

只需要确保你做正确的封装:

  • 实体修改自己
  • 实体通过聚合根只

但Policy.addAuto(修改)不会相当飞,因为一个不能只是通过一个新的汽车(右!?)

通常应该是这样的。问题是自动创建任务可能会变得太大。如果你很幸运,并且知道实体可以被修改,能够平滑地将它分成更小的任务,如SpecifyEngine,问题就解决了。


然而,“现实世界”不工作的方式,我觉得你的痛苦。如果用户上传18张excel表格,就会遇到长时间的数据加载(还有一些奇怪的规则 - 无论数据是如何失效,它都应该“导入”(就像我说的 - 就像说true == false一样))。这个上传过程被认为是一个原子操作。

我在这种情况下怎么办?

首先 - 我的Excel文档对象模型,映射(例如Customer.Name ==第1张,“C24”),并填补DOM的读者。这些东西生活在远离基础设施的地方。

下一件事 - 实体和我的域名看起来类似于DOM dto`s值对象,但只有我感兴趣的,在适当的数据类型,并根据验证投影。 +我在我的领域模型中有1:1的关联,将脏乱隔离(幸运的是,它适合于无处不在的语言)。

武装与 - 还是有一个棘手的部分左 - Excel的DOM之间的映射DTO的域对象。这就是我牺牲封装的地方 - 我用外部的价值对象来构造实体。我的思维过程很简单 - 过度曝光的实体无论如何都不能坚持,并且仍然可以通过构造函数强制验证。它位于聚合根下面。

基本上 - 这是你不能从CRUDyness失控的部分。
有时应用程序只是编辑一堆数据。

P.s.我不确定我是否做对了。我很可能错过了这个问题上的一些重要事情。希望其他回答者会有一些见解。我的回答

+0

感谢您的回答。 – christian 2011-01-11 12:17:07

+0

关于实体的不变性,我的意思是来自外部,所以我们在那里。我发现原子性可以被分解,并可能在某个地方得到,但如果这不可能发生(像你的例子,但即使在我的政策示例中),看起来像建议是复杂的工厂......最终会产生回应所有您的域的可修改状态或必须映射DTO类型的东西。也许别人会摆脱一些额外的光。 – christian 2011-01-11 12:24:50

0

部分似乎是在这些职位被捕获:

Domain Driven Design - Parent child relation pattern - Specification pattern

Best practice for Handling NHibernate parent-child collections

how should i add an object into a collection maintained by aggregate root

总结:

它是确定之外创建一个实体如果它可以管理自己的一致性,那么它是合计的(你仍然可以使用a工厂)。因此,暂时引用自动即可,然后新的策略(自动)就是如何将其引入聚合中。这将意味着构建“临时”图表以获得详细信息(不是所有的工作方法或构造函数都被堆砌)。

我看到我的替代品之一:

(一)建立一个DTO或其他贫血图形第一,然后将它传递到工厂来获得建总。

喜欢的东西:

autoDto = new AutoDto(); 
autoDto.setVin(..); 
autoDto.setEtc... 
autoDto.setGaragedLocation(new Location(..)); 
autoDto.addDriver(...); 
Policy policy = PolicyFactory.getInstance().createPolicy(x, y, autoDto); 
auto1Dto... 
policy.addAuto(auto1Dto); 

(B)使用制造商(潜在的化合物):

builder = PolicyBuilder.newInstance(); 
builder = builder.setX(..).setY(..); 
builder = builder.addAuto(vin, new Driver()).setGaragedLocation(new Location()); 
Policy = builder.build(); 
// and how would update work if have to protect the creation of Auto instances? 
auto1 = AutoBuilder.newInstance(policy, vin, new Driver()).build(); 
policy.addAuto(auto1); 

由于这件事情的曲折一圈又一圈,有两件事情似乎是清楚的。

在通用语言的精神,这是有道理的,能够说:

policy.addAuto

policy.updateAuto

的争论这些,以及如何聚合和实体创建语义的管理不是很清楚,但必须查看一个工厂来了解该域名似乎有点被迫。

即使政策是一个聚合和管理的东西是如何结合在一起它下面,关于汽车的外观看起来怎样的规则属于自动或出厂(有一些例外,其中政策参与)。

由于政策是没有最低限度构建一套儿童无效,那些孩子需要被它的创建之前或之内创建。

而最后的声明是关键。大多数情况下,这些帖子看起来像创建独立的事务,然后粘贴它们。纯粹的DDD方法似乎认为,政策必须创造汽车,但在非平凡的情况下,这种旋转的细节疯狂失控。

+0

我想说,非微不足道的情况是由于您没有创建自动聚合根引起的。一个实体必须非常微不足道,因为它不是一个聚合本身。如果Auto需要自行创建,那么它就是自己创建的,而不是Policy的一部分。如果自动仅仅是政策关心的一些信息,并且政策对此负责,那么让策略创建并更新并删除它是有意义的。 – 2014-05-02 20:02:36

13

骨料罗茨为事务一致性的目的存在。

从技术上讲,所有你必须是值对象和实体。

两者的区别在于不变性和身份。

值对象应该是不可变的,它的身份就是数据的总和。

Money // A value object 
{ 
    string Currency; 
    long Value; 
} 

如果两个Money对象具有相等的货币和相等的值,那么它们是相等的。因此,你可以在另一个概念上交换一个,就好像你有同样的金钱。

实体是一个随时间变化的对象,但其身份在其整个生命周期中是不可变的。

Person // An entity 
{ 
    PersonId Id; // An immutable Value Object storing the Person's unique identity 
    string Name; 
    string Email; 
    int Age; 
} 

那么,何时以及为什么你有聚合根?

聚合根是专门的实体,其工作是将一组领域概念归入一个事务范围,仅用于数据更改。也就是说,一个人有腿。您需要问自己,在一次交易中,腿部的变化和人员的变化是否应该组合在一起?或者我可以单独更换一个吗?

Person // An entity 
{ 
    PersonId Id; 
    string Name; 
    string Ethnicity; 
    int Age; 
    Pair<Leg> Legs; 
} 

Leg // An entity 
{ 
    LegId Id; 
    string Color; 
    HairAmount HairAmount; // none, low, medium, high, chewbacca 
    int Length; 
    int Strength; 
} 

如果Leg可以自行更改,并且Person可以自行更改,那么它们都是Aggregate Roots。如果腿不能单独改变,并且人必须始终参与交易,那么腿应该在人体内组成。在这一点上,你将不得不通过人来改变腿。

这一决定将取决于域你模拟:

也许人就是他的腿唯一的权威,他们的成长更远,更强的基于他的年龄,根据他的种族色彩的变化,等等。这些是不变式,Person将负责确保它们得到维护。如果其他人想要改变这个人的双腿,比如说你想剃腿,那么你必须让他自己刮胡子,或者暂时将它们交给你,以便你刮胡子。

或者您可能处于考古学领域。在这里你找到腿,你可以独立操纵腿。在某个时候,你可能会发现一个完整的身体,并猜测这个人是谁的历史,现在你有一个人,但是人没有发言权,你会用你发现的腿做什么,即使它被证明是他的腿。腿的颜色会根据您对其应用的复原程度或其他因素而发生变化。这些不变量将由另一个实体维护,这次它不会是Person,而可能是考古学家。

回答你的问题:

我不断听到你谈论汽车,所以这显然是您的域的一个重要概念。它是一个实体还是一个价值对象?如果汽车是带#XYZ系列的汽车,或者你只对品牌,颜色,年份,型号,品牌等感兴趣,那么这有什么关系?假设你关心的是Auto的确切身份,而不仅仅是它的功能,而不是你的域名的实体。现在,你谈论政策,一项政策规定了什么是汽车覆盖和不覆盖,这取决于汽车本身,也可能是客户,因为根据他的驾驶历史,类型和年份以及什么不是汽车他的政策可能会有所不同。

所以我已经可以设想有:

Auto : Entity, IAggregateRoot 
{ 
    AutoId Id; 
    string Serial; 
    int Year 
    colour Colour; 
    string Model 
    bool IsAtGarage 
    Garage Garage; 
} 

Customer : Entity, IAggregateRoot 
{ 
    CustomerId Id; 
    string Name; 
    DateTime DateOfBirth; 
} 

Policy : Entity, IAggregateRoot 
{ 
    string Id; 
    CustomerId customer; 
    AutoId[] autos; 
} 

Garage : IValueObject 
{ 
    string Name; 
    string Address; 
    string PhoneNumber; 
} 

现在你使它听起来的方式,你可以改变策略,而无需改变自动和客户在一起。你说的话,如果汽车在车库,或者我们从一个策略转移到另一个策略。这让我感觉自己就像是自己的聚合根,策略也是这样,客户也是这样。这是为什么?因为它听起来像是你的域名的使用,你会改变一个汽车的车库而不关心该政策会随之改变。也就是说,如果有人更改了自动的车库和IsAtGarage状态,则不必在意不更改策略。我不确定我是否清楚,您不想以非交易方式更改客户的姓名和出生日期,因为可能您更改了他的姓名,但它未能更改日期,现在您有腐败客户的出生日期与他的姓名不符。另一方面,可以在不更改策略的情况下更改Auto。因此,Auto应该不在Policy的集合中。实际上,Auto不是Policy的一部分,但只是Policy所追踪并可能使用的内容。

现在我们看到,它是完全有意义的,因为它是一个聚合根,您可以自己创建一个Auto。同样,您可以自己创建客户。当你创建一个策略时,你只需将它链接到相应的客户和他的汽车。

aCustomer = Customer.Make(...); 
anAuto = Auto.Make(...); 
anotherAuto = Auto.Make(...); 
aPolicy = Policy.Make(aCustomer, { anAuto, anotherAuto }, ...); 

现在,在我的示例中,Garage不是聚合根。这是因为,它似乎并不是该领域直接使用的东西。它始终通过自动使用。这是有道理的,保险公司不拥有车库,他们不从事车库业务。你永远不需要创建一个自己存在的Garage。在Auto上创建一个anAuto.SentToGarage(name, address, phoneNumber)方法很容易,它会创建一个车库并将其分配给Auto。你不会自己删除一个车库。您可以改为anAuto.LeftGarage()

相关问题