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().
聚合根以下的实体应该是不可变的。
不。值对象应该是不可变的。实体可以改变它们的状态。
只需确保您进行了正确的封装:
但是Policy.addAuto()不会完全成功,因为不能只传递一个新的Auto(对吧!?)
通常情况下应该是这样。问题是自动创建任务可能会变得太大。如果您很幸运,并且知道可以修改实体,并且能够将其平滑地划分为更小的任务,例如SpecifyEngine,问题就得到解决。
然而,“现实世界”并非如此,我感受到你的痛苦。
当用户上传 18 个 Excel 表的长垃圾数据时,我遇到了这样的情况(还有额外的奇特规则 - 无论数据有多么无效,都应该“导入”(正如我所说 - 这就像说 true==false))。这一上传过程被视为一个原子操作。
在这种情况下我做什么...
首先 - 我有 Excel 文档对象模型、映射(例如 Customer.Name==第一张表,“C24”)和填充 DOM 的阅读器。这些东西存在于遥远的基础设施中。
接下来 - 我的域中的实体和值对象看起来与 DOM dto 类似,但只有我感兴趣的投影,具有正确的数据类型和相应的验证。+ 我的领域模型中有 1:1 关联,可以隔离肮脏的混乱(幸运的是,它适合普遍存在的语言)。
有了这个——还剩下一个棘手的部分——excel DOM dtos 到域对象之间的映射。这就是我牺牲封装的地方 - 我从外部构造实体及其值对象。我的思维过程很简单 - 这个过度暴露的实体无论如何都无法持久,并且仍然可以强制有效性(通过构造函数)。它生活在聚合根之下。
基本上 - 这是你无法逃避 CRUDyness 的部分。
有时应用程序只是编辑一堆数据。
Ps 我不确定我是否在做正确的事情。我可能错过了关于这个问题的一些重要内容。希望其他回答者能提供一些见解。