去年倒腾了一个半月,写过一篇 DDD 的文章,当时没有推广,完全自嗨,为了不让这篇好文被埋没,现重新整理,突出重点,可读性更强!
不 BB,直接上思维导图:
为什么要用 DDD?
原因如下:
面向对象设计,数据行为绑定,告别贫血模型
降低复杂度,分而治之
优先考虑领域模型,而不是切割数据和行为
准确传达业务规则,业务优先
代码即设计
它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域和应用边界,可以很容易地实现业务和技术统一的架构演进
领域知识共享,提升协助效率
增加可维护性和可读性,延长软件生命周期
中台化的基石
DDD 作用
说到 DDD,绕不开 MVC,在 MVC 三层架构中,我们进行功能开发的之前,拿到需求,解读需求。
往往最先做的一步就是先设计表结构,在逐层设计上层 dao,service,controller。对于产品或者用户的需求都做了一层自我理解的转化。
用户需求在被提出之后经过这么多层的转化后,特别是研发需求在数据库结构这一层转化后,将业务以主观臆断行为进行了转化。
一旦业务边界划分模糊,考虑不全,大量的逻辑补充堆积到了代码层实现,变得越来越难维护。
假如我们现在要做一个电商订单下单的需求,涉及到用户选定商品,下订单、支付订单、对用户下单时的订单发货。
MVC 架构: 我们常见的做法是在分析好业务需求之后,就开始设计表结构了,订单表,支付表,商品表等等。然后编写业务逻辑。
这是第一个版本的需求,功能迭代饿了,订单支付后我可以取消,下单的商品我们退换货,是不是又需要进行加表,紧跟着对于的实现逻辑也进行修改。功能不断迭代,代码就不断的层层往上叠。
DDD 架构: 我们先进行划分业务边界。这里面核心是订单。那么订单就是这个业务领域里面的聚合逻辑体现。支付,商品信息,地址等等都是围绕着订单实体。
订单本身的属性决定之后,类似于地址只是一个属性的体现。当你将订单的领域模型构建好之后,后续的逻辑边界与仓储设计也就随之而来了。
DDD 整体作用总结如下:
消除信息不对称
常规MVC三层架构中自底向上的设计方式做一个反转,以业务为主导,自顶向下的进行业务领域划分
将大的业务需求进行拆分,分而治之
DDD 分层架构
如下:
严格分层架构: 某层只能与直接位于的下层发生耦合。
松散分层架构: 允许上层与任意下层发生耦合。
在领域驱动设计(DDD)中采用的是松散分层架构,层间关系不那么严格。每层都可能使用它下面所有层的服务,而不仅仅是下一层的服务。
每层都可能是半透明的,这意味着有些服务只对上一层可见,而有些服务对上面的所有层都可见。
分层的作用,从上往下:
用户交互层: web 请求,rpc 请求,mq 消息等外部输入均被视为外部输入的请求,可能修改到内部的业务数据。
业务应用层: 与 MVC 中的 service 不同的不是,service 中存储着大量业务逻辑。但在应用服务的实现中,它负责编排、转发、校验等。
领域层: 或称为模型层,系统的核心,负责表达业务概念,业务状态信息以及业务规则。即包含了该领域所有复杂的业务知识抽象和规则定义。该层主要精力要放在领域对象分析上,可以从实体,值对象,聚合(聚合根),领域服务,领域事件,仓储,工厂等方面入手。
基础设施层: 主要有 2 方面内容,一是为领域模型提供持久化机制,当软件需要持久化能力时候才需要进行规划;一是对其他层提供通用的技术支持能力,如消息通信,通用工具,配置等的实现。
在设计和开发时,不要将本该放在领域层的业务逻辑放到应用层中实现,因为庞大的应用层会使领域模型失焦,时间一长你的服务就会演化为传统的三层架构,业务逻辑会变得混乱。
各层数据转换
每一层都有自己特定的数据,可以做如下区分:
VO(View Object): 视图对象,主要对应界面显示的数据对象。对于一个 WEB 页面,或者 SWT、SWING 的一个界面,用一个 VO 对象对应整个界面的值。
DTO(Data Transfer Object): 数据传输对象,主要用于远程调用等需要大量传输对象的地方。比如我们一张表有 100 个字段,那么对应的 PO 就有 100 个属性。但是我们界面上只要显示 10 个字段,客户端用 WEB service 来获取数据,没有必要把整个 PO 对象传递到客户端,这时我们就可以用只有这 10 个属性的 DTO 来传递结果到客户端,这样也不会暴露服务端表结构。到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就转为 VO。在这里,我泛指用于展示层与服务层之间的数据传输对象。
DO(Domain Object): 领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。
PO(Persistent Object): 持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应 PO 的一个(或若干个)属性。最形象的理解就是一个 PO 就是数据库中的一条记录,好处是可以把一条记录作为一个对象处理,可以方便的转为其它对象。
学习 DDD 前,有很多基础概念需要掌握,这幅图总结的很全,他把 DDD 划分不同的层级:
最里层是值、属性、唯一标识等,这个是最基本的数据单位,但不能直接使用。
然后是实体,这个把基础的数据进行封装,可以直接使用,在代码中就是封装好的一个个实体对象。
之后就是领域层,它按照业务划分为不同的领域,比如订单领域、商品领域、支付领域等。
最后是应用服务,它对业务逻辑进行编排,也可以理解为业务层。
领域和子域
在研究和解决业务问题时,DDD 会按照一定的规则将业务领域进行细分,当领域细分到一定的程度后,DDD 会将问题范围限定在特定的边界内,在这个边界内建立领域模型,进而用代码实现该领域模型,解决相应的业务问题。
简言之,DDD 的领域就是这个边界内要解决的业务问题域。
领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。
领域的核心思想就是将问题域逐级细分,来降低业务理解和系统实现的复杂度。通过领域细分,逐步缩小服务需要解决的问题域,构建合适的领域模型。
举个简单的例子,对于保险领域,我们可以把保险细分为承保、收付、再保以及理赔等子域,而承保子域还可以继续细分为投保、保全(寿险)、批改(财险)等子子域。
核心域、通用域和支撑域
子域可以根据重要程度和功能属性划分为如下: