DDD的应用实践是一个认知的过程,在实践时团队成员尽量保持同一水平的认知,通俗来说,就是错也要错的一致,同时在落地时要以多数人的认知为基准实施,不能以认知程度高的少数人标准来落地。
初学者可以按本节精炼战略和战术的核心内容先实践。本节中提到的知识点会在后续章节详细展开。概念和术语会随章节内容逐个介绍。在开始之前有必要先澄清两个概念,以免造成混淆:

  • 应用:指物理存在且可部署的软件单元,在java程序中一般分为两种,一种是提供用户操作界面的web端应用一般为.war包形式存在;另外一种是提供API的数据中心应用以.jar包的形式存在;
  • 系统:多个应用的集合,是应用的逻辑组合,其本身只是一个代号。比如用户管理应用+流程管理应用+SSO应用共同组成了OA系统

    一个虚构的系统

    在设计开始之前,我们先虚构一个简单的协同系统,此协同系统采用scrum管理方式,采用传统的scrum模型,主要包括:Product(产品)、Release Plan(发布计划)、Backlogitem(待办项)、sprint(冲刺项)、Team(负责团队)、Product Owner(产品负责人)、Task(任务)、Priorit(优先级),业务模型经过初步分析后,如下图所示:

业务规则大概如下:

  • 产品由三部分组成:待办、冲击、已发布;
  • 产品归属于产品负责人;
  • 一个待办项包含若干任务,而且都会有一个优先级用于标识其重要程度;
  • 产品负责人可以已在发布计划中的待办项提交到冲刺中;
  • 当待办项被提交冲刺中后,需通知冲刺方;

下面我们就以这个虚构的业务案例为背景,看下DDD是如何应用到软件系统设计中的。由于DDD中的概念和规则还是挺多的,所以在每节的开头会先解释下DDD中相关的术语,然后再以本案例进行分析,读者也可以先按照DDD的概念分析本案例,最后对比笔者的分析,可能会达到事半功倍的效果。

战略设计

DDD中的战略主体是业务,并不是由公司经营者制定的以运营或管理为目的的那种战略规划。详细来讲是指以业务为核心,合理的设计模型(领域)、划分(限界上下文),再综合组织架构、技术等因素辅以相应的集成策略(上下文映射)。
以公司运营为目的的战略规划虽不是DDD的设计范畴,但在DDD战略设计时需要参考公司战略、组织架构等因素,因为这些因素会影响两个上下文映射时的策略选择,后面章节会详细讲述。

术语

  • 限界上下文( bounded context :语义和语境上的边界用来封装通用语言和领域模型,在边界内的模型都有特定的含义并处理特定的事务;
  • 通用语言( ubiquitous language ): 不是指特定的法语、英语这种规则比较明确的语言,它是指贴合业务并经过提炼后能被所有团队成员沟通的短语;通用语言有几个特征: a 、受限于上下文,在同一上下文中没有二义性; b 、贴合业务; c 、持续提炼; d 、能被所有团队成员理解;
  • 领域( domain ): 域是一个概念,用于封装业务模型;应用于高层设计,比如业务间、也可用在具体业务的子域中。一个业务系统的成功取决于它所提供的多种功能,将这些功能分开是有好处的。在 DDD 中,一个领域被分为若干子域,领域模型在限界上下文中完成开发。
  • 领域模型( model ): 可理解为与业务对应的实体,比如用户,角色;

上面的术语经过解释后还是会比较抽象,举一个例子可能会更好的理解一些,比如一个只包含用户管理、角色管理、资源管理(url菜单)三个模块的简单权限管理系统,套用上面的术语可以表述为:

  • 在权限管理系统中,包含用户、资源、角色三个限界上下文;
  • 在用户限界上下文中包含用户域;
  • 在用户域中包含用户组、用户两个模型,在角色域中包含角色、用户两个模型;

注意”用户”出现在了两个域中,但业务含义是不一样的。在用户域中多指真实存在的人或系统,关注用户身份、密码等属性,在角色域中的用户一般是指系统的操作者,一般只需关注用户标识即可。

实施步骤

第一步、划分限界上下文

从业务上划分模型的职责和边界,传统模式下业务模型一般会以E-R图、数据模型图或是图+表的方式来表述,关注点通常是实体属性、数据导航等,然后把模型划分到不同的系统模块中来表述隔离,如下图所示(图中的M指模型是Model的缩写):

实际上系统模块并不适合做为业务模型间隔离的依据,严格来讲系统模块本质上更趋向于终端应用界面的展现、模型才是是业务的映射,模型间的隔离也需要从业务角度来划分会更为合适。因此系统模块和业务模型本质上是没有直接关系的。本节中所说的上下文划分正好弥补了上述设计过程中的不足,下图展示的是限界上下文划分的过程,左下方框内是拆分后的限界上下文。
一般会先先识别出核心业务,然后再丰富模型。上图中最左侧是去掉模块后的传统业务模型,蓝色的三个步骤是建议的限界上下文的拆分过程,每一步的过程及要点如下:

  1. 识别核心模型:找出业务中最最核心的部分(比如商品管理中心,一般包含商品管理、问答管理等多个模块,但最核心的应该只是商品信息,而不包含问答管理),用一个椭圆把最最核心的模型圈起来起一个名称,这就是DDD中限界上下文的图形表示方法,暂且称为”核心上下文”,实际分析时需要用真实的业务术语代替”核心”二字,比如商品上下文、订单上下文;
  2. 统一语言: 明确定义上下文中各个模型的术语及含义,保证任一模型在此限界上下文中的唯一性,这里只保证名称唯一性即可,在下节提炼时会再复盘这个过程;
  3. 丰富核心模型: 贴合业务重新审视核心上下文中的模型并进行必要的增删,还是以商品中心举例,商品除了基本信息外,一般还需要与之对应的库存信息,否则在实际业务中商品是无法售卖的;
  4. 当用核心上下文圈定的核心模型梳理完成后,重复上面的 3 步分堆非核心模型,直到所有模型都归属到特定上下文中;

经过分析后,协同系统的模型拆分如下:

第二步、提炼通用语言

用业务术语命名模型,拉齐认知,方便团队沟通。当限界上下文划分完成后,我们有必要全部重新审视一次,就好比考试答完试题后的复查,并用下面的表格记录所有提炼出来的业务短语,并在全团队范围内明确。软件最终交付的是代码,但交付不了知识。但在迭代过程中需要把知识传递下去,可以把通用语言看作知识传递的方式,可视化的表格就是这种知识的载体,建议用excel表格即可,最好是在线的那种。

  • 通用语言:不局于名词也可以是动词短语,比如领域事件最好用动词的过去式来命名;
  • 类型:在DDD中一共包含7种类型的模型,比如域、事件、聚合根等,在战术部分会详细说明;
  • 详细说明:可以是通俗的解释,也可以通过场景的方式描述(如上述表格的第三行);

通用语言
模型在不同的上下文中才有意义,所以不建议创建全局唯一的通用语言;上下文和通用语言是紧密联系在一起,不能脱离开上下文只说通用语言;
不同上下文中可以存在名称相同的通用短语,多数情况下也是同一个模型,区别在于关注点不一样,比如在团队管理上下文中关注的用户唯一标识信息,而在 scrum 上下文中关注的责任人或负责任务开发的人员名称,并不关注用户标识

第三步、划分子域

定义价值,DDD战略是通过业务价值分析划分领域类型进而制定战略的。子域划分除了可以识别出对组织最有价值的模型,同时也可为系统建设在优先级、投资规模、架构设计、产品定位等一系列活动过程中提供决策依据。DDD中的域可以分三类:

  • 核心域: 唯一的、定义明确的领导模型是业务中最重要的部分,是核心竞争力的体现;在系统建议时对核心域要有资源倾向性;
  • 支撑子域: 核心域的成功离不开它,但它又不是业务的核心部分,在系统建设时一般提供定制开发,架构设计时需要考虑其可替换性;
  • 通用子域: 指通用功能,一般来讲市场上已经有很多成熟的产品了,在系统建设时往往倾向于采购;

经过分析后,此时应该会有多个限界上下文,其中之一必定会成为核心域,而其它的则成为支撑域或通用域。我们用绿色表示核心域、黄色表示通用域、粉色表现支撑域。经过分析后,协同系统的上下文划分如下图所示:

域、限界上下文和模型
这三者可以看做是顺序包含的关系,但是建议一个上下文只包含一个子域而且要尽量保持,实际上允许的一个上下文中包含多个子域,但是容易造成模糊,同时需要用辅助文字来解释。
如果遇到一个上下文中多个子域时,更建议用模块来代替子域,即一个上下文由多个模块组成,每个模块对应一个子域。
第四步、上下文映射
制定上下文间集成策略,这是战略设计的最后一步。前面我们已经按内聚性、不变性、价值等维度划分了业务模型,本质上业务运行过程中是少不了任何一个模型的。这一步我们需要再把他们联系起来。DDD中用下图所示方法表示,如果需要关联,则在两个椭圆间画一条不带简单的实线,线上标识U和D,U表示上流,D表示下游。

注意,这里的上下游关系并非应用间 API 接口调用的表述,而是纯粹的业务映射,比如订 scrum 上下文必须依赖团队管理上下文。同时这个映射关系有时需要考虑组织架构的影响,比如由两个团队负责开发协同系统,负责团队管理应用的团队就是不提供 API 接口给 Scrum 团队,虽说业务上依赖,但在这种情况下 scrum 团队就需要重新考量了,是组织升级还是自建。
在业务模式不变的前提下,上下文映射策略是会发生变化的,但领域模型是不会变化的
在DDD共总结了8种映射策略,本小节先介绍两种常见的映射策略,其它的会在后续章节详细展开:

  • 合作关系: 日常工作上两个团队可能存在交叉影响,此时映射关系本质上依赖高度的承诺,但需要注意时效性因为长久的稳定合作关系很难维护;
  • 客户供应商: 比较常见,双方高度协同,比如前台(客户)对中台(供应商)的依赖,此时映射关系本质上由供应商主导;在系统建设时建议客户按自己的预期测试供应商的接口,辅助供应商升级,并把这些测试变成供应商测试的一部分;

经过分析的,协同办公的上下文映射如下图:

第五步、补充上下文映射
软件系统实质上是一种IT技术的编码实现,在战略设计过程中除了考虑业务、业务价值、组织架构等因素,个人认为是有必要加入技术考量的。
比如:在应用集成时,上游团队提供的接口有可能是Soap、Restful、Mq、SDK等多种形式,相对来讲,同样复杂度的服务不同协议类型的接口性能是不一样的。如果我们的业务对性能、安全有要求,在上下文映射时就需要考虑技术因素。下图是一个简单示例(PL=已发布语言、OHS=开放主机服务、ACL=防腐层):

  • C1-G1 :上游 G1 ,下游 C1 ,映射策略是共享内核,类似 java 应用共享 .jar 包方式;
  • C1-G2 : 上游 G2 ,下游 C1 ,映射策略是已发布语言,类似数据中心的方式;
  • C1-S1 :上游 C1 ,下游 S1 ,映射策略是开放主机服务,类似 Rpc 方式的调用;

在讲述上下文映射集成和架构设计时会详细展开技术集成的细节内容
补充后的上下文映射如下图所示:

  • 配置管理上下文中包含优先级管理,它和核心的 Scrum 上下文采用共享内核的方式集成,一般来说就是实现代码打包在一起部署;
  • 团队管理上下文,将来会一个单独的应用形式部署,它和 Scrum 上下文之间通过 Rpc 接口相互调用,集成时在 Scrum 上下文中做一层防腐层以隔离自己的内核不会受团队管理应用的 API 变化而变化;

战略设计结果