首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >请说明如何针对聚合根的子实体进行创建/更新

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

Stack Overflow用户
提问于 2011-01-11 01:03:39
回答 3查看 9K关注 0票数 21

经过大量的阅读和思考,当我开始理解DDD时,我对处理聚合根下的复杂层次结构的最佳实践感到有点困惑。我认为这是一个常见问题,但在阅读了无数的例子和讨论后,没有人真正谈论我所看到的问题。

如果我同意DDD的想法,那么聚合根下的实体应该是不可变的。这就是我问题的症结所在,所以如果这是不正确的,那就是我迷路的原因。

这是一个虚构的example...hope,它有足够的水分来讨论。

考虑一份汽车保险单(我不是做保险的,但这和我和我的保险公司通电话时听到的语言一致)。

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

现在,要创建一个策略,让我们假设它必须至少有一个自动。这就是我感到沮丧的地方。假设Auto是相当复杂的,包括许多字段,可能还包括存放它的位置(一个位置)的一个子字段。如果我理解正确,"create Policy“构造器/工厂必须接受Auto作为输入,或者通过构建器进行限制,使其不能在没有Auto的情况下创建。而Auto的创建,因为它是一个实体,不能预先完成(因为它是不可变的?也许这只是一种错误的解释)。所以你不会说new Auto,然后是setX,setY,add(Z)。

如果Auto不仅仅是一些琐碎的东西,那么您最终必须构建一个庞大的构建器层次结构,并尝试在Policy的上下文中管理创建Auto。

这方面的另一个转变是在创建策略后,希望添加另一个Auto...or更新现有的自动。显然,策略控件this...fine...but Policy.addAuto()不能很好地运行,因为不能简单地传入一个新的自动(对吧!?)。例如,Policy.addAuto(VIN、make、model等)但都是如此简单,这看起来是合理的。但是,如果这种工厂方法使用了太多的参数(可以想象,整个Auto接口),我需要一个解决方案。

从我的想法来看,我意识到对一个实体有一个暂时的引用是可以的。因此,在瞬态环境中,在聚合的父级之外创建一个实体可能是很好的,所以可以这样说:

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

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

然后让它在你说Policy.addAuto(自动)的时候进行排序

如果添加事件,这个保险示例会变得更有趣,比如一个带有PolicyReports或RepairEstimates...some值对象的意外事件,但对于我的简单示例来说,大多数实体在policy...at之外都是没有意义的。

我想我快到了。希望这是清楚的,而不只是一个重复的常见问题,到处都有答案。

EN

回答 3

Stack Overflow用户

发布于 2014-05-03 03:53:20

聚合根的存在是为了实现事务一致性。

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

两者之间的区别在于不变性和同一性。

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

代码语言:javascript
复制
Money // A value object
{
    string Currency;
    long Value;
}

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

实体是具有随时间变化的可变性的对象,但其标识在其整个生命周期中是不可变的。

代码语言:javascript
复制
Person // An entity
{
    PersonId Id; // An immutable Value Object storing the Person's unique identity
    string Name;
    string Email;
    int Age;
}

那么什么时候和为什么会有聚合根呢?

Aggregate Root是专门的实体,其工作是将一组域概念分组到一个事务范围内,仅用于数据更改。也就是说,假设一个人有腿。您需要问自己,腿上的更改和Person上的更改是否应该一起分组到单个事务下?或者我可以单独更换一个或另一个?

代码语言:javascript
复制
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必须始终参与事务,则应在Person实体内部组成Leg。在这一点上,你将不得不通过人来改变腿。

此决策将取决于您正在建模的域:

也许这个人是他腿上的唯一权威,他们会随着年龄的增长而变得更长更强,颜色会随着他的种族而变化,等等。这些都是不变的,并且这个人将负责确保他们得到维护。如果别人要给这个人换腿,比如说你要给他刮腿毛,你就得让他自己刮,或者暂时交给你刮。

或者你可能是在考古学的领域。在这里你可以找到腿,并且你可以独立地操纵腿。在某些时候,你可能会发现一个完整的身体,并猜测这个人在历史上是谁,现在你有了一个人,但这个人没有权利决定你将如何处理你发现的腿,即使它被证明是他的腿。腿的颜色会根据您对其应用的恢复程度或其他情况而变化。这些不变量将由另一个实体维护,这次不是人,而是考古学家。

回答你的问题:

我一直听到你在谈论Auto,所以这显然是你的领域的一个重要概念。它是实体还是值对象?如果汽车是序列号为XYZ的汽车,这有关系吗?或者你只对品牌、颜色、年份、型号、制造等感兴趣?假设你关心Auto的确切身份,而不仅仅是它的功能,而不是它需要是你的域的一个实体。现在,您谈到了保单,保单规定了汽车的承保范围和不承保范围,这取决于汽车本身,可能也取决于客户,因为根据他的驾驶历史、类型和年份以及他拥有的汽车以外的东西,他的政策可能会有所不同。

所以我已经可以想象拥有:

代码语言:javascript
复制
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和Customer。你会说,如果汽车在车库,或者我们将汽车从一个保单转移到另一个保单怎么办。这让我觉得Auto是它自己的聚合根,Policy和Customer也是如此。为什么会这样呢?因为这听起来像是你的域名的使用,你会改变一辆汽车的车库,而不关心政策是否随之改变。也就是说,如果有人更改了Auto's Garage和IsAtGarage状态,您并不关心不更改策略。我不确定我是否清楚,您不会希望以非事务性的方式更改客户的姓名和DateOfBirth,因为您可能更改了他的姓名,但它无法更改日期,现在您有一个腐败的客户,其出生日期与他的姓名不匹配。另一方面,在不更改策略的情况下更改Auto是很好的。因此,Auto不应该在Policy的聚合中。实际上,Auto不是Policy的一部分,而只是Policy跟踪并可能使用的东西。

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

代码语言:javascript
复制
aCustomer = Customer.Make(...);
anAuto = Auto.Make(...);
anotherAuto = Auto.Make(...);
aPolicy = Policy.Make(aCustomer, { anAuto, anotherAuto }, ...);

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

票数 23
EN

Stack Overflow用户

发布于 2011-01-11 06:49:49

聚合根目录下的

实体应该是不可变的。

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

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

  • 实体修改自身
  • 实体仅通过聚合根进行修改

但Policy.addAuto()不能很好地运行,因为不能简单地传入一个新的Auto (对吧!?)

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

然而,“现实世界”不是这样的,我能感受到你的痛苦。

我遇到过这样的情况,当用户上传18张excel表格时,会有大量的数据(有额外的花哨的规则--不管数据有多无效,它都应该被“导入”(正如我所说的,这就像是在说true==false)。这个上传过程被认为是一个原子操作。

在这种情况下我所做的..。

首先,我有excel文档对象模型,映射(例如,Customer.Name==1st工作表,"C24")和填充DOM的阅读器。这些东西生活在遥远的基础设施中。

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

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

基本上--这就是你不能逃避CRUDyness的部分。

有时应用程序只是编辑一堆数据。

附注:我不确定我做的是不是对的。在这个问题上,我可能遗漏了一些重要的东西。希望能从其他回答者那里获得一些见解。

票数 1
EN

Stack Overflow用户

发布于 2011-01-12 03:02:02

我的部分答案似乎在这些帖子中得到了捕捉:

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或其他贫血图,然后将其传递给工厂以构建聚合。

类似于:

代码语言:javascript
复制
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)使用构建器(可能是复合的):

代码语言:javascript
复制
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

关于这些的争论以及聚合和实体创建语义是如何管理的还不是很清楚,但是必须看一看工厂才能理解这个域似乎有点勉强。

即使Policy是一个集合,并管理如何将它下面的东西放在一起,有关Auto外观的规则似乎属于Auto或其工厂(涉及Policy的地方有一些例外)。

由于Policy在没有最小限度构造的子代集合的情况下是无效的,因此需要在创建之前或在其创建过程中创建这些子代。

最后一句话才是关键所在。看起来在很大程度上,这些帖子将孩子的创建作为独立的事务来处理,然后将它们粘合在一起。纯DDD方法似乎认为,策略必须创建汽车,但在非平凡的情况下,这一细节严重失控。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/4649348

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档