我的英语不好,所以我会举两个例子。在这个例子中,让我们从DDD中删除一些重要的东西,主要是事情的本质。
如何从DDD的角度做到这一点?
我们有两个聚合根,Seller
和Advert
。 Seller
可以在以下示例中修改Advert
:
1
如果模型应该反映真实的业务逻辑。然后,Seller
必须更改Adverts
。即客户端层调用聚合changeAdvertName()
的方法changeAdvertCost()
和Seller
。顺便说一句,这提供了访问检查等优势。我们可以看到Seller
只能修改自己的Adverts
。这是第一个选择。
//Client layer call seller.changeAdvertName(name)
//AR Seller
class Seller{
adverts
changeAdvertName(advertId, name){
adverts[advertId].changeName(name)
}
changeAdvertCost(advertId, cost){
adverts[advertId].changeCost(cost)
}
}
//AR Advert
class Advert{
name
cost
changeName(name){
this.name = name
}
changeCost(cost){
this.cost = cost
}
}
2
另一种变体,客户端层可以直接从聚合changeName
调用方法changeCost
和Advert
。我多次看到这个实现。
//Client layer call advert.changeName(name)
//AR Advert
class Advert{
name
cost
changeName(name){
this.name = name
}
changeCost(cost){
this.cost = cost
}
}
您如何看待这些选择?它们是否对DDD有效 实施?从DDD的角度来看哪一个更正确和合乎逻辑?
谢谢!
答案 0 :(得分:4)
它们是否对DDD实施有效?
域驱动设计中的一个重要思想是一致性边界的概念 - 聚合是一个可以单独修改的状态边界 - 而不需要查看聚合之外的任何状态。
主要的好处是客户端代码不需要担心管理一致性规则;这个责任总是存在。
另一个好处是对一个聚合的修改不需要阻止对另一个聚合的修改。
嵌套聚合根,通过让一个聚合包含对另一个聚合的引用,会产生一些混乱的想法;尝试修改广告的线程可能会干扰尝试修改广告卖方的其他线程。
在单个聚合中拥有多个实体基本上没有任何问题。例如,您可以合理地将卖方实体和广告实体合并为单个卖方聚合,并通过对广告进行的所有更改通过卖方来强制执行一致性保证。然而,重要的是要认识到,在这种设计中,广告本身不是聚合根。
让广告成为自己的聚合根,处理自己的一致性规则也没有错,而卖家则生活在不同的聚合中。
在这个简单的例子中,卖方只是推迟对广告的更改,将它们彼此分开是有意义的,这样可以同时修改同一卖方的不同广告。
如果有一些跨越多个广告的关键域不变量,那么您可能需要将它们全部拉入一个集合中,这可能存在于卖方聚合中。
我的想法是,在实际业务流程中,卖方专门创建广告并更改广告。但是,抽象客户端层不是创建和更改广告。那么,你能帮忙理解吗?
事实证明,在真实的商业世界中,卖家可以创建(),drop(),modify()... - >广告。但在DDD聚合中,卖方只能实现创建(广告)行为吗?
这真的不是DDD特有的;它更像是“面向对象”编程的反映(如Java所理解的那样)。行为 - 也就是说,状态的变化 - 通过向管理该状态的实体发送消息而发生。
面向对象的习语实际上与英语语法不太匹配。我们通常会写“卖家修改广告” - 主题 - 动词 - 对象形式。但是在面向对象编程的语法中,对象响应命令性时态消息(命令)而改变它们自己的状态。
List.addItem(...)
- 我们没有修改列表,我们正在向列表发送一个命令,上面写着:“修改自己的状态”。
同样,卖方不会修改广告的状态;她正在发送一条消息,描述广告应该改变的方式,并由广告来做。
这是故意的:这意味着卖方可以与广告协作,而无需了解广告的实施,这意味着我们可以在不破坏卖家的情况下随时替换该实施。
答案 1 :(得分:0)
聚合根是一致性边界,以确保域模型保持可靠状态。 许多DDD从业者都知道这一点。
交易应该,而不是跨越聚合边界。在单独的事务中更新另一个聚合使用域事件。 more
从这个角度来看第二个选项会更有效,因为你有2个聚合根。