带你入门领域驱动设计(DDD)

要讲DDD,不得不先说一下微服务,因为2004年由Eric Evans提出的DDD就是伴随微服务的兴起才被人们重视起来的。

微服务是一种架构,它将传统的单体架构进行拆分,使系统可以分而治之、相互解耦。我们享受微服务带来的便利的同时,也不得不直面随之而来的困惑。比如,如何拆分服务?微服务架构理论工具大都从技术角度考虑,比如服务间调用、分布式、系统运维等。当业务需求迭代的时候,却不能很好地支撑,甚至造成一个简单需求需要改动许多微服务的情况。有没有一套可以指导我们微服务拆分和代码落地的理论?答案就是我们要介绍的DDD。

一、DDD的战略模式

如果把构建一套系统比喻成打仗,那我们首先要考虑的是制定作战计划,需要把各方面的专家召集到一起,共同讨论如何作战,比如我们需要分为海陆空几方面来作战,比如哪个高地是我们重点攻击对象,这就是DDD中的战略模式。

对应到软件系统架构,我们需要首先将系统划分成多个领域,然后聚焦到核心领域,也就是DDD中说的核心域,剩下非核心的领域我们称为通用域和支撑域,通用域一般可以通过采购业界现有的系统来实现,支撑域则可以采用一般架构来实现。

确定了核心域,我们需要和该领域的专家一同分析业务需求、用户故事,找出中间的关键事件(事件:领域专家关心的,在业务上真实发生的事情,例如:客户订单已提交;账户已锁定),这一过程在DDD中被称为“事件风暴”。下一步,分析出这些关键事件的发起者、动作(什么活动产生了事件,事件是业务的输出,命令是业务的输入,例如:提交客户订单;锁定账户),这一过程在DDD中被称为“命令风暴”。我们将识别出的关键事件中的实体、命令、事件等领域对象,从不同维度归类成一个个聚合,再将一个个聚合划分出限界上下文,这就是我们后面做微服务设计的边界。以上就是DDD工作坊的一般做法。

带你入门领域驱动设计(DDD)

二、DDD的术语

在做好战略设计、划分出一个个聚合后,我们需要考虑如何实现这些聚合。我们难免会被许多DDD专业术语搞得一头雾水,先来简单介绍一下吧。

限界上下文:用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等有一个确切的含义,没有二义性。这个边界定义了模型的适用范围,使团队所有成员能够明确地知道什么应该在模型中实现,什么不应该在模型中实现。

聚合:是一组组相关的领域对象模型的集合,用来封装业务的不变性,确保关联关系紧密的领域模型能够内聚在一起。

聚合根:负责执行业务规则,聚合根有全局标识,而边界内的实体只有局部标识;聚合边界外的对象只能引用聚合根,不能持有聚合内其他实体对象的引用。

实体:具有生命周期,具有唯一标识,通过ID判断相等性。

值对象:起描述作用,无唯一标识,通过属性判断相等性。

下面我举一个例子来说明上面术语对应于现实世界中的事物。

比如网购,你能浏览到各种商品,每一种商品在电商系统中,它就是一个实体,因为每种商品都是唯一的,有唯一的商品码来标识出来,这种商品还具有上架、修改价格、下架这样的生命周期。你确定要不要买这个商品,肯定会关心它的价格是不是便宜,价格就是一个值对象,因为100元的裤子和100元的上衣都是100元,从价值上来看是没有区别的。当你把选好的商品提交后,就生成了一个商品订单,这个订单也是一个实体,因为每个订单都具有唯一的订单号。当支付完成后,订单信息会发到仓库,此时仓库看到的商品和你看到的商品是不一样的,仓库看到的商品是商品码、商品大小,而不再关注商品的价格。你会发现同样是商品,在不同的系统中关注点是不一样的,这就是我们所说的限界上下文

当然,订单中肯定是有地址的,这里的地址是值对象,因为它不具有生命周期,也没有唯一标识,这里仅仅关心地址的内容。但是在地址管理中的地址确是实体,是具有生命周期的,也有唯一标识,你可以删除一个地址,或者添加和修改一个地址,甚至可以添加两个一模一样的地址,这里区别就是靠地址的ID来区分的,而不是通过地址的内容。所以,一个实体(地址)如果在另一个实体(订单)中被引用,可以是值对象的方式。
以上的订单聚合中,不仅仅有订单这个实体,还有评价、意见反馈等实体,他们都属于订单这个聚合,但是我们要提交评价或者提交意见反馈必须依赖于订单,所以订单这个实体就是订单这个聚合的聚合根。可以理解为聚合根是聚合与外部聚合或者系统交互的唯一出入口。

带你入门领域驱动设计(DDD)

三、DDD的分层架构

DDD的经典分层架构一般为四层架构,分别为用户接口层、应用层、领域层和基础层。

领域层是最重要的一层,我们和业务专家讨论沉淀出的领域知识和逻辑都要放在领域层中,这一层应该是和技术无关的,我们在做领域层的时候,不需要关心用什么数据库、用不用缓存等这些技术问题,只需要保证业务概念、业务状态、业务规则的正确性。对于领域服务,它不同于传统的服务,我们一般会优先将业务规则逻辑放入合适的实体中,但有些业务规则逻辑不属于任何一个实体,这时候我们需要一个领域服务。

应用层最主要的内容就是应用服务,它和领域服务最重要的区别在于是否包含领域知识,应用服务的作用在于协调多个聚合的服务和领域对象完成服务编排和组合,协作完成业务操作,而不关心业务规则的正确性,有关业务规则相关的都应该交由领域服务或者实体去完成。正常情况下,应用层应该是很薄的一层。

基础层的作用是为各个层提供技术支持,包括数据库、消息中间件、缓存等,最重要的,基础层应该采用依赖倒置的设计方式,实现其他层与基础层的解耦。这样的好处是,即使你的系统突然需要更换数据库,这对你的领域层、应用服务层都是无侵入的,你只需要将基础层的oracle更换为mysql即可,这符合DDD倡导的业务与技术无关的理念。

带你入门领域驱动设计(DDD)

四、总结

DDD是一种方法论,而不是一种架构,它最大的作用是为我们提供了一套构建系统的指导思想。当按照DDD的方法论去分析业务逻辑,会得到更贴近业务的微服务划分,这在后面业务迭代演进的时候,很大概率可以将业务的变化聚焦到最少的微服务,减少了微服务之间的相互影响。

按照DDD的方法论构建的系统代码,符合业务和技术隔离,当你技术上需要演进的时候,同样可以保证业务不受影响。

DDD中的代码应该是充分的面向对象编程的,大量的业务逻辑放在了实体中而非服务中,避免了模型的过度贫血。

作者:宋松涛

参考书目:极客时间,欧创新《DDD分层架构:有效降低层与层之间的依赖》