肝了一个月的 DDD,一文带你掌握!(一)

简介: 大家好,我是楼仔!去年倒腾了一个半月,写过一篇 DDD 的文章,当时没有推广,完全自嗨,为了不让这篇好文被埋没,现重新整理,突出重点,可读性更强!

大家好,我是楼仔!

去年倒腾了一个半月,写过一篇 DDD 的文章,当时没有推广,完全自嗨,为了不让这篇好文被埋没,现重新整理,突出重点,可读性更强!


为了证明该文没有注水,列一下当时的学习资料:

  • 小米内部 DDD 系列分享;
  • 小米内部 DDD 脚手架;
  • 小米内部授权认证项目(应用 DDD);
  • 极客时间欧创新的《 DDD 实战课》;
  • 掘金“柏炎”的 DDD 系列文档和 DDD Demo;
  • 美团技术团队、阿里云开发社区、网上博客等。

不 BB,直接上思维导图!

EFCH%EONX9U7MPBSO`(4_CS.png


1. 走进 DDD


1.1 为什么要用 DDD ?

  • 面向对象设计,数据行为绑定,告别贫血模型;
  • 降低复杂度,分而治之;
  • 优先考虑领域模型,而不是切割数据和行为;
  • 准确传达业务规则,业务优先;
  • 代码即设计;
  • 它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域和应用边界,可以很容易地实现业务和技术统一的架构演进;
  • 领域知识共享,提升协助效率;
  • 增加可维护性和可读性,延长软件生命周期;
  • 中台化的基石。

A$CZ]6K}56HK6(9LOGXGCA4.jpg


1.2 DDD 作用

说到 DDD,绕不开 MVC,在 MVC 三层架构中,我们进行功能开发的之前,拿到需求,解读需求。往往最先做的一步就是先设计表结构,在逐层设计上层 dao,service,controller。对于产品或者用户的需求都做了一层自我理解的转化。

image.gifA@73A(T8M`B`9CWLD1HY5FE.png

用户需求在被提出之后经过这么多层的转化后,特别是研发需求在数据库结构这一层转化后,将业务以主观臆断行为进行了转化。一旦业务边界划分模糊,考虑不全,大量的逻辑补充堆积到了代码层实现,变得越来越难维护。

假如我们现在要做一个电商订单下单的需求,涉及到用户选定商品,下订单、支付订单、对用户下单时的订单发货:

  • MVC 架构:我们常见的做法是在分析好业务需求之后,就开始设计表结构了,订单表,支付表,商品表等等。然后编写业务逻辑。这是第一个版本的需求,功能迭代饿了,订单支付后我可以取消,下单的商品我们退换货,是不是又需要进行加表,紧跟着对于的实现逻辑也进行修改。功能不断迭代,代码就不断的层层往上叠。
  • DDD 架构:我们先进行划分业务边界。这里面核心是订单。那么订单就是这个业务领域里面的聚合逻辑体现。支付,商品信息,地址等等都是围绕着订单实体。订单本身的属性决定之后,类似于地址只是一个属性的体现。当你将订单的领域模型构建好之后,后续的逻辑边界与仓储设计也就随之而来了。

DDD 整体作用总结如下:

  • 消除信息不对称;
  • 常规MVC三层架构中自底向上的设计方式做一个反转,以业务为主导,自顶向下的进行业务领域划分;
  • 将大的业务需求进行拆分,分而治之。


2. DDD 架构


2.1 DDD 分层架构

严格分层架构:某层只能与直接位于的下层发生耦合。

松散分层架构:允许上层与任意下层发生耦合。

在领域驱动设计(DDD)中采用的是松散分层架构,层间关系不那么严格。每层都可能使用它下面所有层的服务,而不仅仅是下一层的服务。每层都可能是半透明的,这意味着有些服务只对上一层可见,而有些服务对上面的所有层都可见。

image.gif]$NT})4]`K7K2U2159G@E)F.png

分层的作用,从上往下:

  • 用户交互层:web 请求,rpc 请求,mq 消息等外部输入均被视为外部输入的请求,可能修改到内部的业务数据。
  • 业务应用层:与 MVC 中的 service 不同的不是,service 中存储着大量业务逻辑。但在应用服务的实现中,它负责编排、转发、校验等。
  • 领域层:或称为模型层,系统的核心,负责表达业务概念,业务状态信息以及业务规则。即包含了该领域所有复杂的业务知识抽象和规则定义。该层主要精力要放在领域对象分析上,可以从实体,值对象,聚合(聚合根),领域服务,领域事件,仓储,工厂等方面入手。
  • 基础设施层:主要有 2 方面内容,一是为领域模型提供持久化机制,当软件需要持久化能力时候才需要进行规划;一是对其他层提供通用的技术支持能力,如消息通信,通用工具,配置等的实现。

在设计和开发时,不要将本该放在领域层的业务逻辑放到应用层中实现,因为庞大的应用层会使领域模型失焦,时间一长你的服务就会演化为传统的三层架构,业务逻辑会变得混乱。


2.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 就是数据库中的一条记录,好处是可以把一条记录作为一个对象处理,可以方便的转为其它对象。


3. DDD 基础


%SC}]X$6G}K03S~CB1@A$NH.png

学习 DDD 前,有很多基础概念需要掌握,这幅图总结的很全,他把 DDD 划分不同的层级:

  • 最里层是值、属性、唯一标识等,这个是最基本的数据单位,但不能直接使用。
  • 然后是实体,这个把基础的数据进行封装,可以直接使用,在代码中就是封装好的一个个实体对象。
  • 之后就是领域层,它按照业务划分为不同的领域,比如订单领域、商品领域、支付领域等。
  • 最后是应用服务,它对业务逻辑进行编排,也可以理解为业务层。


3.1 领域和子域

在研究和解决业务问题时,DDD 会按照一定的规则将业务领域进行细分,当领域细分到一定的程度后,DDD 会将问题范围限定在特定的边界内,在这个边界内建立领域模型,进而用代码实现该领域模型,解决相应的业务问题。简言之,DDD 的领域就是这个边界内要解决的业务问题域。

领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。

领域的核心思想就是将问题域逐级细分,来降低业务理解和系统实现的复杂度。通过领域细分,逐步缩小服务需要解决的问题域,构建合适的领域模型。

举个简单的例子,对于保险领域,我们可以把保险细分为承保、收付、再保以及理赔等子域,而承保子域还可以继续细分为投保、保全(寿险)、批改(财险)等子子域。


3.2 核心域、通用域和支撑域

子域可以根据重要程度和功能属性划分为如下:

  • 核心域:决定产品和公司核心竞争力的子域,它是业务成功的主要因素和公司的核心竞争力。
  • 通用域:没有太多个性化的诉求,同时被多个子域使用的通用功能的子域。
  • 支撑域:但既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域。

核心域、支撑域和通用域的主要目标:通过领域划分,区分不同子域在公司内的不同功能属性和重要性,从而公司可对不同子域采取不同的资源投入和建设策略,其关注度也会不一样。

很多公司的业务,表面看上去相似,但商业模式和战略方向是存在很大差异的,因此公司的关注点会不一样,在划分核心域、通用域和支撑域时,其结果也会出现非常大的差异。

比如同样都是电商平台的淘宝、天猫、京东和苏宁易购,他们的商业模式是不同的。淘宝是 C2C 网站,个人卖家对个人买家,而天猫、京东和苏宁易购则是 B2C 网站,是公司卖家对个人买家。即便是苏宁易购与京东都是 B2C 的模式,苏宁易购是典型的传统线下卖场转型成为电商,京东则是直营加部分平台模式。

因此,在公司建立领域模型时,我们就要结合公司战略重点和商业模式,重点关注核心域。

8]$]UX~ZKKCQDAK9QZ5ZBVN.png


3.3 通用语言和限界上下文

  • 通用语言:就是能够简单、清晰、准确描述业务涵义和规则的语言。
  • 限界上下文:用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。

3.3.1 通用语言

通用语言是团队统一的语言,不管你在团队中承担什么角色,在同一个领域的软件生命周期里都使用统一的语言进行交流。那么,通用语言的价值也就很明了,它可以解决交流障碍这个问题,使领域专家和开发人员能够协同合作,从而确保业务需求的正确表达。

这个通用语言到场景落地,大家可能还很模糊,其实就是把领域对象、属性、代码模型对象等,通过代码和文字建立映射关系,可以通过 Excel 记录这个关系,这样研发可以通过代码知道这个含义,产品或者业务方可以通过文字知道这个含义,沟通起来就不会有歧义,说的简单一点,其实就是统一产品和研发的话术。

直接看下面这幅图(来源于极客时间欧创新的 DDD 实战课):

image.gif

3.3.2 限界上下文

通用语言也有它的上下文环境,为了避免同样的概念或语义在不同的上下文环境中产生歧义,DDD 在战略设计上提出了“限界上下文”这个概念,用来确定语义所在的领域边界。

限界上下文是一个显式的语义和语境上的边界,领域模型便存在于边界之内。边界内,通用语言中的所有术语和词组都有特定的含义。把限界上下文拆解开看,限界就是领域的边界,而上下文则是语义环境。

通过领域的限界上下文,我们就可以在统一的领域边界内用统一的语言进行交流。


3.4 实体和值对象

3.4.1 实体

实体 = 唯一身份标识 + 可变性【状态 + 行为】

DDD 中要求实体是唯一的且可持续变化的。意思是说在实体的生命周期内,无论其如何变化,其仍旧是同一个实体。唯一性由唯一的身份标识来决定的。可变性也正反映了实体本身的状态和行为。

实体以 DO(领域对象)的形式存在,每个实体对象都有唯一的 ID。我们可以对一个实体对象进行多次修改,修改后的数据和原来的数据可能会大不相同。

但是,由于它们拥有相同的 ID,它们依然是同一个实体。比如商品是商品上下文的一个实体,通过唯一的商品 ID 来标识,不管这个商品的数据如何变化,商品的 ID 一直保持不变,它始终是同一个商品。

3.4.2 值对象

值对象 = 将一个值用对象的方式进行表述,来表达一个具体的固定不变的概念。

当你只关心某个对象的属性时,该对象便可作为一个值对象。 我们需要将值对象看成不变对象,不要给它任何身份标识,还应该尽量避免像实体对象一样的复杂性。

还是举个订单的例子,订单是一个实体,里面包含地址,这个地址可以只通过属性嵌入的方式形成的订单实体对象,也可以将地址通过 json 序列化一个 string 类型的数据,存到 DB 的一个字段中,那么这个 Json 串就是一个值对象,是不是很好理解?

下面给个简单的图(同样是源于极客时间欧创新的 DDD 实战课):

PUHXFZ0N9C7IW6MB$N3$B(V.jpgimage.gif

HPS~2)3UQLL~@NT17MHSW66.jpg

3.5 聚合和聚合根

3.5.1 聚合

聚合:我们把一些关联性极强、生命周期一致的实体、值对象放到一个聚合里。聚合是领域对象的显式分组,旨在支持领域模型的行为和不变性,同时充当一致性和事务性边界。

聚合有一个聚合根和上下文边界,这个边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,而聚合之间的边界是松耦合的。按照这种方式设计出来的服务很自然就是“高内聚、低耦合”的。

聚合在 DDD 分层架构里属于领域层,领域层包含了多个聚合,共同实现核心业务逻辑。跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现。

比如有的业务场景需要同一个聚合的 A 和 B 两个实体来共同完成,我们就可以将这段业务逻辑用领域服务来实现;而有的业务逻辑需要聚合 C 和聚合 D 中的两个服务共同完成,这时你就可以用应用服务来组合这两个服务。

3.5.2 聚合根

如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。

  • 首先它作为实体本身,拥有实体的属性和业务行为,实现自身的业务逻辑。
  • 其次它作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。
  • 最后在聚合之间,它还是聚合对外的接口人,以聚合根 ID 关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同。也就是说,聚合之间通过聚合根 ID 关联引用,如果需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体。

上面讲的还是有些抽象,下面看一个图就能很好理解(同样是源于极客时间欧创新的DDD实战课):

image.gifXCXZTPR@50AU)F`RL4RPZ0C.png

简单概括一下:

  • 通过事件风暴(我理解就是头脑风暴,不过我们一般都是先通过个人理解,然后再和相关核心同学进行沟通),得到实体和值对象;
  • 将这些实体和值对象聚合为“投保聚合”和“客户聚合”,其中“投保单”和“客户”是两者的聚合根;
  • 找出与聚合根“投保单”和“客户”关联的所有紧密依赖的实体和值对象;
  • 在聚合内根据聚合根、实体和值对象的依赖关系,画出对象的引用和依赖模型。


3.6 领域服务和应用服务

3.6.1 领域服务

当一些逻辑不属于某个实体时,可以把这些逻辑单独拿出来放到领域服务中,理想的情况是没有领域服务,如果领域服务使用不恰当,慢慢又演化回了以前逻辑都在 service 层的局面。

可以使用领域服务的情况:

  • 执行一个显著的业务操作
  • 对领域对象进行转换
  • 以多个领域对象作为输入参数进行计算,结果产生一个值对象

3.6.2 应用服务

应用层作为展现层与领域层的桥梁,是用来表达用例和用户故事的主要手段。

应用层通过应用服务接口来暴露系统的全部功能。在应用服务的实现中,它负责编排和转发,它将要实现的功能委托给一个或多个领域对象来实现,它本身只负责处理业务用例的执行顺序以及结果的拼装。通过这样一种方式,它隐藏了领域层的复杂性及其内部实现机制。

应用层相对来说是较“薄”的一层,除了定义应用服务之外,在该层我们可以进行安全认证,权限校验,持久化事务控制,或者向其他系统发生基于事件的消息通知,另外还可以用于创建邮件以发送给客户等。


3.7 领域事件

领域事件 = 事件发布 + 事件存储 + 事件分发 + 事件处理。

领域事件是一个领域模型中极其重要的部分,用来表示领域中发生的事件。忽略不相关的领域活动,同时明确领域专家要跟踪或希望被通知的事情,或与其他模型对象中的状态更改相关联。

下面简单说明领域事件:

  • 事件发布:构建一个事件,需要唯一标识,然后发布;
  • 事件存储:发布事件前需要存储,因为接收后的事建也会存储,可用于重试或对账等;
  • 事件分发:服务内直接发布给订阅者,服务外需要借助消息中间件,比如Kafka,RabbitMQ等;
  • 事件处理:先将事件存储,然后再处理。

比如下订单后,给用户增长积分与赠送优惠券的需求。如果使用瀑布流的方式写代码。一个个逻辑调用,那么不同用户,赠送的东西不同,逻辑就会变得又臭又长。

这里的比较好的方式是,用户下订单成功后,发布领域事件,积分聚合与优惠券聚合监听订单发布的领域事件进行处理。


3.8 资源库【仓储】

仓储介于领域模型和数据模型之间,主要用于聚合的持久化和检索。它隔离了领域模型和数据模型,以便我们关注于领域模型而不需要考虑如何进行持久化。

我们将暂时不使用的领域对象从内存中持久化存储到磁盘中。当日后需要再次使用这个领域对象时,根据 key 值到数据库查找到这条记录,然后将其恢复成领域对象,应用程序就可以继续使用它了,这就是领域对象持久化存储的设计思想。

是不是感觉这块内容比较抽象?直接对着Demo学习吧,很多东西你就会豁然开朗。

相关文章
|
存储 消息中间件 缓存
肝了一个月的 DDD,一文带你掌握!(二)
大家好,我是楼仔! 去年倒腾了一个半月,写过一篇 DDD 的文章,当时没有推广,完全自嗨,为了不让这篇好文被埋没,现重新整理,突出重点,可读性更强!
858 0
肝了一个月的 DDD,一文带你掌握!(二)
|
测试技术
2011年下半年11月份系统架构设计师上午试题答案之二
  2011年下半年11月份系统架构设计师上午试题答案之二 试题一:网络三层 层次化的网络设计在互联网组件的通信中引入了三个关键层的概念,分别是: 核心层、汇聚层和接入层。   核心层是为网络提供了骨干组件或者高速交换组件,在纯粹的分层设计中,核心层只完成数据交换的特殊任务。
864 0
|
程序员
2011年下半年11月份系统架构设计师上午试题答案之三
  2011年下半年11月份系统架构设计师上午试题答案之三 试题一:著作权 2007年上半年系统分析师上午试题12 ●甲公司的程序员在不影响本职工作的条件下,在乙公司兼职并利用其兼职单位的物质技术条件开发了一套应用程序,该应用程序的著作权属于(12)。
901 0
|
机器人 调度 语音技术
2010年下半年11月份系统架构设计师上午试题以及参考答案之七
2010年下半年11月份系统架构设计师上午试题以及参考答案之七   ●某公司承接了一个开发家用空调自动调温器的任务,调温器测量外部空气温度,根据设定的期望温度控制空调的开关。根据该需求,公司应采用____(50)___架构风格最为合适。
843 0
2010年下半年11月份系统架构设计师上午试题以及参考答案之十
2010年下半年11月份系统架构设计师上午试题以及参考答案之十   ●The software architecture is a set of software components, subsystems, relationships,interactions, the properti...
798 0
|
设计模式 调度
2010年下半年11月份系统架构设计师上午试题以及参考答案之四
2010年下半年11月份系统架构设计师上午试题以及参考答案之四 ●在实际的项目开发中,人们总是希望使用自动工具来执行需求变更控制过程。下列描述中,___(24)___不是这类工具所具有的功能。    (24)A.可以定义变更请求的数据项以及变更请求生存期的状态转换图          B.记录每一种状态变更的数据,确认做出变更的人员          C.可以加强状态转换图使经授权的用户仅能做出所允许的状态变更          D.定义变更控制计划,并指导设计人员按照所制定的计划实施变更 ●需求管理是CMM可重复级中的6个关键过程域之一,其主要目标是___(25)___。
909 0
|
网络协议 数据安全/隐私保护 开发者
2010年下半年11月份系统架构设计师上午试题以及参考答案之八
2010年下半年11月份系统架构设计师上午试题以及参考答案之八   ●某服务器软件系统能够正确运行并得出计算结果,但存在“系统出错后不能在要求的时间内恢复到正常状态”和“对系统进行二次开发时总要超过半年的时间”两个问题,上述问题依次与质量属性中的___(58)___相关。
789 0
|
测试技术
2010年下半年11月份系统架构设计师上午试题以及参考答案之五
2010年下半年11月份系统架构设计师上午试题以及参考答案之五   ●下列关于不同软件开发方法所使用的模型的描述中,正确的是___(32)___。(32)A.在进行结构化分析时,必须使用数据流图和软件结构图这两种模型      B.采用面向对象开发方法时,可以使用状态图和活动图对系统的动态行为进行建模      C.实体联系图(E-R图)是在数据库逻辑结构设计时才开始创建的模型      D. UML的活动图与程序流程图的表达能力等价 参考答案:B   ●某银行系统采用Factory Method方法描述其不同账户之间的关系,设计出的类图如下所示。
1027 0
|
测试技术 项目管理
2009年下半年11月份系统架构设计师上午试题以及参考答案之七
2009年下半年11月份系统架构设计师上午试题以及参考答案之七 ● 系统测试将软件、硬件、网络等其它因素结合,对整个软件进行测试。(43)不是系统测试的内容。(43)A.路径测试B.可靠性测试C安装测试D.安全测试 参考答案:A   ● 软件测试是为了发现错误而执行程序的过程。
916 0
|
存储 文件存储 BI
2009年下半年11月份系统架构设计师上午试题以及参考答案之四
2009年下半年11月份系统架构设计师上午试题以及参考答案之四 ● 某公司欲构建一个网络化的开放式数据存储系统,要求采用专用网络连接并管理存储设备和存储管理子系统。针对这种应用,采用(15)存储方式最为合适。
1205 0