跳到主要内容

从敏捷开发的角度入门领域驱动设计(Domain-Driven Design,简称DDD)

这并非一篇传统意义上讲解如何入门的文章,但如果想要真正理解和掌握DDD,我想这里是个不错的答案。很多人之所以觉得DDD难以理解和掌握,其实只是缺乏软件工程思维。而该文章则是针对该问题来讲述软件工程的价值,及其与敏捷开发DDD之间的关系。

统一语言(减少理解偏差)

用语
解释
价值观表示某事物行为的重要程度。目的是确定哪些行为更值得去做,或描述不同行为的重要性
软件(开发)过程对软件进行规划开发管理的过程。通常涉及将开发工作划分为更小、可并行或可连续的步,以此来达到产品管理改进设计的目的。
敏捷一套秉承持续改进紧密地与客户合作等理念的项目管理价值观(并非只适用于软件行业)。
敏捷(软件)开发基于敏捷价值观进行的软件开发过程。如业界有名的 Scrum极限编程(Extreme programming,简称XP)Kanban 等。
线性强调步骤遵循先后顺序。每个步骤都具有里程碑(即阶段性)意思。
可用(性)技术上来说,可用性指的是以用户角度进行观察,从请求发出到响应之间的时间间隔(间隔越短可用性越高)。从软件交付层面来说,个人认为更倾向于用户的满意程度。
方法论做事的方法和思路。只是一种指导,而并非具体的执行过程
精益一种追求卓越的价值观。通常体现为尽可能地减少浪费优化过程提高效率等。
模式一种针对特定问题被证明有效的通用设计原则惯用方案
问题域企业的现状(即软件开发的背景)。通常从问题域中我们可以得知软件需要解决哪些具体的问题。
分离子域基于业务领域知识来分解问题域。将大问题分解为多个小问题,是应对复杂性的有效方法。
概念模型用一组概念来描述一个系统,或用任何代替的形式来描述一个概念,以期能进一步了解或说明事物的运作原理。

1. 软件工程的价值

1.1 我眼中的软件工程

软件工程(Software engineering)计算机科学工程学的一个分支。换句话说,它是为了解决人类现实问题而存在的一门学科。

当人们去评价一个应用软件的好坏时,通常看的是它能不能满足我们的需求,以及操作起来是否方便,再者则可能是样子是否符合审美。 我相信这是一名普通用户在对应用软件好坏作出评价前进行的基本思考逻辑。 也就说,一个应用软件的好坏,主要取决于是否满足用户需求操作是否方便这两点。 前者很好理解,但后者通常会包含一些个人的主观意见。所以对于操作是否方便这点来说,它起码应该是一个客观评价(即得到大部份人认可)。 而我个人则更愿意用操作是否符合逻辑操作是否简单来代替它。

总的来说,个人为软件工程(Software engineering) 价值给出的定义是开发出满足(甚至超出)人类需求,且操作又方便,样子又符合现代人审美的软件

1.2 现实困境

然而,当你工作几年任职过几家软件公司之后就会发现。其实大部份从业人员的价值观或是他们正在做的事,都与我上面提及的软件工程价值背道而驰。 大部份公司(作为用户观察和了解所得)开发出来的软件都只是停留在能用(差强人意) 的层面,而且经常将关注点搞错。他们大致的问题有如下这些:

  1. 软件过程效率低下时,第一件想到的事是透过加班加点来完成手头上的任务,而并非改变不科学的工作方式。
  2. 在一个性能不敏感的系统中谈性能(面向性能的解决方案通常缺乏合理的结构设计,久而久之会导致整个系统的可维护性下降)。在一个问题域简单的系统中引入复杂的软件解决方案(过度设计是一些“欠缺架构设计经验”和“面向简历编程”的人最常做的事)
  3. 轻视设计的重要性,将重点都放在了写代码上。所以做出来的东西只能是差强人意,因为根本没有站在用户角度去思考问题。
  4. 项目知识缺乏管理和共享。一些重要的知识仅掌握在部分人手上(通常是老员工)。这使得新员工难以较好地开展工作,以及一旦这部分人离职就会对项目造成较大打击。
  5. 过度追求效率而导致项目变得极其难以维护。这种情况通常反映在缺乏单元测试缺乏设计技术债务持续堆积而得不到清理错判变更成本而采用开源项目作为开发基础等。
  6. 欠缺敏捷的价值观。问题总是到关键时刻才显现出来。总是手忙脚乱,不会做出什么好东西。
  7. 过度强调开发工具的重要性。编程是为了将设计落地,而非反作用于设计。所以用什么编程语言框架技术库其实并不重要;或者说,价值不大,不要将关注点搞错。

关于以上这些问题,我想敏捷开发可以帮到你。

2 敏捷开发

2.1 被唾弃瀑布模型

瀑布模型是一种线性的软件开发过程。其流程大致包含提出远景需求调研需求分析设计构建测试部署维护等阶段。

瀑布模型最大的问题在于反馈周期过长,软件公司通常会在测试完成后才会让客户进行验收。这使得设计和修改成本都大大增加。 设计者需要在构建阶段开始之前掌握所有的需求细节并完成设计,这无疑是一种极其困难而又不科学的做法。首先,甲方(出钱的人)未必真的十分清楚自己想要一个什么样子的系统(通常是知道要解决什么问题,但细节上无法确定),这使得需求的变数大大提高。其次,即使甲方开始就对自己的需求十分清晰和明确,然而你是否能确保他在此之后不会做出任何改变?我想没人能够给你一个这样的保证。或者有人会说,按照合同来办事就好。然而当你以软件开发为生时,这种态度可能会丢掉饭碗。因为只要做出来的东西不能让人满意,那么甲方就会转向其他公司或产品。如果甲方是你老板,那么你的重要性就变得更加可有可无了。

2.2 用敏捷代替瀑布模型

这并非一篇关于敏捷开发的文章,所以这里只会阐述一下敏捷的基本价值观,以及它如何解决瀑布模型的问题。 如果想进一步了解敏捷开发。建议去阅读《规划极限编程》1和《敏捷软件开发》2两本书。

严加上来说,敏捷只是一套关于精益管理的价值观和原则,它源自于《敏捷宣言》3。 明白这一点非常重要,因为现实中确实存在很多死脑筋,他们会认为敏捷就是Scurm,或者是XP。然而并非如此。

以下是《敏捷宣言》所阐述的价值观(并附上个人的理解)。敏捷宣言认为“右边”虽然有价值,但是“左边”的价值更大。

  • 个体和交互优于过程工具
    • 工作的是人,所以对于每个团队成员都应该给予尊重;良好的工作方式,其实就是成员平时的互动方式。这比遵循刻板、复杂的规章流程和使用工具来进行沟通更有价值。
  • 可工作的软件优于面面俱到的文档
    • 直接交付可用的软件(可用性越高越好),比撰写全面、繁复的文档更有价值。
    • ⚠️ 很多人认为敏捷就是不写文档,其实就是这里理解出了问题。所谓价值观并不是说非黑即白,而是“哪一个更重要一些”。而你完全可以选择性来做。譬如仅编写必须的文档;而且文档并非一定是正式的文件,它也可以是一张示意图一段聊天记录一封邮件等等。应该善于利用敏捷管理工具。
  • 与客户合作优于合同谈判
    • 与甲方紧密合作(如果可以的话,最好是同一办公室内一起工作),比独自履行合同义务更有价值。
    • 目的是促进沟通,减少对需求的理解偏差。可大程度地降低软件开发风险和修改成本。
  • 及时响应变化优于遵循计划
    • 根据实际情况来变更策略,比严格遵循计划更有价值。
    • 强调要为甲方争取最大的利益。为团队建立口碑,为软件增加竞争力。

当你理解了《敏捷宣言》所表达的价值观之后,就能理解我接下来的话了。但如果还是不理解,那么我建议你进一步去阅读敏捷宣言的十二条原则(它讲述如何更加具体地去工作)

敏捷开发本身并不是一个有固定流程步骤的软件开发过程。它强调的只是团队应该促进沟通(成员与成员、成员与甲方)寻找合适的工作方式交付可用软件来代替繁复的文档响应变化。 但也可以说它是一个持续精益的过程,因为只有不断反思和调整,才能得到一个适合当前团队和环境的软件过程。而这种思想,目前已被视为敏捷开发和解决复杂问题的核心技术,即小步快跑,快速迭代。事实上在敏捷原则中亦表明了这点,它建议采用短周期交付策略(即增量交付)。也就说,将整个瀑布模型拆分成多个短周期(通常是2~4周)。每个短周期都可以理解为是一个 小型的瀑布模型 。但因为粒度变小了(粒度和工作量成正比),所以即使期间遇到需求变更,其成本也相对少了很多。此外,因为与甲方保持紧密的合作关系,所以在每个短周期交付日都能够得到甲方的反馈,这使得做出一个高度符合甲方需求的软件变成了一件普通的事情。

3 认识DDD

3.1 敏捷是DDD的基础。若你不清楚这一点,则无法理解或运用好它

想较于瀑布模型而言,敏捷最突出的优势在于引入了增量迭代交付的策略; 加上与甲方保持着紧密的合作关系的价值观,这使得软件开发过程得到了一个及时的信息反馈机制。这使得错误处理需求变更设计优化等工作都变得简单了起来。 而这正正就是实施DDD的基础,因为DDD不仅要与甲方(领域专家)紧密合作,而且还需要依赖敏捷的增量迭代交付来得到合理的设计。

3.2 DDD的价值

DDD其实是一套解决复杂业务领域问题的方法论(由一系列模式组成)。 值得强调的是,它并不是一种架构;其本身的价值亦不在于代码层面的设计模式。 我发现很多人在这一点上存在极大的误区。以至于GitHub上出现所谓的“DDD脚手架”,这着实有点儿滑稽。

个人认为,DDD大体为软件工程带来了三种价值,分别是合理地运用资源运用模式将复杂问题分解交付符合用户期望的软件

  • 合理地运用资源:在DDD实践过程中,并不是非得整个项目都要遵循DDD方法论。你完全可以利用分离子域策略来精炼出核心域,然后仅在核心域上应用DDD。而其他问题子域,则可以交由资历较浅的开发人员去完成,或是直接外包出去。即仅需保证将大部份资源投放在核心域中即可。所谓的核心域指的是企业“当前”的战略核心(也可以理解为主要挣钱的那部分业务);之所以强调“当前”是因为企业的战略核心并不是固定的,特别是当一个企业打算大力发展其他业务的时候,这时就可能会同时存在多个核心域。

  • 运用模式将复杂问题分解:除了上面提及的分离子域策略外,DDD还提供了其他手段来化解问题域的复杂性。例如限界上下文(下面将介绍)、*聚合模式(不属于本文谈范围)*等。

  • 交付符合用户期望的软件:DDD中强调了模型驱动设计(下面将介绍)所带来的价值,即使用尽可能避免理解偏差的方式来进行软件开发过程。期间依赖于通用语言(下面将介绍)敏捷

3.3 DDD的核心概念

DDD中有四个重要的概念,分别是领域专家统一语言(Ubiquitous Language)4模型驱动设计限界上下文(Bounded Context,简称BC)

  • 领域专家其实就是敏捷中紧密合作的甲方。但这个甲方角色更加专业,他需要对某个业务范畴的知识有深刻的理解。此外,领域专家只是一种称呼,而并非某个固定职位的人。换句话,只要一个人对某个业务范畴有着深刻的理解,那么他就能成为团队的领域专家。自研项目中可能是产品经理业务分析师,企业软件则通常是甲方员工

  • 如果有人问DDD中什么最重要,我会二话不说地回答统一语言。因为贯穿了DDD的整个生命周期,而且深刻地影响着沟通建模设计编码等各个环节。建立统一语言的目的是为了减少理解偏差,使得相关人员在沟通和理解上达成一致。此外,统一语言在DDD中还有更深一层的含义,那就是模型驱动设计。模型驱动设计强调应该确保理解模型领域(分析)模型设计模型代码模型之间的一致,因为但凡可能造成理解偏差的点最终都会导致代码实现偏离真实需求(即墨菲定律5),从而降低软件工程价值。至于如何建立统一语言并没有一个规定。譬如可以与领域专家交谈时学习查阅相关资料浏览现有的遗留或同行系统从上下文中创建(但要得到领域专家认可) 等等。将其记录下来放在大家都显而易见的地方以便公告天下和持续改进。最重要的就是相关人员必须使用这套语言来进行业务沟通建模设计编码等工作,否者就会失去统一语言的价值。

  • 很多人都知道限界上下文能够胜任“良好服务边界”这一职责(通常我们谈论边界,其实都是在谈论职责的范围),但基本上没有人能有十足把握地给出一个完美的边界。个人认为软件开发中存在两大难题:命名边界划分。好的名字可以大大提高代码的可读性,使我们无需花费多余时间去理解代码实现细节就能得知其作用;好的边界会让职责变得明确,使我们很容易就可以得知应该往那里添加代码。但显然这两件做起都不简单。但此时使用敏捷就可以使两者都得到合理的处理。前者可以通过重构(XP/TDD的一种实践)来解决。后者则通过持续迭代来让它自然地形成即可。没做,是自然地形成。具体推荐做法是让边界先大后小,然后在持续迭代期间将不属于边界内的概念排除出去,再将缺乏的概念纳入进来。周而复始,慢慢就会得到一个合理的边界。这就是敏捷的力量,该方法适用于所有需要精益的活动。

3.4 统一语言(Ubiquitous Language)与BC的关系

为了避免不必要的歧义,这里直接使用Ubiquitous Language,而不是统一语言通用语言

其实在DDD社区中,Ubiquitous Language这个名字本身就存在一定的歧义。 因为事实上它并不是那么的通用。或者说,它应该叫“BC Ubiquitous Language”更为贴切。

它的歧义在于只适用于BC范围之内(,因此有人建议将其称为“模型语言6”)。 例如关于“吃饭”这个Ubiquitous Language,虽然作为一个人,大家都知道它名面上的含义,但如果具体问你是指吃什么的话,可能不同的人会有不一样的答案。 广东人通常是米饭,但东北则更可能是面条或者馒头。 这就是Ubiquitous Language的歧义(即冲突)。出现这种歧义时,通常意味着BC中存在两个名字相同,但职责不同的模型(泛指代表某个业务职责的实体)。 此时常见做法是根据业务情况将其中某个模型排除到BC之外。因为Ubiquitous Language冲突通常是因为存在多个BC(而你之前可能并没有察觉到它的存在)。

参考


  1. 《规划极限编程》Kent Beck & Martin Fowler ↩︎

  2. 《敏捷软件开发:原则、模式与实践》Robert C. Martin ↩︎

  3. https://agilemanifesto.org/iso/zhchs/manifesto.html ↩︎

  4. Ubiquitous Language 被翻译为“统一语言”或“通用语言”,但个人认为前者更能表达其含义 ↩︎

  5. https://en.wikipedia.org/wiki/Murphy%27s_law ↩︎

  6. https://hermanpeeren.nl/varia/why-i-dont-need-a-bounded-context ↩︎

Dik Tam
作者
Dik Tam
只是喜欢写代码。