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

chr*_*ian 17 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的事故......某些值对象,但大多数实体在策略之外都没有任何意义,这个保险示例会变得更有趣......至少对于我的简单示例.

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

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

Did*_* A. 22

存在聚合根以实现事务一致性.

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

两者之间的区别在于不变性和身份.

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

Money // A value object
{
    string Currency;
    long Value;
}
Run Code Online (Sandbox Code Playgroud)

如果两个Money对象具有相等的货币和相等的值,则它们是相等的.因此,您可以将一个换成另一个,从概念上讲,就好像您拥有相同的Money一样.

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

Person // An entity
{
    PersonId Id; // An immutable Value Object storing the Person's unique identity
    string Name;
    string Email;
    int Age;
}
Run Code Online (Sandbox Code Playgroud)

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

聚合根是专门的实体,其作用是将一组域概念分组在一个事务范围内,仅用于数据更改.也就是说,一个人有腿.如果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;
}
Run Code Online (Sandbox Code Playgroud)

如果Leg可以自己更改,而Person可以自行更改,那么它们都是Aggregate Roots.如果Leg不能单独更改,并且Person必须始终参与事务,则Leg应该在Person实体内部组成.此时,你必须通过Person来改变Leg.

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

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

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

回答你的问题:

我一直听到你谈论Auto,所以这显然是你域名的一个重要概念.它是实体还是价值对象?如果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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

aCustomer = Customer.Make(...);
anAuto = Auto.Make(...);
anotherAuto = Auto.Make(...);
aPolicy = Policy.Make(aCustomer, { anAuto, anotherAuto }, ...);
Run Code Online (Sandbox Code Playgroud)

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

  • 也许我读得太仔细了,或者这只是关于你的措辞。你说 VO 是不可变的,Money 是 VO,但后来“如果 Money 可以改变”它可以是 AR。它不能改变,它作为一个 VO 是不可变的!VO 可以是聚合的一部分,但不能是 AR。在 DDD 的第 6 章中,Evans 说:“选择一个实体作为每个 AGGREGATE 的根......”强调没有添加。此外,由于 Aggregate 的目的是将 Aggregate 的数据更改视为一个单元,而 VO 不能更改,因此将一个作为 AR 是无意义的,至少在 IMO。 (3认同)

Arn*_*psa 1

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

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

只需确保您进行了正确的封装:

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

但是Policy.addAuto()不会完全成功,因为不能只传递一个新的Auto(对吧!?)

通常情况下应该是这样。问题是自动创建任务可能会变得太大。如果您很幸运,并且知道可以修改实体,并且能够将其平滑地划分为更小的任务,例如SpecifyEngine,问题就得到解决。


然而,“现实世界”并非如此,我感受到你的痛苦。

当用户上传 18 个 Excel 表的长垃圾数据时,我遇到了这样的情况(还有额外的奇特规则 - 无论数据有多么无效,都应该“导入”(正如我所说 - 这就像说 true==false))。这一上传过程被视为一个原子操作。

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

首先 - 我有 Excel 文档对象模型、映射(例如 Customer.Name==第一张表,“C24”)和填充 DOM 的阅读器。这些东西存在于遥远的基础设施中。

接下来 - 我的域中的实体和值对象看起来与 DOM dto 类似,但只有我感兴趣的投影,具有正确的数据类型和相应的验证。+ 我的领域模型中有 1:1 关联,可以隔离肮脏的混乱(幸运的是,它适合普遍存在的语言)。

有了这个——还剩下一个棘手的部分——excel DOM dtos 到域对象之间的映射。这就是我牺牲封装的地方 - 我从外部构造实体及其值对象。我的思维过程很简单 - 这个过度暴露的实体无论如何都无法持久,并且仍然可以强制有效性(通过构造函数)。它生活在聚合根之下。

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

Ps 我不确定我是否在做正确的事情。我可能错过了关于这个问题的一些重要内容。希望其他回答者能提供一些见解。