研发深恶痛绝,业界持续热捧,DDD 到底是啥?

2023-10-23 18:20:10 浏览数 (2)

# 关注并星标腾讯云开发者

# 每周3 | 谈谈我在腾讯的架构设计经验

# 第7期 | 刘啸南:领域驱动设计四论 — 在江湖派与学院派之间架起桥梁

冯友兰治哲学,提出“照着讲”和“接着讲”的方法论。近两年,我断断续续梳理出关于领域驱动设计的两个 PPT:《领域驱动设计》和《领域驱动设计四论》。前者的内容主要是关于 DDD 经典著作的读书笔记,可视为照着讲,以证明自己学有所本,讲的不是野狐禅;后者则是在继承的基础上所做的创新阐释,可视为接着讲,发前人之未发。本文重点围绕《四论》展开,从四个方面梳理出 DDD 的整个逻辑脉络。

曾见郭象注庄子,却是庄子注郭象。一些领域驱动设计的拥趸者,如果看到本文的论述和自己的理解相左,丝毫不用奇怪,本文阐述的四论,不是我注六经,而是六经注我。

本文是 DDD 系列三篇文章的开篇,为你提纲挈领地梳理出 DDD 的核心脉络。在后两篇文章中,我们还将展开分享 DDD 的系统架构设计与核心概念、关键方法等内容。

在系统开发这一行摸爬滚打多年,开发过众多业务系统,也接触过各种技术流派,结合众多书籍,从技术风格上分为两派:

  • 江湖派:重实践,重个人领悟,以思想体系见长
  • 学院派:重规范,重系统训练,以方法体系见长

在技术社区里,热闹程度可谓冰火两重天,江湖一派热热闹闹,追随者众,耳濡目染,无师自通;学院一派则是养在深闺人未识,门可罗雀,亟待挖掘。

有趣的是,两派似乎从不往来,从两派的著作来看,江湖派偶尔涉足学院派的领域,但学院派决口不提江湖派的东西。看来在技术圈子里,同样也是文人相轻、道不同不相为谋,各自混各自的圈子。

作为一个互联网工程师,长期奋战在开发第一线,对这种技术分裂深感无奈,总感觉有座巴别塔横亘其中。本人结合自身长期在一线工作的经验积累、对各种经典著作的熟稔以及对软件开发的持久兴趣,试图通过本文在两派之间架起一座桥梁,相互借鉴,熔于一炉。

从《领域驱动设计·软件核心复杂性应对之道》(英文版)在2004年第一次发表算起,到现在已有近20年,期间阐述 DDD 的书汗牛充栋,但 DDD 具体包含哪些内容,似乎从没有达成共识,有种包罗万象的感觉。本文围绕 DDD 的经典著作,参照学院派的风格,将 DDD 概括为四部分:

  • 结构论:战术建模与战略建模。
  • 过程论:系统重构与软件工程。
  • 语言论:基于模型的统一语言。
  • 建模论:模型驱动的领域建模。

这个划分,其实体现了本人长期的实践体悟和理论思考,并不是随意为之。王阳明晚年将其毕生所学,浓缩为四句话:

  • 无善无恶心之体。
  • 有善有恶意之动。
  • 知善知恶是良知。
  • 为善去恶是格物。

本人将 DDD 概括为四论,不是要盖棺定论,只是希望去掉枝叶保留主干,避免 DDD 逐渐沦为一种坊间流传的野狐禅。

  • 复杂度

《没有银弹:软件工程的本质性与附属性工作》(No Silver Bullet—Essence and Accidents of Software Engineering),1986年发表一篇关于软件工程的经典论文,打开了复杂度这个潘多拉魔盒。论文中 brooks 把失控的、复杂的软件项目比作中世纪的狼人,只有银弹才能杀死它。但是由于软件开发的本质复杂性,使得真正的银弹并不存在,即没有任何技术或管理上的进展, 能够独立地许诺十年内使软件系统项目生产率、 可靠性或简洁性获得数量级上的进步。

到现在几十年过去了,狼人依旧未被杀死,人月依旧是个神话,银弹在哪?

复杂度也许永远不能消除,但我们可以分析复杂度,进而管理复杂度。在软件开发领域,大体上可以将复杂度分为三类:软件本身固有的复杂度,业务逻辑带来的复杂度,以及技术复杂度。

  • 应对之道

从我看过的几本经典书籍来看,软件核心复杂性的应对之道,不外乎就是:抓住总体,理清局部。

  • DDD 与复杂度

DDD 提供了一些应对复杂度的具体方法:

  • 通过架构设计来分离业务复杂度和技术复杂度。
  • 通过限界上下文将一个大系统切分为若干高内聚低耦合的子领域。
  • 通过领域模型对业务领域的知识进行抽象。

  • Domain-Driven Design 还是 Model-Driven Design ?

在《领域驱动设计·软件核心复杂性应对之道》这本书里,对什么是“领域”,只有简单的一句话:“每个软件程序是为了执行用户的某项活动,或是满足用户的某种需求。这些用户应用软件的问题区域就是软件的领域。” 除此之外,讲得更多的是“模型” 或者“领域模型”,纵观全书,“Model-Driven Design”是其核心概念之一。某种程度上,把“领域驱动设计”改为“模型驱动设计”,一点问题都没有,甚至“模型驱动设计”更能体现整本书的精髓。

  • 领域划分与领域模型

总的来说,“领域”这个词可能承载了太多含义。领域既可以表示整个业务系统,也可以表示其中的某个核心域或者支撑子域。在本书中,我将尽可能地区分这些概念。当谈及到业务系统中的某个方面时,我会使用诸如“核心域”或者“子域”以示区别。

由于“领域模型”包含了“领域”这个词,我们可能会认为应该为整个业务系统创建一个单一的、内聚的、全功能式的模型。然而,这并不是我们使用 DDD 的目标。正好相反,在 DDD 中,一个领域被分为若干子域,领域模型在限界上下文中完成开发。事实上,在开发一个领域模型时,我们关注的通常只是这个业务系统的某个方面。试图创建一个全功能的领域模型是非常困难的,并且很容易导致失败。

  • 关于 DDD 的一个野狐禅

本人接触领域驱动大概已有10年时间,直到近期才听同事说起过“贫血模型”,然后在网上一查,原来是 martin fowler 在2003年发的一个 blog:AnemicDomainModel。其本意只是指出,很多人其实误用了领域模型,把领域模型的 Model 直接等同于 MVC 的 Model,导致 Model 里只有数据没有逻辑,fowler 给这种情形取了一个名字叫“Anemic Domain Model”。

Fowler 把这种误用概括为一种反模式 AnemicDomainModel,在流传过程中,有人把“Anemic Domain Model”翻译为“贫血模型”,后面又衍生出“充血模型”“失血模型”“胀血模型”,这些就都是穿凿附会之说,跟 Martin Fowler无关,跟领域驱动更无关系。很多 blog 都是把 DDD 跟MVC/DAO/ORM/TDD 放在一个层次来理解,这说明完全不理解 DDD,实际上 DDD 是跟 MBSE/SysML/UML 是一个层次的概念。

在领域驱动的经典著作里,完全没有 AnemicDomainModel 的相关提法,把 martin fowler 的说法穿凿附会乱加引申,将领域模型简单分类为“失血,贫血,充血,胀血”,这个大概就属于野狐禅了,完全背离了 DDD 的本质精神。

  • 战略建模与战术建模的划分

DDD 相关经典著作,一般都将 DDD 划分为战略设计和战术设计两部分。《领域驱动设计·软件核心复杂性应对之道》和《实现领域驱动设计》都明确有战略设计的提法,其他两本书则明确提出了战略设计和战术设计。

顾名思义,战略设计就是宏观设计,即对系统整体进行建模,称之为“战略建模”;战术设计则是微观设计,即对系统的局部进行细粒度的建模,称之为“战术建模”。

分享的一张图,很清晰:

  • 战略建模

战略设计原则必须指导设计决策,以便减少各个部分之间的互相依赖,在使设计意图更为清晰的同时而又不失去关键的互操作性和协同性。战略设计原则必须把模型的重点放在捕获系统的概念核心,也就是系统的“远景”上。而且在完成这些目标的同时又不能为项目带来麻烦。为了帮助实现这些目标,我们提出了战略设计的3大原则:上下文、精炼和大型结构。

理解大型系统的常用方法:

  • 使用隐喻,比如电动汽车其实就是一台电脑装了四个轮子。
  • 把大型系统从逻辑上切分成若干层,分而治之。
  • 把大型系统提炼为一个抽象结构,例如,冯诺依曼计算机=IO CPU Memory。

DDD 中的上下文(Context)是个让人迷惑的词,从一种比较宽泛的视角来看的话,Context 可以对应于 UML 的 class 或者 SysML 的 block,即 Context 可理解为是一个类或模块。ContextMap 则对应 UML/SysML 的 Relationship。

战略精炼:对核心域进一步萃取,过滤掉不必要的杂质,使得其方向更清晰,内容更准确、内核更精干。

战略精炼:相关方法可以分为三大类,即提炼出内核、进一步精炼内核、增强沟通。

  • 战术建模

战术建模侧重于从微观层面对系统进行建模。DDD 提到的战术建模方法主要是构造块与柔性设计。

构造块:在类、对象、组合、继承等层次上对系统进行设计。按照 DDD 的术语,我们可以把服务、事件、实体、值对象归类为原子块,把资源库、聚合、工厂归类为组合块。

柔性设计:列举了一些设计原则,类似于常见的软件设计原则,只是换了一种说法。 例如通过明确概念、避免概念过载、处理副作用等做法,得到的是一个高内聚低耦合的设计。

软件设计原则,说到底不外乎,模块内高内聚,模块间去耦合。下面是学院派总结的一些原则,可以对照来看。

  • DDD关于开发过程的论述

关于开发过程,《领域驱动设计·软件核心复杂性应对之道》这本书第三部分用6章的内容重点论述了重构,但对于软件工程,只是简单提及“敏捷开发过程”。整体而言,作者更倾向于用重构等手段不断迭代,开发人员和业务专家紧密配合,让协作贯穿整个项目的生命周期。本小节从下面三个方面对 DDD 的开发过程作一扩展性论述:

  • 通过重构加深理解
  • 敏捷开发过程与持续交付
  • 引入软件工程的规范方法
  • 通过重构加深理解

领域驱动设计的重点在系统设计阶段,但领域驱动设计同样将重构作为重要内容。《领域驱动设计·软件核心复杂性应对之道》这本书第三部分共6章的篇幅在介绍重构。看其内容,名为重构,实则仍然是设计,例如概念建模、柔性设计、分析模式、设计模式等等,基本都是一些偏宏观的设计内容。内容上,跟 Martin Fowler 的《重构:改善既有代码的设计》还是有很大区别。

论重构,当首推软件开发一代宗师 Martin Fowler 的《重构:改善既有代码的设计》,无人能出其右。

《重构:改善既有代码的设计》这本书的主要内容,可以用一个思维导图展示如下。

从这本书开始,”代码坏味道”成为开发领域的一个标准术语。在重构的过程中,有一些基于经验的原则可供参考。

就重构的具体做法上,可以从函数、数据、业务逻辑三个方面来展开。

某种程度而言,软件工程师仍然是手工业者,软件开发仍然没有银弹,重构仍然是软件在生长过程中不可或缺的调校手段。

因此,我们也不用迷信什么银弹,也不必忌讳什么过度设计与设计不足,通过多次重构迭代,让正确的设计逐步显现。

  • 敏捷开发与持续交付

在《领域驱动设计》出版的年代(2004),正值敏捷开发和极限编程大行其道的年代,《领域驱动设计》尽管不局限于某种固定的开发过程,但主要还是面向“敏捷开发过程”这一新体系。

二十年后的今天,敏捷开发和极限编程早已式微,但乔梁老师的著作《持续交付》给我们提供了新的视角。乔梁老师在其著作《持续交付》里梳理了软件工程的进化史。最近两三年,作为腾讯外聘高级管理顾问,其“价值探索-快速验证”的持续交付2.0双环模型,令人印象深刻。

  • 引入软件工程的规范方法

国内最新的一本著作《解构领域驱动设计》,作者试图参考 RUP,给 DDD 建立一种类似的规范过程。

《解构领域驱动设计》出版于2021,据了解作者张逸是业内知名 DDD 专家,也是《实现领域驱动设计》一书的审校。在《解构领域驱动设计》这本书里,作者试图将 DDD 与 RUP 融合起来,提出了 DDDUP(领域驱动设计统一过程)模型,能否成功,我们拭目以待。

《解构领域驱动设计》这本书很厚,500多页,值得一读,读完能体会到作者深厚的行业经验。

领域驱动设计统一过程(DDDUP)参考了统一过程(rationalunified process,RUP)的二维开发模型。整个过程的二维模型图所示,横轴代表推动领域驱动设计在构建过程中的时间,体现了过程的动态结构,构成元素主要为3个阶段(phase),每个阶段可以由多个迭代构成;纵轴表现了领域驱动设计在各个阶段中执行的活动,体现了过程的静态结构,构成元素包括工作流(workflow)和元模型(meta model)。

  • 统一语言

UBIQUITOUS LANGUAGE,有的书翻译为通用语言,有的书翻译为统一语言,其核心要点为:

  • 一个团队一种语言,语言统一才能沟通
  • 将领域模型作为统一语言的核心,基于模型进行沟通
  • 业务领域的例子

业务领域有业务领域的语言,不同业务有不同语言。下面是腾讯动漫的一个例子。

注:图片来源于内部同事的文章

  • 技术领域的例子

以下是几个技术领域的例子,不同层次有不同层次的语言。

在《领域驱动设计·软件核心复杂性应对之道》这本书里,明确提到了设计模式,这可以看做比编程语言更高一个层级的语言,提高了思维的抽象层次。

《微服务架构设计模式》和《面向模式的软件架构》在架构设计层面建立起了一套完整的模式语言,比设计模式再高一个层级。

  • 江湖派 vs 学院派

尽管在各自的著作中,两派是互不感冒,但在平时的工作中,通常不分派别,哪派管用用哪派。在诊断方法上,中医只能讲出望闻问切,西医能讲的东西就太多太多。在系统建模这个事情上,学院派能讲的东西,无论深度广度还是精度,都远超江湖派。

  • Model 是结合点

DDD、UML 和 Sysml 的底层逻辑全都指向 Model,Model 是三种技术的结合点。

  • 限界上下文是理解 DDD 的钥匙

限界上下文(Bounded Context)是 DDD 关键概念之一,同时可能是 DDD 里面最令人迷惑的一个概念。可以说,理解了限界上下文也就理解了 DDD。技术上,我们可以把限界上下文建模为 UML 的 Object/Class,或者是 SysML 的 Block,理解了这一点:Bounded Context = Object/Class = Block ,所有秘密迎刃而解。

有趣的是,《解构领域驱动设计》的作者,对限界上下文的理解也经历了一个禅宗式的参悟的过程:参禅之初,看山是山,看水是水;禅有悟时,看山不是山,看水不是水;禅中彻悟,看山仍然山,看水仍然是水。

大概所有研究 DDD 的人,都会经历类似的过程吧。从这个角度看,把一种技术方法讲成玄学,需要学习的人去慢慢参悟,这很难说不是一种退步。学院一派虽然繁琐但至少规范,而 DDD 在术语上的模棱两可进而导致很大的解释空间,让学习的人经常误入歧途,这是 DDD 的缺点之一。

  • UML/SysML 是重装备

学院一派,仍在不断精进,从多年前发展起来的 UML,到新近在其基础上发展起来的 SysML,建模技术正在从软件行业逐步拓展到一般技术行业。UML 强绑定于面向对象,SysML 去除了这种绑定,因而可用于更广泛的领域。在软件开发领域,一般学会 UML 就可以了,如果所属业务不采用面向对象开发模式的话,SysML 则是更适合的建模工具。

下面是一些用 UML 做的图,总体视觉效果看着还不错。

  • 架起桥梁

有一段时间,天天在琢磨 DDD/UML/SysML,脑子里各派的技术体系在翻江倒海地打架,偶然有一次把几幅图放在一起,突然间眼前灵光乍现,刹那间顿悟了,那一刻终于找到了解开密码的钥匙。从此困扰自己很久的 DDD 的几个核心概念,终于理清了头绪,完成了整个 DDD 大厦的最后一块拼图,感觉自己终于可以从路的这头走到路的那头,再从路的那头走到路的这头。注意下面这张图中左下角的表格,将 DDD/UML/SysML 之间的核心概念对应起来,三者之间的桥梁得以建立,从此一通百通。

DDD 自创立以来,到现在近20年,这期间 DDD 野蛮生长,逐渐变为一个杂乱无章的的技术丛林,一百本书有一百种讲法,包罗万象。而同样源远流长的UML/SysML/RUP 等专业方法,却逐渐式微。

DDD 为什么这么香?其中的奥秘在哪?就 DDD 本身而言,顶多算一种思想体系,远未发展为一种规范方法,其解释空间很大导致人们的发挥空间也很大,这就非常适合技术咨询界拿来做包装讲故事。相应的,UML/SysML/RUP 等专业方法,因为严谨所以可发挥的空间就少,最关键的是,UML/SysML/RUP 等都有版权保护,各自也都推出了自己的专业认证体系,这就阻止了众多技术咨询公司的进入。

在中文社区里,主要是众多咨询公司在热情推动 DDD,沉寂多年的 DDD 进而又重出江湖。最近新出的一些书,各种 DDD 培训课,就其内容而言,要么是引经据典照着书本讲,要么就是顶着 DDD 的帽子讲的却是 UML 的内容。总的来说,DDD 没多少新东西,只是把同样的东西,以区别于学院派的语言,重新归纳了一遍,从而看起来像是新的东西。例如,把概要设计讲成战略建模,把详细设计讲成战术建模,没有本质区别,听起来高大上而已。

本人学院派出身,对学院一派的技术更熟悉也更擅长,奈何江湖一派大行其道,从耳濡目染到深入研究,历时数载,若有所得,在方法论上认识到一点:以学院一派的知识体系为根基,用江湖一派的方法去讲故事,两派融合方可修得上乘武功。有点类似中西医结合,以西医打底,以中医行走江湖,虽不中亦不远。伟人曾说,自己对词的欣赏趣味是“偏于豪放,不废婉约”,我对不同技术流派的态度是“偏于学院,不废江湖”。

当人们都在做多的时候,我选择做少,正本清源,理清主脉。试图在江湖派和学院派之间架起一座沟通的桥梁,是我一直想做的事情,本文只是一个起点。

以上就是关于 DDD 的核心脉络,如果文章对你有帮助,欢迎转发分享。

-End-

原创作者|刘啸南

0 人点赞