27 | 领域驱动设计:如何从零开始设计一个软件?

你好!我是郑晔。

在前面的内容中,我给你讲了软件设计的各种基本工具。我们学习了程序设计语言,知道怎样把设计更好地落地;学会了各种编程范式,知道了可以用什么样的元素去做设计;我们还学习了设计原则与模式,知道了怎样组合分解出来的各个元素。

工具都有了,理论也武装上了,那么我们该如何实践呢?或者说,我要去分解组合的东西是从哪而来的呢?这就需要你对设计方法有一个基本的认知了,也就是说,我们要理解,在真实世界中,解决具体问题是怎样的一个过程。

那从这一讲开始,我们就来谈谈设计方法的话题,一起了解一下设计的基本过程。

首先,你知道哪些设计方法呢?

我知道的一种做法是,有些人一上来会先设计数据库,因为他们觉得,程序就是数据加函数。数据呢,就要存到数据库里,剩下的就是根据需要对数据库表进行增删改查。但是从我们之前的讲解中,你可以看出,这种思路实际上是一种结构化编程的思路。

后来有人就用面向对象的思路,先来找实体,也就是对象,当然这些实体也要有一些能力。最终,这些对象还是要写到数据库里,同样也是要提供增删改查的能力。

你看,这两种做法本质上没什么太大的区别,都是围绕着数据在做文章。在业务需求不复杂的年代,围绕数据做文章的做法还能满足开发的要求,但随着软件日益深入到人们日常工作和生活中,软件变得越来越复杂,这种做法就越发显得笨拙了。

对,软件会越来越复杂的。当软件变得复杂起来,如果我们靠着程序员们本能的做法,就会遇到各种问题,所以,很多人探索了不同的做法。

在诸多的探索之中,有一种做法逐渐脱颖而出,它成功地解决业务软件开发中遇到的大部分问题,这就是领域驱动设计。虽然它不是万能药,但对大部分人面对的场景而言,它都能够有效地应对。

领域驱动设计

领域驱动设计(Domain Driven Design,简称 DDD),作为一个新的设计方法正式登上历史舞台,是从 Eric Evans 的著作《领域驱动设计》正式出版开始的。

这种设计方法通过使用通用语言,让业务人员加入到设计过程中,拉近了业务人员与开发人员之间的距离,打破了组织的藩篱。同时,还提供了一套标准的建模方法,帮助团队识别业务模型,避免程序员犯下一些低级错误。

按理说,这种设计方法这么好,应该很快流行起来才对。然而真实情况是,很多程序员都不知道 DDD,一个重要的原因就是 Eric 的这本书写得实在不怎么样。要想从中读出味道,你得比较懂DDD,但是,大多数人并不懂,这就是矛盾的地方。所以,DDD在很长一段时间都被埋没了。

不过,后来,随着微服务的兴起,人们越发认识到,微服务的难度并不在于将一个系统拆分成若干的服务,而在于如何有效地划分微服务。这个时候,人们发现,DDD才是最恰当的指引。关于微服务和 DDD 之间的关系,我在《10x 程序员工作法》里已经讲过了,有兴趣的可以去了解一下,这里就不再赘述了。

现在,你已经知道了DDD的好,准备学习DDD 了。但你只要一打开 DDD 的书,一大堆名词就会扑面而来:限界上下文、聚合根、实体、值对象,等等。我该如何下手学习呢?这是摆在每个DDD学习者面前最严峻的问题。

学习 DDD,就要从理解 DDD 的根基入手:通用语言(Ubiquitous Language)和模型驱动的设计(Model-Driven Design),而领域驱动设计的过程,就是建立起通用语言和识别模型的过程。

通用语言

通用语言,就是在业务人员和开发人员之间建立起的一套共有的语言。要知道,在从前的设计方法中,业务人员总是把问题扔过墙头,让开发人员去解决。可是,业务人员说的都是业务名词,比如:产品、订单等等,而开发人员嘴里全是技术,比如:线程、存储等等,二者除了最基础的几个概念之外,其他的内容基本是没法沟通的。所以,一道人为鸿沟就在开发人员和业务人员之间形成了。

我们在第1讲说过,软件设计是要在问题和解决方案架设一座桥梁,好的设计要更接近问题。开发人员对解决方案一端简直再熟悉不过了,但是对业务一端理解则通常不够充分。而通用语言所做的事情,就是把开发人员的思考起点拉到了业务上,也就是从问题出发,这就在一定程度上填平了那道人为的鸿沟。

通用语言是什么呢?就是这个业务中有哪些概念以及哪些操作。比如说,我要做一个电商平台,就要有产品、订单的概念。其中,产品就要有上架、下架、修改产品信息等操作,而订单就会有下单、撤单、修改订单等操作。

在业务人员看来,这里说的都是自己擅长的事情,自己就可以有更多的发言权。在开发人员的视角,概念就是一个一个的类,操作就是一个一个的方法,也很好理解。所以,有一套通用语言,双方皆大欢喜。

但是,通用语言是从哪来的呢?也就是说,如何设计通用语言呢?最简单的做法就是让业务人员和开发人员一起,找一块白板,把各种概念都写在上面。然后,双方重新进行分类整理。

这里面的重点是,让业务人员和开发人员在一起。如果只让一方出现,结果又会是原来的样子,因为你没法判断,这里面的语言对方是否听得懂。

这种做法很简单,但通常都不够系统,会存在各种遗漏。所以,有人探索出一种更正式的实践:事件风暴(Event Storming)。

事件风暴是一个工作坊,基本做法就是找一面很宽的墙,上面铺上大白纸,然后,用便利贴把识别出来的概念贴在上面。当然,前提依然是让业务人员和技术人员都参与其中。

这个实践之所以叫作事件风暴,因为它的关注点在于领域事件。领域事件是用来记录业务过程中发生过的重要事情,比如,作为电商平台的工作人员,你想知道产品是不是已经上架了,这个领域事件就是产品已上架;作为消费者,你会关心我的订单是不是下成功了,这个领域事件就是订单已下。

人们做了一个动作,都会关心做过这个动作之后的结果,所以,领域事件用的描述方式都是过去式,比如:OrderPlaced。

事件风暴这个工作坊主要分成三步:

  • 第一步就是把领域事件识别出来,这个系统有哪些是人们关心的结果。有了领域事件,下面一个问题是,这些事件是如何产生的,它必然会是某个动作的结果。
  • 第二步就是找出这些动作,也就是引发领域事件的命令。比如:产品已上架是由产品上架这个动作引发的,而订单已下就是由下单这个命令引发的。
  • 第三步就是找出与事件和命令相关的实体或聚合,比如,产品上架就需要有个产品(Product),下单就需要有订单(Order)。

至此,我们已经把最核心的内容找出来了。通常,在工作坊过程中,为了增强趣味性和清晰性,不同的概念会用不同的颜色的便利贴标识出来,比如,领域事件用橙色、命令用蓝色、实体/聚合用黄色等等。

其实,用不同的颜色建模,事件风暴并不是独一份。Peter Coad也曾提出过一种四色建模的方法:

  • 粉色表示时标性对象(moment-interval);
  • 黄色表示角色(role);
  • 蓝色表示描述(description);
  • 绿色表示人、地点、物(party/place/thing)。

他还写了一本《彩色UML建模》(Java Modeling in Color with UML)介绍这种方法。我在ThoughtWorks的前同事徐昊按照自己的理解,对这种方法做了一些更新,有兴趣的话,可以去了解一下

当然,这里的事件风暴,我只是描述了最简单的一个过程。在具体实施的过程中,还会有更多的细节。不过,最重要的还是,让不同角色的参与其中,让知识在所有人的头脑中进行构建,得到一个大家都认同的结果。

模型驱动设计

有了通用语言,接下来就进入模型设计阶段了。虽然有了通用语言,但是业务人员能够帮到开发人员的还是很少,他们只能告诉开发人员哪些模型是符合业务概念的。

但这么多的业务模型,该如何组织呢?怎样补全欠缺的模型,使之成为一个可以落地的方案呢?这就是开发人员要想办法解决的事情了。

也正是因为在通常情况下,业务模型数量众多,所以在 DDD 的过程中,我们将设计分成了两个阶段:战略设计(Strategic Design)和战术设计(Tactical Design)。

战略设计是高层设计,是指将系统拆分成不同的领域。而领域驱动设计,核心的概念就是领域,也就是说,它给了我们一个拆分系统的新视角:按业务领域拆分。

比如,我把一个电商系统拆分成产品域、订单域、支付域、物流域等。拆分成领域之后,我们识别出来的各种业务对象就会归结到各个领域之中。然而,有时候,不同领域的业务对象会进行交互,比如,我要知道自己订单的物流情况。所以,要在不同的领域之间设计一些交互的方式。

而战术设计是低层设计,也就是如何具体地组织不同的业务模型。在这个层次上,DDD 给我们提供了一些标准的做法供我们参考。比如,哪种模型应该设计成实体,哪些应该设计成值对象。

我们还要考虑模型之间是什么样的关系,比如,哪些模型要一起使用,可以成为一个聚合。接下来,我们还需要考虑这些模型从哪来、怎样演变,DDD 同样为我们提供了一些标准的设计概念,比如仓库、服务等等。

通过战略设计和战术设计,我们就可以把发现出来的不同业务概念各归其位了。

总结时刻

今天,我们讲了领域驱动设计,这是目前在软件行业内最符合软件发展趋势的一种设计方法,因为它把软件设计的起始点从技术拉到了业务。

学习领域驱动设计,我们要从通用语言和模型驱动设计入手。通用语言是在业务人员和技术人员之间建立一套共有的语言,开发通用语言的一种实践是事件风暴,这是一种工作坊,通过识别领域事件找到引发事件的命令,找出与事件和命令相关的实体或聚合,帮助团队建立通用语言。

DDD 的模型设计可以分为战略设计和战术设计。战略设计是高层设计,将系统拆分成领域,战术设计是低层设计,考虑如何组织不同的模型。

好,我们已经对 DDD 有了一个初步的了解。接下来的两讲,我们就分别来看看,如何进行战略设计和战术设计。

如果今天的内容你只能记住一件事,那请记住:建立一套业务人员和开发人员共享的通用语言

思考题

最后,我想请你分享一下,你们在实际工作中是如何与业务人员沟通的?欢迎在留言区写下你的想法。

感谢阅读,如果你觉得这一讲的内容对你有帮助的话,也欢迎把它分享给你的朋友。

精选留言

  • 沧浪之水

    2020-08-12 16:56:21

    如果把写代码比作搬砖的话,那么学习代码设计,无疑就是去蓝翔学了个挖掘机,效率大大提升。至于现在很多程序员追求的各种面试造的火箭,更像是一种企业管理知识,对于大部分搬砖的人,提升意义不大。
    作者回复

    这个比喻实在是太有特色了😂

    2020-08-12 18:49:10

  • jg4igianshu

    2020-07-31 07:36:59

    实体:在时间上有连续性,并且有唯一标识可以来区分的对象,具有生命周期和行为。
    值对象:用来描述事物的,不区分谁是谁的,不可变的对象,不具有生命周期和行为。
    作者回复

    学会抢答了😄

    2020-07-31 09:34:41

  • 桃子-夏勇杰

    2020-08-01 06:35:57

    郑老师,说了实话,《领域驱动设计》这本书一般人真是读不下去,应该耽误了不少人学习DDD。但是说实话,DDD真是太难学了,能把DDD在公司里运用起来,特别是战略设计,几乎就是一个组织变革,需要有同时能够影响业务合作又能引导事件风暴这个高难度的工作坊的人才。虽然像TW这样的公司已经在努力推广这种设计方法了,极客时间也开了《DDD实战课》,但是,离很多公司离这个设计方法还是很远的。DDD之后如何发展,郑老师怎么看?
    作者回复

    DDD其实不是难,而是概念多,缺乏一个结构理解它。另外,很多人欠缺的不是DDD,而是设计的基础知识。这就是我在做的尝试,先铺垫设计的基础知识,然后,再讲DDD,用一个结构化的方式帮你把DDD串起来。

    真正的难点是结合实际工作,把DDD完整地走一遍,看到代码就理解了。

    退一步,可以去看《实现领域驱动设计》的源码。

    2020-08-01 08:45:04

  • 阳仔

    2020-07-29 08:10:45

    总结一下
    理解DDD就要理解通用语言和模型驱动设计。
    通用语言就是要把业务人员和开发人员对具体业务概念和逻辑的理解达成一致,可使用事件风暴和彩色建模等方法建立通用语言
    模型驱动设计可以从战略设计和战术设计两方面入手,战略设计属于高层设计,将系统安装领域拆分;战术设计属于低层设计,考虑的是如何组合业务模型
    作者回复

    总结得好!

    2020-07-29 08:48:45

  • 蓝士钦

    2020-07-29 08:51:19

    领域驱动设计可以和传统的面向数据库设计的方式结合吗,比如引入一个model模块用来聚合模型
    作者回复

    先按照一个思路走,否则,你会回到老路上去的。

    2020-07-29 10:25:54

  • Jxin

    2020-07-29 12:52:56

    1.各自阐述理解。
    2.消除分歧。
    3.达成共识。
    作者回复

    总结得好。

    2020-08-10 18:51:55

  • escray

    2020-09-27 19:51:43

    以前大概也是从“面向数据库编程”到“面向对象”,打引号是说并不是真正的面向对象。

    现在的领域驱动设计可能更像十几年前的面向对象设计,流行,但是大多数用的不对,面向对象现在也还是假装使用或者错误使用的更多一些。

    敏捷编程也提倡业务人员和技术人员坐在一起,有没有通用语言和模型驱动可能并没有那么重要。而领域驱动设计可能是这种方法论的理论拔高,另外似乎是针对更宏观的系统。

    专栏里面说,领域驱动设计是“软件行业内最符合软件发展趋势的一种设计方法”,不是最流行的或者最主流的。

    留言里面有同学说,以前也提倡“说客户的语言”,这个似乎是技术向业务靠拢,而领域驱动设计似乎是更多要求双方相向靠近。

    以前的实际工作中,主要是靠技术人员去观察业务人员的操作,然后猜测业务人员的想法,然后做出来原型看是否满足需要;也有“空中架构师”不顾业务,随手拿来新名词、新技术,反过来要求业务去适应的。成本高是一方面,另外效果也差强人意。
  • 人间四月天

    2020-07-30 08:58:33

    领域事件风暴,使用的是事件,动作,实体,建立基本业务模型,我认为这是时序图,状态图的另外一种表达,战略设计,很像业务架构设计,按照领域职责去划分。
    建立一个好的平台,需要做好职责划分,配置团队,要不就是无尽的折磨。
    作者回复

    这个理解的角度是在新事物和已有知识建立起连接的角度,很有趣。

    2020-08-10 18:50:31

  • 加加林

    2021-04-11 10:37:26

    想请问一下,与业务人员交流,在互联网行业,传统的方式有文档以及用axure 高保真原型的方式向前后端开发人员甚至设计师展示产品的最终形态,尽量做到无遗漏无重复,怎么看待这种开发方式,或者说,互联网行业,与把传统行业信息化过程中的领域驱动设计的关系,和使用场景?
    作者回复

    这实际上需求层面,把需求澄清清楚,还不到软件设计。

    2021-04-21 22:03:01

  • giteebravo

    2020-08-14 07:58:21


    老师,再推荐一些建立通用语言和设计领域模型的方法和工具吧。
    作者回复

    难道已经有了这么多,还不够吗?精通一样工具,就已经很厉害了。

    2021-01-27 14:29:33

  • 峰回路转

    2020-07-29 12:28:00

    数据加函数用了好多年,现在终于想通了为啥 EF 有种 codefirst编程模式, 因为别人早就想通了 面向对象编程!!!
  • 花落菩提

    2021-05-27 08:05:48

    领域驱动设计学起来比较难,真正将DDD落地的公司比较少,第一个是相关概念比较多,比较偏理论,可以参考借鉴的真实案例和代码相对较少,很多网上的案例介绍得都不够深入,代码也比较少。第二是需要业务、产品、研发配合去实践。
    老师有没有推荐的DDD方面的案例,介绍得比较详细深入的,也有对应的代码可以参考学习的DDD的案例呢。
    作者回复

    DDD也是有专栏的,可以看看

    2021-06-06 18:35:35

  • 第一装甲集群司令克莱斯特

    2020-12-15 20:48:57

    我认为系统中,代表业务的模型是最有价值的,值得好好设计,方便维护和扩展!
    作者回复

    一个系统值得设计的真正原因。

    2020-12-18 08:37:39

  • 6点无痛早起学习的和尚

    2023-10-10 09:10:07

    通用语言+模型驱动设计
    通用语言 <== 事件风暴
    事件风暴 <== 三步走:找结果、找结果因X动作产生、结果和动作的共性名词
    模型驱动设计:利用通用语言材料进行战略设计、战术设计

    比较难在找结果和找动作,这两者的区别
  • 杜秀清

    2022-10-11 16:45:02

    UML建模:领域模型、分析类,再逐步过渡到设计类。前面偏业务侧,后面偏实现侧
  • ifelse

    2022-05-20 23:48:44

    建立一套业务人员和开发人员共享的通用语言--记下来
  • Nio

    2022-04-17 10:57:24

    现在公司的开发和业务沟通基本靠产品,所以很难受。
  • aoe

    2021-11-03 07:50:56

    彩色建模的方法很好,写文档的时候用emoji表情标记也可以快速找到自己关注的地方。
  • 磉盘

    2021-05-24 17:58:55

    感觉领域驱动设计传递的真正价值是:通过分离关注点解决复杂业务。重视业务,业务反应到软件设计编码,代码的迭代根据业务的变化而变化。
  • 磉盘

    2021-05-24 17:51:19

    个人理解,领域驱动设计不同于软件开发方式如瀑布模型和敏捷开发,它是一种思想或设计方法,解决的是需求到设计的问题,应对复杂业务的方法。

    最早提出应该是服务于面向对象设计开发,但这种思想更适合现在的微服务模式。

    领域驱动设计= 模型驱动设计,通过建立通用语言,找到模型,确定模型的细节,模型之间的关系。