前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

一、上下文映射概述

限界上下文即使都被设计为自治的独立王国,也不可能“老死不相往来”。要完成一个完整的业务场景,可能需要多个限界上下文的共同协作。只有如此,才能提供系统的全局视图

每个限界上下文的边界只能控制属于自己的领域模型,对于彼此之间的协作空间却无能为力。在将不同的限界上下文划分给不同的领域特性团队进行开发时,每个团队只了解自己工作边界内的内容,跨团队交流的成本会阻碍知识的正常传递。随着变化不断发生,难免会在协作过程中产生边界的裂隙,导致限界上下文之间产生无人管控的灰色地带。当灰色地带逐渐陷入混沌时,就需要引入上下文映射

映射关系 说明
防腐层(Anticorruption Layer 一个上下文通过一些适配和转换与另一个上下文交互。
开放主机服务(Open Host Service 定义一种协议来让其他上下文来对本上下文进行访问。
发布语言(Published Language 通常与OHS一起使用,用于定义开放主机的协议。
共享内核(Shared Kernel 两个上下文依赖部分共享的模型。
合作关系(Partnership 两个上下文紧密合作的关系,一荣俱荣,一损俱损。
客户方-供应方开发(Customer-Supplier Development 上下文之间有组织的上下游依赖。
分离方式 分离方式的团队协作模式是指两个限界上下文之间没有一丁点关系
遵奉者(Conformist 下游上下文只能盲目依赖上游上下文。
大泥球(Big Ball of Mud 混杂在一起的上下文关系,边界不清

二、下文映射模式

1、通信集成模式

通信集成模式决定了限界上下文之间的协作质量。只要产生了协作,就必然会带来依赖,选择正确的通信集成方式,就是要在保证协作的基础上尽可能降低依赖,维护限界上下文的自治性( UD 分别代表上游和下游,以圆形代表只有领域模型的领域层”

image-20240402134208779

1)防腐层

说明:一个上下文通过一些适配和转换与另一个上下文交互。

正如 David Wheeler 所说:“计算机科学中的大多数问题都可以通过增加一层间接性来解决。”防腐层( anti corruption layerACL )的引入正是“间接”设计思想的一种体现。在架构层面,为限界上下文之间的协作引入一个间接的层,就可以有效隔离彼此的耦合。

image-20240402175020631

a、没有防腐层

防腐层往往位于下游,通过它隔离上游限界上下文可能发生的变化,这也正是“防腐层”得名的由来。若下游限界上下文的领域模型直接调用了上游限界上下文的服务,就会产生多个依赖点。下游团队无法掌控上游的变化。变化会影响到下游领域模型的多处代码,破坏了限界上下文的自治性

image-20240402134824941

b、有防腐层

在下游定义一个与上游服务相对应的接口,就可以将掌控权转移到下游团队,即使上游发生了变化,影响的也仅仅是防腐层的单一变化点,只要防腐层的接口不变,下游限界上下文的其他实现不会受到影响

image-20240402134937894

c、多个防腐层处理

当上游限界上下文存在多个下游时,倘若都需要隔离变化,就需要在每个下游限界上下文的自治边界内定义相同的防腐层,造成防腐层代码的重复。如果该防腐层封装的转换逻辑较为复杂,重复的成本就太大了。为了避免这种重复,可以考虑将防腐层的内容升级为一个独立的限界上下文

不要将这个由防腐层升级成的限界上下文与其他提供了业务能力的限界上下文混为一谈。说起来,防腐层升级成的限界上下文更像伴生系统放在系统上下文内部的一个代理。”

例如:在确定电商平台的系统上下文不包含支付系统的前提下,所有的支付逻辑都被推给了外部的支付系统,订单上下文在支付订单时、售后上下文在发起商品退货请求时都需要调用。虽说支付逻辑封装在支付系统中,但在向支付系统发起请求时,难免需要定义一些与支付逻辑相关的消息模型。可以认为,它们都是集成支付系统的适配逻辑。这些逻辑该放在哪里呢?原本防腐层是这些逻辑的最佳去处,但位于下游的订单上下文与售后上下文都需要这些逻辑,就会带来支付适配逻辑的重复。这时就有必要引入一个简单的支付上下文,用来封装与外部支付系统的集成逻辑

2)开放主机服务

说明:定义一种协议来让其他上下文来对本上下文进行访问

如果说防腐层是下游限界上下文对抗上游变化的利器,开放主机服务(open host serviceOHS)就是上游服务招徕更多下游调用者的“诱饵” ,设计开放主机服务,就是定义公开服务的协议,包括通信的方式、传递消息的格式(协议)。同时,开放主机服务也可被视为一种承诺,保证开放的服务不会轻易做出变化

倘若上下游的限界上下文位于同一个进程,下游就应该直接调用上游的应用服务,以规避分布式通信引发的问题,例如序列化带来的性能问题、分布式事务的一致性问题以及远程通信带来的不可靠问题;

若它们位于不同进程,下游就需要调用上游的远程服务,自然也需要遵循分布式通信的架构约束。因为通信机制的不同,一旦限界上下文的通信边界发生了变化,就不可避免地要影响下游限界上下文的调用者。 为了响应这一变化,需要将防腐层与开放主机服务结合起来。防腐层就好像开放主机服务的“代理”:由于应用服务与远程服务的服务契约相同,因此防腐层在指向开放主机服务时就可以保证接口不变,而仅仅改变内部的调用方式

image-20240402175057154

3)发布语言

发布语言(published language)是一种公共语言,用于两个限界上下文之间的模型转换,通常与OHS一起使用,用于定义开放主机的协议

防腐层和开放主机服务都是访问领域模型时建立的一层包装,前者针对发起调用的下游,后者针对响应请求的上游,以避免上下游之间的通信集成将各自的领域模型引入进来,造成彼此之间的强耦合。因此,防腐层和开放主机服务操作的对象都不应该是各自的领域模型,这正是引入发布语言的原因

防腐层调用开放主机服务时用到的发布语言,亦可认为是消息契约

image-20240402175005334

4)共享内核

说明:两个上下文依赖部分共享的模型。

共享内核指将限界上下文中的领域模型直接暴露给其他限界上下文使用。注意,这会削弱了限界上下文边界的控制力。上面我们讲述的防腐层、开放主机服务以及发布语言无不传达一种思想,限界上下文不能直接暴露自己的领域模型或直接访问其他限界上下文的领域模型,一定要有隔离层!

但是,在特定的场景下,共享内核不见得不是一种合理的方式。任何软件设计决策都要考量成本与收益,只有收益高于成本,决策才是合理的。一般对于一些领域通用的值对象是相对稳定的,这些类型通常属于通用子领域,会被系统中几乎所有的限界上下文复用,那么这些领域模型就适合使用共享内核的方式。共享内核的收益不言而喻,而面临的风险则是共享的领域模型可能产生的变化

image-20240402175043057

2、团队协作模式

确定限界上下文的团队协作模式时,需要更多站在团队管理与角色配合的角度去思考

image-20240402175249557

1)合作者

合作关系指的是协作的限界上下文由不同的团队负责,且这些团队之间具有要么一起成功,要么一起失败的强耦合关系。合作者模式要求参与的团队一起做计划、一起提交代码、一起开发和部署,采用持续集成的方式保证两个限界上下文的集成度与一致性,避免因为其中一个团队的修改影响集成点的失败。

2)客户方/供应方

当一个限界上下文单向地为另一个限界上下文提供服务时,它们对应的团队就形成了客户方/供应方模式。这是最为常见的团队协作模式,客户方作为下游团队,供应方作为上游团队,二者协作的主要内容包括:

⬤ 下游团队对上游团队提出的服务调用需求

⬤ 上游团队提供的服务采用什么样的协议与调用方式

⬤ 下游团队针对上游服务的测试策略

⬤ 上游团队给下游团队承诺的交付日期

⬤ 当上游服务的协议或调用方式发生变更时,如何控制变更

3)分离方式

分离方式的团队协作模式是指两个限界上下文之间没有一丁点关系。这种“无关系”仍然是一种关系,而且是一种最好的关系,意味着我们无须考虑它们之间的集成与依赖,它们可以独立变化,互相不影响。还有什么比这更美好的呢。如果此时双方使用到了相似/相同的领域模型,则可以通过拷贝的方式解决,保证限界上下文之间的物理隔离!

4)遵奉者

当上游的限界上下文处于强势地位,且上游团队响应不积极时,我们可以采用遵奉者模式。即下游严格遵从上游团队的模型,以消除复杂的转换逻辑。

这种情形在现实的团队合作中可谓频频发生,尤其当两个团队分属于不同的管理者时,牵涉到的因素不仅仅与技术有关。限界上下文影响的不仅仅是设计决策与技术实现,还与企业文化、组织结构直接有关。很多企业推行领域驱动设计之所以不够成功,除了团队成员不具备领域驱动设计的能力,还要归咎于企业文化和组织结构层面,比如企业的组织结构人为地制造了领域专家与开发团队的壁垒,又比如两个限界上下文因为利益倾轧而导致协作障碍。团队领导的求稳心态,也可能导致领域驱动设计的改良屡屡碰壁,无法将这种良性的改变顺利地传递下去。从这一角度看,遵奉者模式更像一种“反模式”。当两个团队的协作模式被标记为“遵奉者”时,其实传递了一种组织管理的风险

问:当上游团队不积极响应下游团队的需求时,下游团队该如何应对?

分离方式:下游团队切断对上游团队的依赖,由自己来实现。

防腐层:如果自行实现的代价太高,可以考虑复用上游的服务,但领域模型由下游团队自行开发,然后由防腐层实现模型之间的转换。

遵奉者:严格遵从上游团队的模型,以消除复杂的转换逻辑

问:最后一种方式,实际上是权衡了复用成本和依赖成本的情况下做出的取舍,当下游团队选择“遵奉”于上游团队设计的模型时,意味着什么?

答案:

⬤ 可以直接复用上游上下文的模型(好的);

⬤ 减少了两个限界上下文之间模型的转换成本(好的);

⬤ 使得下游限界上下文对上游产生了模型上的强依赖(坏的)。

5)大泥球

说明:混杂在一起的上下文关系,边界不清

1、越来越多的聚合因为不合理的关联和依赖导致交叉污染;

2、对大泥球的维护牵一发而动全身;

3、强调“个人英雄主义”,只有个别“超人”能够理清逻辑。

6)发布者/订阅者

MQ 消息

三、上下文映射的设计误区

1、语义关系形成的误区

不要使用语义之间的关系去揣测限界上下文之间的关系。

2、对象模型形成的误区

对象模型关系也并非和限界上下文关系完全一致。

四、上下文映射的确定

1、任务分解的影响:最小知识法则

2、呈现上下文映射:上下文映射图

  • 菱形表示限界上下文采用菱形对称架构(结合了防腐层、开放主机、发布语言模式);椭圆形表示采用共享内核模式
  • 客户方/供应方:两端为C/S
  • 发布者/订阅者:两端为P/S,且是虚线标注
  • 遵奉者:连线上标注文字
  • 合作者:可省略标注

ContactAuthor