1 界限上下文
限界上下文确实和划分模块、划分子系统一样,是一种分而治之的手段,可以起到分离关注点的作用。但限界上下文增加了一个要点,就是,它的目的还在于维护概念一致性。正是这一点,造成限界上下文和传统方法的本质不同。
当系统达到一定规模,就超过了一个团队的认知能力,无法保证概念的一致性了。这时候,就要把大系统分解成若干子系统,每个子系统对应一个领域模型。每个模型的规模都不超过一个开发小组的认知负载。
在每个子系统的内部实现概念的严格一致性,而不同系统内部之间则没有必要一致。也就是说,不再追求全局一致性,而是退而求其次,只需追求局部的一致性,使概念不一致的问题得到合理管控,从而实现业务目标,这样就足够了。
举个例子:
- “项目”和“基础信息管理”分别属于两个不同的领域模型。
- 在项目领域模型里面,有用到“员工”信息。
- 员工是属于基础信息领域模型里面的。
项目管理中的员工是从基础信息管理上下文里“映射”过来的。两个上下文之间的这种映射关系,可以用<<map from>>来表示员工是从另一个领域模型里面映射过来的,bounded context = 基础信息管理,就表示另一个领域模型是基础信息管理。
1.2 防腐层
还是上面那个例子,在“项目”里面,想要拿到员工Emp信息,那么在系统里面,就会有两个Emp类,“基础信息管理”里面的 Emp 带有工作经验和技能,并且属性会更多;而“工时管理”里面的 Emp 没有工作经验和技能信息,而且只有少数几个要用到的属性。那么这两个 Emp 转换发生在哪里呢?
转换就发生在“项目”里面的员工仓库,也就是 EmpRepository 的实现里。
仓库的实现封装了对其他上下文的调用。如果将来,“基础信息管理”的 API 和 DTO 改变了,那么只需要改 EmpRositoryImpl 内部的逻辑就可以了,“工时管理”的其他部分都不需要修改。EmpRositoryImpl 就充当了防腐层的作用。
DDD 中,防腐层也是一种用于上下文映射的模式。指的是两个上下文之间的转换逻辑,这个逻辑可以屏蔽两个上下文的差异,从而使两个上下文可以相对独立地演进。
2 CQRS
CQRS 是 Command Query Responsibility Segregation 的简称,中文是 “命令查询职责分离”。这个名字乍听起来也不太好理解,咱们还是从业务需求开始,一步一步地理解。
前人已经意识到了查询和其他功能的不同之处,主张采用不同的方式来处理查询逻辑,并提出了所谓 CQRS 架构。
最早提出这个说法的是 Greg Young。他把增、删、改功能称为 Command(命令),把查询称为 Query,这两种功能的职责不同,应该采用不同的方式来处理,因此叫做“命令查询职责分离”(Command Query Responsibility Segregation ),简称 CQRS。
尽管通过 DDD 的领域模型完成增、删、改等功能是很适合的,但是通过领域模型来实现查询功能,常常是比较繁琐的,而且性能也不高。因此, CQRS 就成了 DDD 的有力补充。
实现CQRS有两种模式:
- 应用分离:把写操作和读操作分为两个不同的服务,访问同一个数据库。
- 数据库分离:还是拆分成读服务和写服务,同时呢,也为读服务冗余一个读库(违背DB范式,冗余字段),简化读服务的查询操作。
3 DDD推广
三种常见的切入场景:
- 第一种是新建系统。也就是说现在刚好有一个新项目,可能是要开发一个全新的系统,也可能是为现有系统新增或重写一个比较大的模块。
- 第二种是改造现有系统。常见的情况是,某个对公司很有价值的系统,已经维护了很多年,系统架构和质量日益腐化,很难维护,不能满足快速变化的业务需求。
- 第三种是改进现有研发流程。公司未必想专门花一大笔预算新建或者改造系统,但领导已经意识到目前的研发流程有种种不足,如果再“野蛮生长”下去,会有很大的隐患。因此希望通过引入 DDD 等方法,提高研发水平和效能。
改造现有系统的步骤:
- 第一步是反推领域模型。目的是客观地反映出系统当前的领域知识和逻辑。这时候的模型往往有不少问题,比如不能正确反映领域知识、存在矛盾、冗余等。
- 第二步是建立目标领域模型。根据当前系统的痛点、问题以及业务需求,就可以建立目标领域模型,作为改进的方向。建立目标领域模型,一定要有明确的“时间点”。
- 第三步是设计演进路线。一般要把改进过程化整为零,迭代实施,并且还要兼顾日常的业务需求,后面我们还会提到这个问题。
- 第四步是迭代实施。最好基于敏捷软件开发方法,小步快跑地实施。在这个过程中,必然会对之前建立的目标领域模型进行反馈,不断改进。同时还要不断评估开发现状,保证不偏离目标。