请说明如何针对聚合根的子实体进行创建/更新

时间:2011-01-10 17:03:39

标签: domain-driven-design

经过多次阅读和思考,我开始把头脑包裹在DDD之后,我对在聚合根下处理复杂层次结构的最佳实践感到有些困惑。我认为这是一个FAQ,但在阅读了无数的例子和讨论之后,没有人在谈论我所看到的问题。

如果我与DDD思想保持一致,那么聚合根下面的实体应该是不可变的。这是我的麻烦的关键,所以如果这不正确,那就是我迷失的原因。

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

考虑汽车保险政策(我没有保险,但这与我在保险公司打电话时听到的语言相符)。

政策显然是一个实体。在政策中,假设我们有自动。出于此示例的目的,Auto仅存在于策略中(可能您可以将Auto转移到另一个策略,因此这也可能是聚合,这会更改策略...但是假设它比现在更简单) 。由于没有策略,Auto不能存在,我认为它应该是实体而不是根。因此,在这种情况下,策略是聚合根。

现在,要创建一个策略,我们假设它必须至少有一个自动。这是我感到沮丧的地方。假设Auto相当复杂,包括许多字段,也许是一个儿童用于车库的位置(位置)。如果我理解正确,“创建策略”构造函数/工厂必须将Auto作为输入或通过构建器限制,以便在没有此Auto的情况下创建。并且Auto的创建,因为它是一个实体,不能事先完成(因为它是不可变的?也许这只是一个不正确的解释)。所以你不要说新的Auto然后setX,setY,add(Z)。

如果Auto不仅仅是微不足道的,那么您最终必须构建一个庞大的构建器层次结构,以便尝试在策略的上下文中管理创建Auto。

稍后,在创建策略并希望添加另一个Auto ...或更新现有Auto之后,还有一个问题。显然,政策控制了这个...很好......但是Policy.addAuto()不会完全飞行,因为一个人不能只传入一个新的Auto(对!!)。例子说像Policy.addAuto(VIN,make,model等),但都很简单,看起来很合理。但是如果这种工厂方法方法因参数太多而分崩离析(整个Auto接口,可以想象)我需要一个解决方案。

从我的思考中,我意识到对一个实体进行瞬态引用是可以的。所以,也许在瞬态环境中在聚合体之外创建一个实体是正常的,所以也许可以这样说:

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

或者如果坚持不变性,AutoBuilder.new()。setX()。setY()。build()

然后在你说Policy.addAuto(auto)

时将它整理出来

如果您添加事件,例如带有PolicyReports或RepairEstimates的事故......这些保险示例会更有趣......某些价值对象,但大多数实体在政策之外都毫无意义......至少对于我的简单示例。

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

我想我很亲密。希望这是明确的,而不仅仅是一个重复的常见问题解答,它在各处都有答案。

3 个答案:

答案 0 :(得分:19)

为了事务一致性,存在聚合根。

从技术上讲,你所拥有的只是价值对象和实体。

两者之间的差异是不变性和身份。

值对象应该是不可变的,它的标识是它的数据的总和。

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;
}

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

聚合根是专门的实体,其作用是将一组域概念分组到一个事务范围内,仅用于数据更改。也就是说,一个人有腿。如果Legs上的变化和Person上的更改在单个事务下组合在一起,您需要问问自己吗?或者我可以单独更改一个吗?

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。如果Leg不能单独更改,并且Person必须始终参与事务,则Leg应该在Person实体内部组成。此时,你必须通过Person来改变Leg。

此决定取决于您建模的域名:

也许这个人是他腿上的唯一权威,他们根据他的年龄变长和变强,颜色根据他的种族等变化。这些是不变量,人将负责确保他们得到维护。如果其他人想要改变这个人的腿,说你想要剃掉他的腿,你不得不要求他自己刮胡子,或者暂时把它们交给你,让你剃光。

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

回答您的问题:

我一直听到你谈论自动,所以这显然是你域名的一个重要概念。它是实体还是价值对象?如果Auto是序列号为#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;
}

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

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

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

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

答案 1 :(得分:1)

  

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

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

只需要确保你做了适当的封装:

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

但是Policy.addAuto()不会飞,因为一个人不能只传入一个新的Auto(对吧!?)

通常它应该如此。问题是自动创建任务可能会变得太大。如果您很幸运,并且知道可以修改实体,则可以将其平滑划分为较小的任务,例如SpecifyEngine,问题就解决了。


然而,“现实世界”并不是那样的,我感到痛苦。

我得到的情况是,当用户上传18个excel表格长的垃圾数据负载(带有额外的花哨规则 - 它应该“导入”无论数据无效(正如我所说 - 这就像说真的== false))。此上载过程被视为一个原子操作。

在这种情况下我做了什么......

首先 - 我有excel文档对象模型,映射(例如Customer.Name == 1st sheet,“C24”)和填充DOM的读者。那些东西住在很远的基础设施里。

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

有了这个 - 还有一个棘手的部分 - 在excel DOM dtos和域对象之间进行映射。这就是我牺牲封装的地方 - 我用外部的值对象构造实体。我的思维过程很简单 - 无论如何这个过度曝光的实体都不能被持久化,而且有效性仍然可以被迫(通过构造函数)。它位于聚合根目录下。

基本上 - 这是你无法逃离CRUDyness的部分 有时应用程序只是编辑一堆数据。

P.S。我不确定我做对了。很可能我在这个问题上错过了一些重要的东西。希望其他回答者能有一些见解。

答案 2 :(得分: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

总结:

如果可以管理自己的一致性(可能仍然使用工厂),可以在聚合之外创建一个实体。因此,对Auto进行瞬态引用是可以的,然后新的策略(Auto)是如何将其引入聚合。这意味着建立“临时”图形以使细节分散一点(并非全部堆积到一个工厂方法或构造函数中)。

我看到我的替代方案:

(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

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

即使策略是一个聚合并管理事物在其下面的组合方式,关于Auto看起来如何的规则似乎属于Auto或其工厂(涉及Policy的地方有一些例外)。

由于没有最低限度构建的子集,策略无效,因此需要在创建之前或之内创建这些子项。

最后一句话是关键。看起来大多数情况下这些帖子将子项的创建作为单独的事务处理,然后粘合它们。纯粹的DDD方法似乎认为政策必须创建Autos,但在非平凡的情况下,旋转的细节大大失控。