DDD 中文名:领域驱动设计,做业务开发必不可少的指导方法论。
如果你苦于:
- 不知道如何从产品需求文档中提取关键业务信息;
- 不知道如何根据业务建立领域模型和数据模型;
- 明明知道 MVC 框架解决不了复杂业务,又不了解其他业务框架;
- 只听说过 DDD 高大上的方法论,却不知道如何落地。
那么你都可以来看看这场 Chat。
本场 Chat 结合大家实际开发中场景,以举例子的方式,对比 MVC 和 DDD 战略设计和战术设计的不同,让你对 DDD 有个整体的认识,最后给大家展示一种可以快速落地实践的业务框架。
-
-
- 需求评审阶段
- 需求的来源
- 需求评审上如何提问?
- 抓住动词,联想名词
- WR 原则
- 需求评审的结果是什么?
- 技术评审
- 领域模型
- 数据模型
- 测试评审
- 战术设计
- 分层
- domain 和上层间的高内聚低耦合
- domain 和下层间的高内聚低耦合
- 领域层的富血模型
- 领域元素的边界要清楚
- 分层
-
DDD,中文名为领域驱动设计,为业务开发中必不可少的指导方法论,本文以业务开发中战略设计和战术设计为例,将普通开发模式和 DDD 模式进行对比,让大家对 DDD 有个整体的认识。
我们在工作中,可能常常听到过下面两种声音:
- 产品说:这个做出来的东西,不是我想要的,体现不了业务价值。
- 开发说:你要做的东西和目前系统设计冲突了,如果要做的话,工作量比较大,改动点很多!(感觉在乱提需求……)
产生这两种声音的原因有很多,有的人说是沟通问题,有的人说系统设计不够扩展,而我觉得这些问题都只是表象,不是问题的根源。
DDD 完全可以解决以上两个问题,在战略设计阶段可以解决业务价值的问题,在战术阶段可以解决系统和业务需求不一致的问题。接下来我们按照常见开发流程,把普通开发模式和 DDD 开发模式对比一下,看看两者有啥区别,大家也看看这两个问题具体是怎么被解决的。
业务开发的几个大阶段如下:
接着我们按照几个大阶段分别来描述下 DDD 模式有什么不同。
需求评审阶段我们先描述一下需求评审的场景:
产品经理把开发、测试等相关的人预约到一个会议室,产品把写好的需求文档拿出来讲,大家听,不懂的就问……
不知道其他的需求评审会是不是这样的,反正我经历的基本是这样。不是说这种不对,只是感觉这样似乎缺少一些重点?
- 需求的来源?
- 需求评审上如何提问?
- 需求评审的结果是什么?
常规评审会的做法上,需求的来源都在需求文档上,需求文档写了什么需求,我就设计什么样的系统,需求文档中需求基本都是为了完成特定的场景,不是很全面完整,可能会导致系统设计的不够扩展。
抛开技术不谈,系统设计难以扩展的很大原因就在于需求的不完整。设想一个很大的需求场景,你拿到的只是部分,你设计出来的系统能够满足后来的扩展么?
DDD 强调的有:
- 需求不要仅仅局限于需求文档,要多多联想生活,个人工作经验,多想想竞品的实现。
- 不确定的细节多和领域专家讨论讨论,注意这里领域专家不一定是产品经理,领域专家指的是对业务的广度和深度理解比较强的人。
这两点非常重要,如果你真的实施下来了,最后的结果可能是你得到了 10 个场景,但需求文章只做其中的 2 个场景,但剩下的 8 个场景没有浪费,可以为你的系统设计做好扩展,因为你清楚,以后这里可能会改。
至于如何扩展,核心在于抽象,你可以把共用的事情抽象,可以把行为抽象,可以把流程抽象等。
需求评审上如何提问?需求评审会的主要目的是为了弄懂业务和需求,但真正的弄懂业务和需求是什么,太难了,有可能你专注于这个业务几年后,才能真正知道该如何定义这个业务,所以我们该把这个目标缩小一下。
于是一般的评审会,大家就去听听流程,听听我们要做成什么样子。
DDD 更加强调业务的本质,最后达到能用一两句话直白地把业务描述出来的效果,为了达到这种效果,我们常常会有一些方法,比如:
- 抓住动词,联想名词
- WR 原则
我们小时候学习语文的句子,经常会说到主谓宾,然后为了锻炼我们的语感和对句子的理解,记得那时候还有组句和完形填空的题目。同理,业务需求其实也是一个句子,为了理解这个句子,我们完全可以用小时候做题的方式,但为了方便,我们简化成:抓住动词,联想名词。
我们举个常见场景:
小美在某宝 App 买东西。
这句话比较简单地描述了买东西的场景,我们用抓住动词联想名词来描述一下这个场景,可以简单描述成这样:
通过这种方式,我们可以把业务描述抽象成概念,并且把这些概念丰富。比如说买的东西,我们抽象成商品,并把商品丰富成实物商品、虚拟商品、海淘商品等。
WR 原则九十年代的时候,有个外国人发表了 5W1H 分析法,后人把这种方法称为思考问题的基本方法。我们在 DDD 落地时尝试过这种方法,发现很用有。5W1H 分析法有一张图很清楚的,图片来自于百度百科:
这个图表达了对问题的一种思考套路。
图纵轴就是分析法中的 5W1H:What、Why、Where、When、Who、How,具体含义图解中已经说的很明白了。
图横轴,是对 5W1H 的每个阶段的深入解析,帮助我们对当前阶段有更深的理解。分别是四个问题:现状如何、为什么现状如此、能否改善、该怎么改善。
图纵轴和横轴乘一下,是 24 个方格,是对问题的思考结果,整个思考下来,我们对一件事情应该会有一定的认识了。
在分析业务时,5W1H 分析法会帮助我们理清业务的流程,但我们得不到一个结果,在业务开发过程中,结果往往是非常重要的。我们还是举买东西的例子:
买东西的例子我们抽象成了下单场景。在领域建模的时候,我们往往会用订单这个名词,来承接下单的所有因果,这个订单就是本次下单的结果,记录着下单的所有信息和后续履约过程。如此的例子有很多,比如下单之后,商家去发货,会有发货单,买家来退款,会有退款单等,我们建模的时候,都会用一个结果来承接所有的动作。
所以我们在 5W1H 的基础上加了一个名词,叫做结果,英文为 Result,总体简称 WR 原则。
5W1H 强调的是分析的过程,但我们领域建模使用更多的却是分析的结果。
所以在需求评审的时候,针对每个需求,你可以从抓住动词联想名词 + WR 原则出发,来进行提问,比如问:我们为什么需要做这个业务?价值在哪儿?
需求评审的结果是什么?一般的需求评审会的结尾都是,产品经理开始问:
大家有木有不懂的,不懂的提问。大概几秒的沉默,产品经理接着说,那好,需求评审结束了,说白了,本次需求评审的产出,其实就是那么一篇需求文档。
我们实践 DDD 的时候略有不同,我们要求需要评审会结束后,最好有个最初版的通用语言出来。
我们对通用语言的定义:一定上下文内,对业务概念的一致通用表达,是理清业务是什么,能干什么,以及和其他业务边界的过程。
但在实际的工作中,需求评审会上产出初版通用语言太难了,你很难要求产品经理在需求文档中写出重要的通用语言,不太现实,那么只能开发自己来了,我们会要求懂 DDD 的同学在需求评审会结束时,发出来通用语言的 wiki 地址,要求主设计人员,在技术评审前,通过各种手段,完善好通用语言,并记录在文档中。最近我个人在落地一个项目,总结出来的通用语言,给大家展示下:
在需求评审后和技术评审前这个时间范围内,主导人员主要是开发人员了,开发人员需要在技术文章中,产出三个重要部分:
- 通用语言
- 领域模型
- 数据模型
通用语言我们上图也展示了一个,表达的形式没有标准,表格,图形都行,一般好的通用语言有如下几个特征:
- 描述比较直白简单,基本都是一句话,最好让小白都能看懂。不会用一个很难理解的名词去解释另外一个名词,也不会很复杂啰嗦。
- 重点通用语言会有英文描述。
- 通用语言有顺序,会循序渐进。
这里其实是 DDD 落地最复杂的地方,也是耗时比较久的地方,我们最好能在业务评审的时候,就要开始去关注领域模型。
很多人对领域模型不是很了解,不知道领域模型到底是啥,通俗的来说:领域模型就是用来定义领域元素,和管理领域元素的上下文的。
那么又会有人问,领域元素又那些,领域元素的上下文又是啥?
- 我们常见的领域元素有:实体、值对象、聚合、领域服务、应用服务、领域能力等。
- 领域元素之间的上下文指:元素间包含关系和逻辑关系。
本文主要让大家对 DDD 有个整体的认识,具体的就不细说了,我们有个详细的文章列表,感兴趣的同学可以研究研究:
http://wenhe.online/?p=2043
我们贴出来一张完整的领域模型图(不包含和外域的上下文),感兴趣的同学可以自己研究研究:
数据模型,直白来说,就是在领域模型的基础上进行建表,我们需要表达出一种存储结构,这里特别推荐一本书,叫做《彩色 UML 建模》 ,这本书目前已经不印刷了,网上只有高价买原本的才有,这本书是我看过建模书籍中说的最好的一本了。
还有一些另外的建模小技巧,都是很常见的建模手段:
- 表的二级结构,很多场景下表都可以设计成二级结构,如总账和明细,订单和商品等。
- 不要害怕字段冗余,很多时候冗余是件好事,可以帮忙我们减少表的关联,增加查询的速度,有时候完全按照三范式建表可能会增加很多成本。
- 大宽表,像 ES、Hive 这种非关系型数据库,我们常常会建大宽表,来方便搜索。
虽然说我们在实践 DDD 的时候,尽量不要让数据模型来影响了业务,但有些时候,还是要结合一下公司的规模和成本作一些让步,争取尽快落地。
当通用语言、领域模型、数据模型都准备好了之后,当然我们这里准备的只是初版,我们在技术评审会上,需要把团队成员都叫上,由开发主导,向各个成员展示我们的初版成果,大家可以一起讨论,有争议的地方,讨论出结果后,再修改掉。
技术评审算是全员参与的第二个会议了。
测试评审测试评审会的主导者是测试工程师,开发和产品需要注意的有:
- 测试工程师的表达是否符合通用语言,不符合请纠正。
- 测试工程师的理解是否和你理解一直,不一致请讨论。
以上阶段完成,DDD 战略设计也基本完成了,其实和普通的设计流程差别一致的,但思想侧重点不一样,DDD 更加侧重于如何想清楚业务是什么、能干什么、边界在那里,战略设计的所有产出都是围绕着这三个问题展开的。
战术设计战术设计直白来说,就是写代码了,我们在使用 DDD 模式来写代码时,我们对代码有着严格的约束,大的来说模块、package、类的分层约束、调用约束等,小的来说模块,package,领域元素都是有命名约束,通过这样约束,让代码展示出来,就像展示出业务一样,即代码即设计、即业务。
战术方面内容太多,没有几十篇说不完,我们就说一点:如何写出高内聚低耦合的代码。
我们通过 MVC 和 DDD 两种架构,选取下面两个方面简单阐述下:
- 分层
- 领域层富血模型
大家搭建项目的时候,都会通过模块来进行分层,大家讨论最多的是分三层好,还是分四层好呢?很少见有人去思考分层的好处所在。
分层只是达到系统高内聚低耦合的一种手段。
- 高内聚:指的是对领域层的内聚。
- 低耦合:指的是领域层对上下游的耦合少。
所以我们以下内容,并不是比较分三层还是四层还是五层的好坏,我们主要是来看看那种分层结构比较容易让业务达到高内聚低耦合。
我们来看下传统的 MVC 三层结构是否可以实现高内聚低耦合,画一张图描述下 MVC 三层结构:
MVC 三层指的是 Controller 控制层、Service 业务逻辑层、 Model 数据层。
从图中的三层结构来看,其实是可以做到高内聚低耦合的,但很难。
接下来我们从两个角度来分析一下:
- domain 层和上下游的关系
- domain 层自己内部的关系
Service 上游是 Controller 层,Service 层定义接口,Controller 层进行调用,这点是做到了依赖抽象。但在实践中,2 个很大细节被忽略,导致了违背了高内聚低耦合的原则。
细节一:DTO 流入了 Service 层
大家应该是知道,DTO 是对外的数据载体,是当前业务场景的输入和输出,即是应用服务的输入和输出。在实际对接的过程中,场景的输入和输出是很难制定标准的,很多时候需要去适配别人,所以我们希望 DTO 的身影只出现在 Controller 层,但实际工作中,DTO 也会经过 Controller 层流转到 Service 层,这就导致上游的业务已经侵入到本域了,违背了低耦合。
细节二:Controller 层写业务
Controller 层有个原则,我们只做流程编排、参数转化、事务等事情,绝不写业务,但实际的工作中,这个原则也经常被忽略,我们常常看见 Controller 层被大量的业务逻辑充斥着,这就导致了业务逻辑从 Service 层转移到了 Controller 层,导致业务逻辑分散在两个模块上,违背了业务内聚的原则。
那么这种情况在 DDD 中是如何改善的呢,我看过网上一些同学写的 DDD 代码,即使使用 DDD,只是把两层换了名字,换成了 app 层和 domain 层,但这种问题仍然没有解决,特别是在业务特别复杂的时候,问题还是存在,于是我一直在想,究竟是那里做的不对?
这个问题的根源在于 Controller 层和 Service 层的边界没有划分清楚(这里说的边界是技术边界,业务边界很多同学都划分的清楚,但写代码的时候,代码完全体现不出来业务边界),那么可能同学会说,哈哈,其实我也知道 Controller 层很薄,没有业务逻辑,业务逻辑都在 Service 层,但团队越来越大,业务越来越复杂,这个边界也随着划分不清楚了,慢慢的失控了?
是的,会失控的,但也有办法可控,我们在实践中通过以下两种办法可控:
- 代码自动生成,生成的业务代码已经规定好了业务约束,开发人员不会乱写。
- app 层和 domain 层的边界定义了约束,app 层只能够调用 domain 层的实体行为、聚合行为和领域服务三种领域元素,其他领域元素都不允许出现在 app 层。
自动生成业务代码保证了项目的整体技术框架,可以按照设定的走,不会随着团队规模的扩大而乱掉。
app 层只能调用 domain 层三种接口,也是通过代码自动生成来控制的。
通过这样子的技术约束来固定了代码架构,从而体现业务边界。
有的同学可能会问,这样子做,技术会不会去影响业务?肯定是不会的,我们是先设计领域模型,然后用对应的代码去实现领域模型,这样的技术只会让业务在代码中得到最大的体现。
只要你知道领域模型和代码是如何一一对应的,你会发现,看代码就像看业务文档一样。
DDD 能做到这一点,主要是因为 DDD 将领域层进行了细分,比如说领域对象有实体、聚合,动作和操作叫做领域服务,能力叫做领域能力等,而 MVC 架构并没有对业务元素进行细分,所有的业务都是 Service,从而导致 Controller 层和 Service 层很难定义出技术约束,因为都是 Service,你不会知道这个 Service 是用来描述对象的?还是来描述一个业务操作的?
DDD 将领域层进行了细分,是我个人觉得 DDD 比较 MVC 框架的最大亮点。
我认为能做到以上 2 点的 DDD 业务框架才会比 MVC 好些,否则的话,在 app 层和 domain 层之间,两者并没有差别!
domain 和下层间的高内聚低耦合在 MVC 中,Service 层的下层是 Model 层,Model 层是数据层,我们可以简单理解成 Mysql、es、Redis 等等,通常由 Model 层定义一个接口,Service 层去调用,那么这时候问题来了,Model 层的改动会不会影响 Service 层?
会的,肯定会的,Model 层的改动肯定会影响 Service 层!但这还仅仅是技术上耦合的地方,并不是致命点,致命点是这种依赖会导致业务的耦合!
假设现在 Model 层由 A 来维护,Service 层由 B 来维护,那么 Model 层的接口将由 A 来定义,A 定义出来的接口,应该是按照 Model 层的标准来的,然后 Service 层会去调用 A 的接口,那么问题来了:A 的接口是 Service 层想要的么?符合 Service 层的业务发展需要么?
一个两个可能是,但如果是一百个呢?答案肯定是否定的,这时候 Service 层只能去适配 Model 层的接口,是不是很奇怪,核心业务居然要去适配底层数据的储存结构?这样做的系统,就是大家都说的数据模型驱动的业务系统,是以数据模型出发生产出来的业务,而不是以实际业务出发生产出来的业务,这句话有点点拗口。
为了解决这个问题,DDD 提出了非常棒的解决方案:依赖调用!
MVC 是 Service 层依赖 Model 层,DDD 却完全相反,domain 层的下游都需要去依赖 domain 层!
直白地说,就是 domain 层如果需要什么,就自己去定义接口,然后下游去实现,因为接口是自己定义的,所以业务是内聚在 domain 层,然后 domain 层也不会去耦合下游的业务。
有的人会说,这不就是一种依赖抽象么?我理解绝不是这么的简单,我理解这是前辈在 MVC 的痛点基础上,想出来的通过一种技术手段来解决了模块之间的边界问题,是架构间的慢慢演化,发现目前架构的痛点,并通过演化,提出一种新的架构思想,这是很了不起的,绝不是什么依赖抽象这样的大话。
这里面其实体现了一种思路:我们是可以通过一些技术约束来划清出领域的业务边界的。
前面所说的 app 层和 domain 层的两种解决方案,我也是借鉴了这种思路,技术不能去影响业务,但能够去反哺业务的。
代码就不上了,都是很简单的实现,大家可以去星球自动生成代码看看。
领域层的富血模型分层说的是领域层和其他域之间的关系,用国家来形容的话,说的就是外交,接着我们要来说下自制,领域层内部各个领域元素之间有什么关系,又该如何管理呢?
在此之前,说下 DDD 中三个概念,贫血模型、富血模型、充血模型,三个概念都是用来描述对象状态的。比如说这个实体很贫血,这个聚合充血,而我们的目标是富血,直白来说,贫血就是该做的事情没做,没有尽职;充血就是做的事情太多,把不该做的事情也做了,管的太多;富血就是做的事情刚刚好,我只做了我该做的事情。
为了达到富血模型,我们认为两点是特别重要的:
- 领域元素的定义要清楚
- 领域元素的边界要清楚
DDD 中的领域元素其实还满多的,有实体、聚合、领域能力、值对象、领域服务、应用服务、工厂、仓储等。
要想搞清楚,可以先看看 DDD 经典书籍:《领域驱动设计:软件核心复杂性应对之道》,其他理论派的 DDD 书籍个人都不推荐,最最关键的是需要实战,我基本上也是在实战了公司 2~3 个项目之后,才慢慢对这些领域元素有较深的理解了。
领域元素的边界要清楚边界主要有两个思考的出发点,包含关系和逻辑关系。
其实如果你真的对领域元素的定义理解透了,其实领域元素的边界也出来了。
- 比如说聚合是来管理实体间一种固定的业务关系的,聚合和实体是一对多的关系,聚合会包含多个实体。
- 比如说值对象是用来描述对象的,那么值对象只可能用来描述实体,而不是聚合。
- 比如说领域服务是一种操作或动作,你会发现领域服务在边界解耦,组装领域能力方面也有很大的作用。
领域元素的定义才是非常关键的,吃透了,领域元素的边界自然就清楚了。
篇幅有限,就到这里了,本文其实一点点细的东西都没有说,基本说的都是一种思路,让大家对 DDD 有个简单的认识,对文中有描述感兴趣的同学,可以加我微信:luanqiu0,或者关注知识星球:DMVP,知识星球手把手带你实操 DDD。
最后放一段无声视频,作为一个彩蛋(这段视频才是本文的重中之重),给大家展示下,我们目前快速落地 DDD 的一些工具(编辑如果把视频剪掉了,发文前请提前通知,感谢)。
视频 6 分钟,较大,请勿使用流量观看。
视频点击此处
本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。
阅读全文: http://gitbook.cn/gitchat/activity/5cef33ceec085b26a117f2d3
您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。