最近几年DDD(领域驱动设计 domain-driven design)概念很火,它以统一的语言来表述业务流程和技术架构,方便领域专家、技术开发交流达成共识,不失为一个复杂业务的解决之道。
我们使用DDD,在面向业务变化时首先要理解业务的核心问题,即有针对性地进行关注点分离来找到相对内聚的业务活动形成子问题域。子问题域内部是相对稳定的,即未来的变化频率不会很高,而子问题边界是很容易变化的。也就是说,DDD的核心在于领域边界的识别和划分。
比如物流系统,计算两地路径是相对固定的,但是根据包裹信息进行路径选择却常常跟随业务条件而变化。
下面我们重点来看DDD领域模型有哪些概念~
具有业务属性和业务行为,携带唯一标识,带有生命周期的对象。比如将订单领域中订单抽象为一个实体,其生命周期就包括从下单到最后收货整个周期。
只具有属性信息,不携带唯一标识的对象。
将实体和值对象组织在一起协同工作叫聚合。
聚合根也叫做根实体,聚合之间通信通过聚合根来管理,以聚合根 ID 关联的方式接受外部任务和请求。也就是说,一个聚合内如果需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体。
聚合根一般对应到一个Repositories,DDD之前的分层模型中一般是一个DO对象对应一个Dao。
一般来说,一个业务用例对应一个事务,一个事务对应一个聚合根,也即在一次事务中,只能对一个聚合根进行操作。 如果一个业务流程涉及到多个聚合根操作,不同聚合根之间可通过领域事件解耦,只不过这种是最终一致性的体现。
领域服务就是那些和实体具有交互,但是不完全属于单个实体或者聚合的服务,这种一般会抽象为领域服务。
比如账户管理领域中,转账这个业务行为,由于转账本身是发生在两个账户上的,将其作为账户行为是不合适的。如果将转账名词抽象为一个实体,也是比较尴尬的,毕竟转账是依托于账户的。
应用服务在领域服务的上层,直接对外部提供接口,相较于DDD之前的分层模型(facade-serviece-dao),DDD中的应用服务层会更薄一点,也更适应于业务变化,毕竟领域服务和实体行为相对稳定。
和Repositories类似的一个概念是Dao,不同的是Repositories针对更粗粒度的抽象,其在DDD中对应的维度是聚合,而Dao对应的是维度是DO类或者实体。
为什么Repositories是必须的呢?这是因为获取聚合一般不是简单的Dao.get这种操作,通过Repositories的封装,领域服务和实体行为只需简单的调用Repositories方法就能完成聚合的存取操作,而不用关心数据存储介质。一般来说,Repositories最常见的方法为get和save,前者针对查询场景,后者针对保存更新场景。
比如在订单通知业务场景(业务服务)中,通知作为一个服务是不应该持有具体订单信息的,这个时候我们就需要通过Repositories的抽象来建立对订单这个聚合的查询,即有一个订单的repo,而具体的查询逻辑应该在这个repo中。
限界上下文定义领域模型的边界,每个领域模型都有自己的领域边界,在领域边界内即限界上下文。
一个限界上下文封装了一个相对独立子领域的领域模型和服务。限界上下文地图描述了各个子领域之间的集成调用关系,这个定义和微服务的划分不谋而合,以提供业务能力为导向的、自治的、独立部署单元。
DDD通过子问题域(subdomain)的划分就已经进行了针对业务能力的分解,而限界上下文在解决方案域中完成了进一步分解。当然我们不能完全认为子问题域和限界上下文有严格意义上的一对一关系,但大多数情况下一个子问题域是会被设计成一个或多个限界上下文的。子域subdomain和限界上下文某种意义上是互相印证的,重点在区分问题域和解决方案域,这是落地DDD最困难的地方,也是判断一个架构师能力进阶的分水岭。
说了这么多概念,下面看一下他们在DDD分层中的各自位置:
最后来看下领域事件,DDD提倡聚合之间产生的业务协同使用领域事件的方式来完成,领域事件就是将上游聚合处理完成这个动作通过事件的方式进行抽象。
在DDD中,和领域事件相关的2个概念有事件溯源和CQRS等。
事件溯源并不关心当前状态,而是关注持续不断的变化事件。事件溯源的优势是其记录所有发生的历史事件,方便数据审计和回放;更好的扩展性、减少冲突、方便优化性能。
CURS就是命令查询分离,或者说读写分离模式,写入和查询对应不同的数据模型。 一般实现方案是数据写入之后,发送事件,消费方可根据时间构建自己的读视图数据,读写解耦,可分别进行优化。
DDD的领域概念基本就是上面说的这些了,但是在实际业务落地DDD时,我们会遇到一些问题的,比如最简单的就是有一个对象,目前没有业务行为,但是后续可能有业务行为,这种到底是抽象为值对象还是实体呢?这些是需要一些经验或者tradeoff的。注意,落地DDD是只要不违背大的DDD理念,tradeoff时能够将一些变化点抽象或者隔离起来,那么后续就算改动这些变化点,影响面也是可控的,这些都是允许的。