领域驱动实战思考(二):用分段思想改进那些混乱的战略设计和战术设计

2020 年 1 月 21 日

领域驱动实战思考(二):用分段思想改进那些混乱的战略设计和战术设计

前言

2019 年,在我对DDD 进行基准化的过程中,遇到过非常多的挑战,其中一个便是: DDD 的设计过程,到底应该分为多少个阶段?每个阶段做什么事情?

这个困惑来自于书本上,以及其他咨询师在咨询过程中对于 DDD 设计和操作的差异:

  • 有的人会从电梯演讲和用户地图开始做设计和分析;
  • 有的人会从事件风暴开始做设计和分析;
  • 有的人会从子域开始做设计和分析;
  • 有的人会从限界上下文开始做设计和分析;
  • 有的人会直接从领域建模的聚合、实体、值对象开始做设计和分析;
  • 当然,还有的人会使用“名词动词法”直接从用例描述开始做设计和分析。

对于实际的学习者和使用者来说,这种混乱的操作手法所形成的不一致和不流畅体验,对于坚持进行 DDD 设计和减少吵架来说,简直是一种毁灭性的影响。

在这个过程中,最让我感触深刻的,是在于大家在落地 DDD 的时候,使用了大量具有“二义性”的词汇,讽刺的是,这与 DDD 所强调的统一语言是背道而驰的。

其中对于上述混乱影响最大的因素之一,就是大家对于 DDD 的“战略设计”和“战术设计”认知不一致(甚至说是混沌)的问题。

所以,这篇文章,就是围绕这两个概念,来说一说我是如何在基准化的过程中解决这个问题,统一并形成较为流畅的分段式的设计过程的。

一谈起“战略设计”和“战术设计”,你一定会听到“问题域”、“方案域”、“实现域”等名词。

我的同事肖然在经过反复理解和提炼之后,曾经提出过一个用于解释“问题与方案”、“战略与战术”的“元模型”:

这个模型其实对于具备多维思维和对 DDD 有着深入理解的人来说,是一种不错的可视化辅助理解手段(划分的合理性仅供参考)。但是在成为更多的咨询师做 DDD 咨询或培训时开篇必用的材料之后,事情就变得诡异了。因为这个元模型对于咨询师或者培训师是一个比较好的记忆工具,而不是用来给别人解释的适合的工具。

当没有意识到这一点的人在使用这个模型给客户解释的时候,会向客户描述成:“问题域的战略设计”、“问题域的战术设计”、“解决方案域的战略设计”以及“解决方案的战术设计”。

经过实战证明,这种方式不到 1 分钟,就能让客户及对 DDD 理论没有深入了解的人晕菜,屡试不爽。

它们是从哪儿来的?

在搞清楚这个问题之前,我们先要追本溯源,看看这些概念是从哪里来的。

问题域 / 方案域 / 实现域

什么是问题域?什么是方案域?什么是实现域?

非常有意思的是,以上名词在我搜索的过程中,能够搜到的结果绝大多数都是和软件系统的业务分析及领域驱动设计相关的,当然他们还有很多不同的翻译或者别称。具体到底最初是哪里定义了这三个词汇,我目前还无从查证。但仅从字面上来说,这是其实是一个非常自然的,人类解决问题的过程

我不想用书本上或者别人博客里的概念进行重复介绍,我更喜欢用我在做咨询的时候总是会用一个屡试不爽的小测试来让大家体会。

首先,我会问:

我:“我饿了。”

然后,我会让大家根据这个问题来接一句话去将对话接下去,这时候很多人会这样说:

客户 A:“你吃什么?”
客户 B:“走,一起吃饭去?”
客户 C:“你想什么时候吃?”
……

以上就是绝大多数人所习惯的基于方案而非问题进行回答的方式。但一个更正确的,体现问题驱动的答法应该是:

某善于问题驱动的客户:“你为什么饿了?”

这时候,接下来我的回答有可能是:

我:“我中午加班开会,时间比较紧,下午还要来给大家做工作坊,所以中午没吃饭,现在感觉有些低血糖。”

最后,得到的答案往往就会跟之前有很大不同:

客户 A:“要不要我给你找点吃的你先垫一垫?”
客户 B:“要不我们先暂停工作坊你去吃点东西?”
……

在这个例子中,包含几个不同的部分:

  • 待解决问题:我饿了
  • 问题澄清的结果:中午加班开会,时间比较紧,下午还要来给大家做工作坊,所以中午没吃饭,现在感觉有些低血糖
  • 解决方案:先垫一垫 or 暂停工作坊去吃东西
  • 实现细节:先吃块儿巧克力 or 去外面吃快餐

让我给予一个简单而非严谨的总结:

问题需要被澄清,问题澄清的结果决定解决方案,问题往往会有多种解决方案,每种解决方案的落地过程是实现细节。

那么,所谓的“问题域”无非就是“待解决问题的集合”,“方案域”便是“解决方案的集合”,“实现域”便是“实现细节的集合”。而 **“领域驱动”,其实就是“问题驱动”**——从澄清关键业务问题开始,逐渐寻找适合的解决方案,再到确定实现细节的过程。

战略设计 / 战术设计

说到“战略设计”和“战术设计”,这两个词的来源也非常有意思,让我们来说说。

(以下仅以国内翻译过的书籍来介绍)

Eric Evans 最初的解释

“战略设计(strategic design)”一词最早来自于 2004 年 Eric Evans 最初的《领域驱动设计:软件核心复杂性应对之道》一书的最后一个部分(第四部分:战略设计)。

他的原意是:

战略设计:一种针对系统整体的建模和设计决策。这样的决策影响整个项目,而且必须由团队来制定。

在 Eric Evans 的书中,战略设计会优先关注限界上下文以及限界上下文之间的协作关系,然后通过精炼的方式得到核心域和通用域等“模式”。而在整个书中,其实有关于统一语言和建模的细节都是在全书的前三个部分讨论的,

也就是说,在最初的这本书中,并没有“战术设计”一词,而 Eric Evans 是先说的“战术”后说的“战略”。因为他认为:

随着系统的增长,它会变得越来越复杂,当我们无法通过分析对象来理解系统的时候,就需要掌握一些操纵和理解大模型的技术了。本书的这一部分将介绍一些原则。遵循这些原则,就可以对非常复杂的领域进行建模。大部分这样的决策都需要由团队来制定,甚至需要多个团队共同协商制定。这些决策往往是把设计和策略综合到一起的结果。

换句话说,其实他先是介绍的“简单”的系统的领域驱动设计和建模方法,而在最后才介绍的是“复杂”的系统的领域驱动设计和分析方法。

Vaughn Vernon 的发展

2013 年,Vaughn Vernon 在实施了很长时间以“实现领域驱动设计”为核心内容的工作坊之后,出版了《实现领域驱动设计》一书,在这本书中,他按照从宏观到微观,从问题到方案再到实现的方式进行了叙述,发展并完善了 DDD 的理论和概念,当然也引入了“问题空间”和“解决方案空间”这两个词。

他认为,在 DDD 的“战略设计中”,还需要考虑“问题空间”和“解决方案空间”这样的维度,其中子域(他在“核心域”和“通用域”之间加入“支撑域”来避免两个极端)属于“战略设计”对于“问题空间”的分析方式,而限界上下文则是属于“战略设计”对于“解决方案空间”的分析方式。

至于“战术设计”一词,hum……当时还没出现呢……

那么“战术设计”这个词,到底是什么时候才出现的呢?

Scott Millett 与 Nick Tune 被“忽略”的澄清

说实话,“战术设计”这个大家看到现在基本都已经感受到是什么了的东西,到底什么时候由谁提出的,已经难以追溯,但是,至少有一本书出现了……

在 2015 年,Scott Millett 和 Nick Tune 出版了《领域驱动设计模式、原理与实践》一书,这本书更加全面和细致的介绍了在实现 DDD 的过程中的思考和方法。

然而很可惜的是,这一本书的中文版已经“绝版了”,说明至少在中国,大家的注意力都被《实现领域驱动设计》带跑了,逢人便提 IDDD(书名的缩写),却很少有人提到前述的这一本(当然,我强烈建议对照看英文版,中文版翻译的实在是……)。

在这本书中,作者将全书分为了四个部分:

  1. 领域驱动设计的原则与实践
  2. 战略模式:在限界上下文之间通信
  3. 战术模式:创建有效的领域模型
  4. 有效应用程序的设计模式

至此,在 DDD 的系列书籍中,终于出现了“战术设计 / 模式”一词,Scott Millett 和 Nick Tune 把领域模型的设计全部清晰的纳入到了“战术设计”之中。

为什么大多数人会难以理解?

好吧,大家能看到这里已经很不容易了,让我来为大家做总结并回答一下为什么大多数人会难以理解前述的两个维度的概念呢?

我是这么看的,之所以难以理解,问题来自于两个方面,让我们分别来说。

DDD“依然年轻”

DDD 虽然从 2004 年开始,截止到刚刚过去的 2019 年,已经有 15 年的发展,但是 15 年来,虽然软件架构和设计的新思想层出不穷,复杂度也越来越高。

直到 2014 年,以 Martin Fowler 为代表,在博客上彻底点燃微服务设计这个“燎原之火”之后,微服务所带来的软件系统的复杂度成倍提升才使得人们又重新关注,并开始根据新的形势认真思考 DDD 如何落地(到如今,几乎逢领微服务设计的书,必谈 DDD)。

从前面的介绍和几本书的内容发展我们可以看到,DDD 思想发展的时间跨越很大,语言和理解的统一也并不顺畅,很多概念的清晰化都是最近几年才密集出现。

尤其在中国,发展更为滞后。这个圈子里,有机会深入思考和深耕 DDD 规模化落地的人也不多。

概念理解有难度

让我们回顾这些概念和抽取其中的关键词语,我们会看到这两波东西是完全不同维度的东西:

  • 解决问题的层次:战略、战术
  • 解决问题的步骤:问题、方案、实现

之所以难以理解,是因为这些概念在现有的书籍、文章和实际操作中,是交织在一起的(可以想象一个概念的“大泥球”),而绝大多数人并不擅长(或者说并不熟练)以下三种思维方式:

  • 问题驱动的思考
  • 分层思维
  • 多维思考

所以我们需要甩开这些概念上的反复纠缠,以更加清晰的阶段划分和渐进式的方法来降低门槛,从而让大多数人能够更加容易的理解和入门。

更合理的“DDD 分段式设计”

为了更好的对 DDD 的设计过程进行优化,我们必须要重新审视 DDD 希望解决的问题:

软件核心复杂性

所谓复杂(Complex),按照 Cynefin 框架的解释是这样的(这里使用了我的同事李彤欣的版本):

“复杂系统:代表可能有,也可能没有解决方案的系统,充满未知和不确定性。因果关系往往在事后才能感知,刚开始可能毫无头绪。应对方式是探索 - 感知 - 响应。在允许试错的前提下,先做小范围实验和尝试,等待某些规律和指导涌现出来后,再来认知和评估,然后响应。”

而从我的观察和理解来说,这种复杂问题最直接的影响就是:其复杂程度已经超出了个人所能够理解、分析和解决的范围。我总结了三个针对“复杂性”的典型特征:

  • 业务流程长
  • 业务场景多
  • 业务概念多

要想解决复杂的问题,首当其冲的不是如何进行分析,而是引入更多的人,利用更多的大脑来共同解决复杂问题。当从一个人变成多个人的时候,那么问题就来了:别人的大脑长在别人的脑袋里,我怎么知道他想的是什么?他所理解的和我理解的一样吗?

正因为多人共同做事情时,会出现沟通和理解上的障碍,所以,DDD 给出了一个关键思想:统一语言(Ubiquitous Language)

而统一语言的方式,则是通过领域专家与技术专家通力合作,针对业务问题进行协作分析和设计,通过迭代式的探索、发现和碰撞得来的

所以,以统一语言为核心,通过协作设计的方式,对业务问题进行分析和澄清,就应该是 DDD 设计过程的第一个阶段。在这个阶段中,我们围绕业务问题、场景和流程来进行探索,通过限界上下文寻找概念二义性的边界,划分问题子域,从而在宏观(战略)层面降解系统理解上的复杂度。

我将这第一个阶段称之为:DDD 分段式协作设计的战略设计阶段

在这个过程中,我们首先需要使用“事件风暴工作坊”的方式,对业务流程进行系统实现视角下的抽象分析。然后,根据分析的结果,对领域概念的二义性边界进行识别,最终划定问题子域。

当这一宏观层的分析和设计结束后,我们就需要降低(细化)一个层面,对问题子域和限界上下文内部进行更细化的分析和抽象,通过领域建模的方式,将业务抽象为以“聚合(Aggregate)”进行封装和隔离的一系列领域模型进行承载。然后,基于系统实现的高阶视角出发,参考云原生的弹性边界需求等输入,初步识别出业务服务的划分,并且识别每个业务服务的对外接口能力。

我将这第二个阶段,称之为:DDD 分段式协作设计的战术设计阶段

需要注意的是,在前两个阶段,我们都依然是通过从宏观到微观(从战略到战术)的方式,对业务问题进行了分层的分析和设计。所以在这两个阶段中,我们是忽略了一切技术实现细节,以防止技术实现细节干扰“领域驱动”的。

同时,由于“协作设计”需要投入一定的人天进行集中式的封闭设计,所以在成本上难以长时间进行投入,而技术实现的维度和方式又是种类繁多和及其详细的(例如 API 详细设计、UML 设计、数据库设计、运维与部署方案……等等)。

所以,可以将前两个阶段的产出,交由根据“逆康威定律”划分的不同特性 / 产品团队,由这些团队进行进一步的详细分析和设计,最终予以实现。

我将这第三个阶段,称之为:DDD 分段式协作设计的技术实现阶段

至此,便有了更加平顺的 DDD 设计方法。

总结

根据实践中的思考,以“统一语言”为解决复杂问题的核心目标,以“协作设计”为关键手段,我将 DDD 的设计过程重新梳理和优化为了以下三个阶段:

  1. 战略设计阶段:对业务问题进行宏观分析和设计。
  2. 战术设计阶段:根据战略设计分析结果进行细化和建模。
  3. 技术实现阶段:根据实现需要进行细化分析和设计。

在这个过程中,主要的改进方式,首先就是利用分层的手法,通过聚焦从宏观到微观再到具体的方式,降低对抽象的分类方法的关注,化解对两类概念维度的交叉。然后,再通过定义每个阶段要解决的问题,内聚相关的分析方法,使得每个阶段都能有一些在阶段内闭环性的产出。

这样的话,因为阶段目标清晰且产出内聚,所以可以根据实际需要选择协作设计的深入程度——我遇到过有些客户,因为种种原因,只希望能够先快速的知道我需要根据 DDD 的思想拆分多少个模块,那么就可以先只做战略设计阶段。如果其中某个模块希望进一步细化指导建模和接口设计,那么就可以继续局部进入到战术设计阶段。最后,再根据实际需要去分团队进行技术实现。

我们一定要记住:DDD 是通过协作来达成统一语言从而应对复杂性的,组织背景下的软件开发是一种团队集体运动

所以基于以上观点,我会比较刻意的不提倡以下几种 DDD 的设计过程:

  • 先分析子域 / 先分析限界上下文:业务都未澄清,理解和语言都还没统一,怎么能拆明白?
  • 名词动词法 / 直接进行领域建模:大规模吵架和拍脑袋……

对于以上的分段式设计方法,经过了一年多的实战检验,证明能够被在绝大多数场景下适配和快速复用。在这里给大家分享了我在这个过程中的思考,抛砖引玉,希望能够有更多 DDD 的推动者提出宝贵的建议,共同推动 DDD 在中国的成熟和发展。

参考资料

作者简介

胡皓,ThoughtWorks 首席咨询师。十年以上软件开发工作经验。从士兵成长起来的软件技术顾问,从事过广泛的业务分析,项目管理,全栈软件开发、培训和技术咨询等工作。当前,作为 ThoughtWorks 中国区咨询 BU 数字化架构能力团队的负责人,正深耕于以演进式架构、领域驱动设计、云原生微服务为代表的数字化架构能力,帮助客户实现软件工程能力提升和数字化转型的目标。

相关阅读

领域驱动实战思考(一):用 TDD 思想对 DDD 的协作设计过程进行基准化

2020 年 1 月 21 日 09:30 2445

评论

发布
暂无评论
发现更多内容

架构师训练营第三章总结

吴吴

架构师训练 - 第三周作业

X﹏X

第03周 重构软件代码 命题作业

Jaye

游戏夜读 | 玩游戏能得到什么?

game1night

GoF 23种设计模式之单例模式

无心水

架构师 单例模式 极客大学架构师训练营 GoF 23种设计模式

「架构师训练营」单例与组合模式的应用

Amy

极客大学架构师训练营 作业 第三周

第三周-总结

铁血杰克

第3周学习总结

Glowry

极客大学架构师训练营

第二周

Geek_zhangjian

架构师训练营 -week3- 学习总结

晓-Michelle

极客大学架构师训练营

【架构训练 Week03 作业】Review

Rex

【架构训练 Week03 作业】

Rex

第三周-作业

铁血杰克

Week3 - 总结

Coder

极客大学架构师训练营

本周学习总结

Geek_zhangjian

Java反射与内省(参考小米内部资料)

知春秋

Java 反射 内省

第03周 重构软件代码 学习总结

Jaye

架构师训练营第 0 期第三周学习总结

无名氏

架构师训练营 Week 03 作业

Wancho

2020-06-20-第三周作业

路易斯李李李

week3-作业一

Coder

架构师训练营第三周作业

Bruce Xiong

架构师训练营第三周作业

Java 极客大学架构师训练营

第三周总结

大雄

架构师训练营第 0 期第三周作业

无名氏

单例模式 组合模式

第三周作业一

潜默闻雨

设计模式

ashuai1106

设计模式 架构师 极客大学架构师训练营 23种设计模式

架构师训练营week03 总结

天上星亮晶晶

架构师 极客大学架构师训练营

架构师训练营第三章作业

吴吴

第三周作业

大雄

架构师训练营-W03S-代码重构

BlazeLuLu

极客大学架构师训练营

领域驱动实战思考(二):用分段思想改进那些混乱的战略设计和战术设计-InfoQ