DDD 领域驱动设计剑指何方 - 关于软件复杂性的讨论
领域驱动设计(Domain-Driven Design, 下文简称DDD)自从2004年被提出以来,在业界一直雷声大雨点小。然而随着微服务的兴起,最近DDD受到越来越多的关注。借着这个机会,我重读了 Eric Evans 的同名著作《Domian-Driven Design: Tackling Complexity in the Heart of Software》,并观看了Eric 从2015年到2019年的相关演讲。本文不打算重述书中的核心概念(网络上已经有很多优秀的博文对Eric的书进行总结),而是着重讨论以下一些问题:
- 为什么会提出DDD?什么是软件复杂性?
- DDD 是如何应对软件复杂性问题的?
- DDD 的局限是什么?
如果你之前没有接触过DDD,那么本文可以作为一道前菜,在你享用DDD这道大餐前,了解它产生的背景以及核心思想。
如果你已经是一位资深的DDD实践者,那么请将本文内容作为一个参照,思考是否DDD帮助你在项目中解决了相应的问题。
由于作者水平以及经验有限,错误在所难免,希望大家可以善意地指出,友好的讨论。比对与错更重要的是,思想的交流。
软件为什么变得复杂?
软件的核心
在讨论一切之前,我们需要给软件的核心下一个定义。倘若我们没有对这个定义达成一致的观点,那么后续的讨论就容易失去方向。这里我借用Eric 的定义:
软件的核心是能为用户解决特定领域(domain)相关问题的能力。
关于这个定义,需要注意以下两点:
-
解决问题的能力:一个软件的优秀与否,是根据它解决问题的能力来判断的。然而在现实中,我们却经常听到这样的对话:
- “这个软件很酷,因为它用了最流行的**架构。”
- “这是我见过最好的软件,因为它使用了二十多种不同的设计模式。”
- “不要眨眼睛,我们的程序运行得像流星一样快。”
- ...
这些陈述都表明我们对软件的核心定义存在分歧。优秀的架构,良好的性能都是为解决问题服务的。也许我们应该遵循那个著名的逻辑学法则:“能解决一个问题的最简单的方法,通常就是最好的方法。”
-
特定领域:软件解决的是某个范畴内的问题,根据问题领域的不同,软件对现实中的对象(例如人)进行了抽象。例如学校管理系统关注人的学习科目和成绩,而医疗系统关注人的身体健康情况(血压,血脂等等)。所以现实中的某个概念,在不同软件中会有不同的含义。
你的软件复杂吗?
尽管业界有很多关于软件复杂度的定量研究,例如McCabe复杂度,Cyclomatic Complexity 等等,然而我更倾向于相信项目成员的直观判断。假如你在工作中遇到以下场景,那么你所开发的软件也许过于复杂了:
- 开发人员说:“这个系统到昨天为止一直都工作的很好,我发誓什么也没碰,但是今天早晨就突然出问题了。” - 如果你不知道系统为何可以正常工作,那么当它不工作时,你也什么都不知道。
- 产品经理:“你能解释一下昨天发现的bug 吗?” 技术人员:“这很困难,要不你直接看代码吧?”
- 产品经理:“可以帮忙把这个对象的名字从A改成B吗?”技术人员挠头:“这大概需要花一个礼拜,因为我需要改模块M,而M依赖模块N,而N依赖…… 天哪!你可以给我两个礼拜吗?也许把系统重写一遍更容易一些。”
- 开发人员:“我昨天删了2000行代码,你猜怎么着?系统依然可以工作……”
- 开发人员:“too many magic……”
- 开发人员:“我不太清楚……”
什么让软件变得复杂?
很多因素可以让软件越来越复杂,这里总结了其中重要的几点:
-
领域问题的复杂性: 当一个问题的业务逻辑很复杂时,其对应的软件系统也不可避免得复杂。正如 Fred Brooks在《没有银弹》一文中所说:“软件活动的根部任务是:打造由抽象软件实体构成的复杂概念结构。”
-
对领域知识的忽视:在我们的教育体系中,无论是软件工程还是计算机科学专业,都普遍关注教授学生如何与机器打交道。这带来了一种假象,似乎一个合格的软件工程师只需要通过C++(或者Java,Python,GO……),数据结构,算法导论,计算机体系结构等等一系列科目考试。还记得软件的核心是什么吗?是能为用户解决特定领域相关问题的能力。假如不了解问题,那又如何解决问题呢?一个不懂会记知识的工程师能实现出优秀的财务报表软件吗?不能,大多数情况下,他会设计出一个让别的工程师(而不是会计)使用的“财务软件”。
-
领域和技术之间的鸿沟:在传统的软件开发中,领域专家或者产品经理总结出一系列的产品需求,然后扔给开发人员去实现。然而经常发生的情况是,领域专家想的是需求A,写出了需求B,开发人员理解的是需求C,最后实现出了需求D。隔行如隔山,不同背景不同经验的人对同一事物的理解会有非常大的差距。这导致所有人都觉得自己做了对的事情,然而最后的结果谁都不满意。
-
不分主次:有时候,开发人员、设计师或者运维人员都认为自己做的那一块是最重要的(是啊,谁愿意做边角料呢?),所以他们把自己负责的模块设计的尽可能复杂,以追求完美。我在微软犯过同样的错误,一位头发花白快退休的程序员告诫我:“perfect is the enemy of good enough.” 能用最简单的方法解决必要的问题,才是最酷的。我们需要接受这样一个观念,我做的东西很重要,但是在这个项目中,它也许不是最主要的。
-
软件改进缺乏规范:软件开发是一个迭代的过程。很多时候,软件的第一个版本很漂亮,代码简洁,模块间的关系清晰。但在不断的改进过程中,在破窗效应的影响下,越来越丑陋,越来越复杂。防范破窗效应的最有效的方法,就是不让第一扇窗子被打破。
DDD 领域驱动模型
说了这么久,究竟DDD是什么呢?DDD 领域驱动模型是一种关于如何控制软件复杂度的方法论。下面我们就接合上一节讨论的内容,来看看领域驱动模型包含哪些核心理念。
- 领域模型(domain model):模型是对现实的片面抽象。领域模型就是根据某一领域的特点,对现实中对象的属性进行取舍,从而呈现出的更简单的对象模型。例如在学生管理系统中,对于学生这个对象,我们不会记录他的样貌,体型,社会关系,宗教信仰等(因为在教学这个领域,这些属性不重要),而只记录了姓名、性别以及学习成绩等重要的属性。
- 统一术语 (Ubiquitous Language):我们需要在领域专家和开发人员之间建立统一的术语,使得大家对领域内的知识有一致的理解。这一实践带来以下几点影响:
- 开发人员不得不重视领域的知识,从而理解术语的具体含义。
- 领域专家和开发人员之间需要强有力的沟通,消除那些交流中模棱两可的概念。
- 促使领域专家和开发人员反复提炼领域中的知识,以创造出简洁、正确、易于理解的术语。
- 界限上下文(Bounded Context ): 任何一个模型或者术语都有自己的适用范围。例如,我说“请开门”,这里的门指的是什么?当我站在一辆公交车前,它指的是车门;而当我站在一个公寓前,它指的是房门。为了能对模型对象有统一的理解,我们需要限定模型所适用的环境,也就是上下文。
- 模型驱动设计(Model-driven Design):软件的开发需要和领域模型相结合,这要求:
- 在开发软件前,我们需要明白这个软件的主要任务是什么,次要任务是什么。
- 每个领域模型都能明确地映射到软件实现中的元素(类,接口,函数等等)。Eric 提出了一个方案,将领域模型映射到软件中的 实体(Entity),值对象(value object)以及服务(service)中。
- 领域模型需要和软件实现保持一致。例如当改变软件的实现时,领域模型也需要更新,以反映这个变化。
DDD 的局限
尽管DDD在业界影响力很大,但是在实际运用中依然还是有局限的地方。
- 很难找到完美的统一语言。找到一种让不同背景不同经验的人理解一致的语言是非常困难的。在这一点上做的最好的就是数学领域,如果你曾经接触过《数学分析》或者《高等代数》,你会发现这些书里对每一个概念都给出了精确的定义,保证不同民族,不同文化背景下的人都对它们有一致的理解。然而我认为在软件领域很难做到这一点,因为:
- 数学讨论的是理论问题,而软件需要解决的是现实中的问题。数学的问题域相对来说比较稳定,所以创建严谨的定义语言比较容易。
- 数学语言经过了长达百年的充分沉淀,凝结了许多伟大数学家的天才智慧。而在软件领域,尘土飞扬,机会转瞬即逝,大家都在和时间赛跑,对于领域知识的沉淀是一件奢侈的事情。
- 数学是一种科学,而软件开发是一种实践。科学的世界许多东西非黑即白,概念之间界限明确;而实践的领域却充满了灰色地带,这里没有对与错,只有合适不合适,从而留给了人们很多模糊的值得讨论的空间。
- 面向对象不是唯一的模型:尽管面向对象设计风光无限,但是它并不适用于现实中的所有问题(例如并发问题)。DDD 的一个核心观念是将现实领域中的问题对象化,并映射到软件开发的元素中去。当一个问题很难被对象化时,使用DDD就比较麻烦。
- 规范和自由的矛盾:DDD 希望领域模型和软件实现中的元素有明确的映射,当任意一方产生变化时,另一方也需要更新以适应这个变化。这个想法很好,但是在实际中操作很难做到。过多的规范,是以效率为代价的。更多时候,我们是在规范和自由之间找到一种平衡。
写在最后
最后我想强调的是,DDD 并不是一个完美的设计规范,它只是提供了一种方法论,帮助我们有效地控制软件的复杂度,让软件更容易理解,更适应变化。Eric 在书中提到的Entity,value object, service, aggregate, repository, supple design, distillation 等等都是具体的手段。我们不应该执着于套用具体的手段,而是应该花时间充分了解我们的工作领域,我们所处的组织架构,并以此创造出适合自己的手段。
浙公网安备 33010602011771号