Golang 领域模型 - 聚合根
前言:聚合是要把实体、值对象等聚合起来完成完整的业务逻辑的一个存在。聚合根据上下文边界与业务单一职责、高内聚等原则,定义聚合内部应该包含哪些实体与值对象,这也是微服务为什么要用DDD的思想去划分的重要原因之一:天然的高内聚,低耦合。
Aggregate
要将实体、值对象、其他聚合在一致性边界之内的组合成聚合(Aggregate), 咋看起来是一件轻松的任务,但在DDD众多的战术设计中该模式是最不容易理解的。
聚合是针对数据变化可以考虑成一个单元的一组相关的对象。聚合使用边界将内部和外部的对象区分开来。每个聚合有一个根,这个根是一个实体,并且它是外部可以访问的唯一的对象。根可以保持对任意聚合对象的引用,并且其他的对象可以持有任意其他的对象,但一个外部对象只能持有根对象的引用。如果边界内有其他的实体,那些实体的标识符是本地化的,只在聚合内才有意义。
聚合、聚合根与战术设计
为什么准确的叫聚合根而不是聚合,如果聚合不是派生于实体,这个聚合对象就形成了一个没有边界的对象组合。如果没有边界随意的组合对象怎么还能叫战术设计?战术设计一定是基于模型的边界。聚合一定是派生自实体的,所以叫聚合根,并且使用了其他的实体、值对象,当然也可以使用其他的聚合根。这样设计的好处是可以通过根实体来做边界的选择组合。通常聚合根内是强一致的事务处理,多聚合之间是最终一致的事务处理。
支付聚合根
这个支付聚合根派生自订单实体关联了用户实体,有支付行为。
客户可以直接使用该对象的支付方法。那么经验丰富的读者可能会想示例太简单了,业务场景复杂的情况会关联很多的实体,并且还有很多行为。聚合根的组合实体都是委托资源库去查询的,聚合根的创建意味着依赖的实体要全部加载。
这样的多行为、多实体的会造成冗余的查询,并且边导致实体的界难以界定。后续章节CQRS会单独讲解如何设计小聚合,又回到了我们开篇所强调的分而治之。
工厂
实体和聚合通常会很大很复杂,尤其是聚合根。实际上通过构造器努力构建一个复杂的聚合也与领域本身通常做的事情相冲突。
在领域中,某些事物通常是由别的事物创建的,在聚合根内部组合的实体有可能是依赖于另一些实体或条件所组成的。篇幅所限笔者不能拿太复杂的场景代码。
当一个客户程序想创建另一个对象时,它会调用它的构造函数,可能传递某些参数。但是当构建对象是一个很费力的过程时(对象创建涉及了好多的知识,包括:关于对象内部结构的,关于所含对象之间的关系的以及应用其上的规则等),这意味着对象的每个客户程序将持有关于对象构建的专有知识。这破坏了领域对象和聚 合的封装。如果客户程序属于应用层,领域层的一部分将被移到了 外边,扰乱整个设计。
一个对象的创建可能是它自身的主要操作,但是复杂的组装操作不 应该成为被创建对象的职责。组合这样的职责会产生笨拙的设计, 也很难让人理解。
因此,有必要引入一个新的概念,这个概念可以帮助封装复杂的对 象创建过程,它就是 工厂(Factory)。工厂用来封装对象创建所必 需的知识,它们对创建聚合特别有用。当聚合的根建立时,所有聚 合包含的对象将随之建立,所有的不变量得到了强化。
抽象工厂
既然我们有了工厂了,更深层的解耦,何不用抽象工厂呢? 购买普通商品和购物车里的商品不都是下单吗?可惜普通商品不用关联购物车,那我们又不能设计一个大聚合根。这时候就适合用抽象工厂了
先来定义购买的接口,客户通过工厂传入参数和类型,工厂返回一个抽象接口,那么客户就可以直接调用Shop了.
在实现个抽象工厂,当然我们还要实现2个聚合根,它们都实现了Shop 方法(篇幅有限略过)。
在来看看客户的使用
目录
golang领域模型-开篇
golang领域模型-六边形架构
golang领域模型-实体
golang领域模型-资源库
golang领域模型-依赖倒置
golang领域模型-聚合根
golang领域模型-CQRS
golang领域模型-领域事件
项目代码 https://github.com/8treenet/freedom/tree/master/example/fshop
PS:加我微信:stargaze111,拉你加入DDD交流群,一起切磋DDD与代码的艺术!
版权声明: 本文为 InfoQ 作者【奔奔奔跑】的原创文章。
原文链接:【http://xie.infoq.cn/article/615645e6594bfd23fe3b67c07】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论