张逸:限界上下文的边界

简介: 本文是我即将在2017年领域驱动设计中国峰会演讲《Bounded Context的实践意义》的部分内容。在本次演讲中,我将彻底对限界上下文做一个全方位的解剖,包括解读限界上下文的定义、价值,了解限界上下文的三种边界,并提出如何识别限界上下文的方法。

边界通过限界上下文来确定,这在领域驱动设计中具有非凡的意义。对应于通用语言,限界上下文是语言的边界,对于领域模型,限界上下文是模型的边界,二者对应于问题空间(Problem Space)的界定。对于系统的架构,限界上下文还确定了应用边界和技术边界,进而帮助我们确定整个系统及各个限界上下文的解决方案。可以说,限界上下文是连接问题空间与解决方案空间的重要桥梁。

那么,限界上下文所界定的边界,究竟是逻辑边界,还是物理边界?这并没有定论,需得依据不同场景而做出不同的决策。

逻辑边界

根据业务对领域进行逻辑分解时,分与合是两个矛盾而又统一的概念。合是目标,分是降低复杂度的一种手段。分实则是为了更好的合。通过业务分解,每个分解出来的限界上下文规模就变得更小,因而更容易理解和把控。由于这种分解是从业务相关性来考虑的,使得领域可以更加细分,业务分析师或者领域专家就可以只要求掌握更加细分的专精领域。

从系统的代码模型(Code Model)看,所谓逻辑边界有两种表现形式。以Java为例,归纳如下:

  • 命名空间级别:逻辑边界仅仅通过命名空间进行界定,但是所有的限界上下文其实都处于同一个模块中,编译后都属于同一个Jar包。
  • 模块级别:在命名空间上是逻辑分离的,而不同限界上下文则属于同一个项目的不同模块,编译后会生成各自的Jar包。若限界上下文之间存在依赖,则在运行时,这些Jar会被同时加载到同一个Java虚拟机中。这里所谓的“模块”,在Java代码中也可以创建为Jigsaw的module。

将限定上下文的边界视为逻辑边界是最常见也是最简单的一种形式。一方面逻辑的分离可以保证系统代码的清晰结构,另一方面它也使得限界上下文之间的协作变得更加容易,更加高效。在物理上,限界上下文彼此之间的通信其实是无缝集成的,要重用的领域模型都可以直接访问,并对模型类进行实例化。如下是国际报税系统的逻辑边界(Java): 

7daf582ecd921819cf6aa5f8e9f44722c4b17f88

然而,正所谓越容易重用,就越容易产生耦合。编写代码时,我们需要谨守这条无形的逻辑边界,时刻注意不要逾界,并确定限界上下文各自对外公开的接口,避免对具体的实现产生依赖。

采用逻辑边界划分限界上下文的系统架构是单块(Monolithic)架构,所有的限界上下文都部署在同一个进程中,因此不能针对某一个限界上下文进行水平伸缩。需要对限界上下文的实现进行替换或升级时,会影响到整个系统。即使我们守住了逻辑边界,这种耦合仍然存在,导致各个限界上下文的开发互相影响,团队之间的协调成本也随之而增加。

物理边界

逻辑边界的坏,正是物理边界的好;反过来,物理边界的坏,同样是逻辑边界的好。当我们将限界上下文的边界定义为物理边界时,每个限界上下文就变成了一个个细粒度的微服务。

这里,我们需要针对Eric Evans提出的“限界上下文”概念做进一步澄清:限界上下文究竟是仅仅针对领域模型的边界划分,还是对整个架构(包括基础设施层以及需要使用的外部资源)垂直方向的划分?正如前面对Eric Evans观点的引用,他在《领域驱动设计》一书中明确地指出:“根据团队的组织、软件系统的各个部分的用法以及物理表现(代码和数据库模式等)来设置模型的边界。”显然,限界上下文不仅仅作用于领域层和应用层。它是架构设计而非仅仅是领域设计的关键因素。

倘若我们将限界上下文的边界视为物理边界,则可以保证边界内的服务、基础设施乃至于存储资源、中间件等其他外部资源的完整性,最终形成自治的服务。限界上下文之间仅仅通过限定的方式以限定的通信协议和数据格式进行通信,除此之外,彼此没有任何共享,这种架构被称之为零共享架构。这种架构的表现形式为:每个限界上下文都有自己的代码库、数据存储以及开发团队,每个限界上下文选择的技术栈和语言平台也可以不同。当每个限界上下文都被物理隔离时,一个限界上下文的开发人员就不能调用另一个限界上下文的方法,或者将数据存储在共享结构中了,这可以避免因为共享带来的耦合。下图为危机分析系统的架构: 

57622f4dd2676e50c7815c47d0c42340d59cfcef

物理分隔开的限界上下文变得小而专,使得我们可以很好地安排遵循2PTs规则的小团队去治理它。然而,这种架构的复杂度也不可低估。限界上下文之间的通信是跨进程的,我们需要考虑通信的健壮性。数据库是完全分离的,当需要关联之间的数据时,需得跨限界上下文去访问,无法享受数据库自身提供的关联福利。由于每个限界上下文都是分布式的,如何保证数据的一致性也是一件棘手的问题。当整个系统都被分解成一个个可以独立部署的限界上下文时,运维与监控的复杂度也随之而剧增。

数据库共享

在逻辑边界和物理边界中间,还存在一种折中的手段。在考虑限界上下文划分时,分开考虑代码模型与数据库模型,就可能出现在代码上分离,而在数据库层面却存在数据共享的形式,即多个限界上下文共享同一个数据库。

因为没有分库,在数据库层面就可以更好地保证事务的ACID。这或许是该方案最有说服力的证据,但也可以视为是对“一致性”约束的妥协。

数据库共享的问题在于数据库的变化方向与业务的变化方向会不一致。这种不一致性体现在两个方面:

  • 耦合:虽然业务上限界上下文之间是解耦的,但是在数据库层面依然存在强耦合关系

  • 水平伸缩:部署在应用服务器的应用服务可以根据限界上下文的边界单独进行水平伸缩,但是在数据库层面却无法做到

根据Netflix团队提出的微服务架构最佳实践,其中一个最重要特征就是“每个微服务的数据单独存储”。但是服务的分离并不绝对代表数据应该分离。数据库的样式(Schema)与领域模型未必存在一对一的映射关系。在对数据进行分库设计时,如果仅仅站在业务边界的角度去思考,可能会因为分库的粒度太小,导致不必要的跨库关联。因此,我们可以将“数据库共享”模式视为一种过渡方案,不要在一开始设计微服务的时候,就直接将数据彻底分开,而是采用演进式的设计。

为了便于在演进设计中将分表重构为分库,从一开始要注意避免在两个表之间建立外键约束关系。某些关系型数据库可能通过这种约束关系提供级联更新与删除的功能,这种功能反过来会影响代码的实现。一旦因为分库而去掉表之间的外键约束关系,需要修改的代码太多,会导致演进的成本太高,甚至可能因为某种疏漏带来隐藏的Bug。

没有外键约束关系可能在当前增加了开发成本,却为未来的演进打开了方便之门。例如,在针对某手机品牌开发的舆情分析系统中,危机查询服务提供对识别出来的危机的查询,需要通过userId获得危机处理人、危机汇报人的详细信息。左图为演进前直接通过数据库查询的方式,右图则切断了这种数据库耦合,改为服务调用的方式: 

04f5e64f9ab87b336953efddb4285612f53d35a5

倘若架构被设计为数据库共享,且两个服务需要操作同一张数据表(这张表被称之为“共享表”),则传递了一个信号,即我们的设计可能出现了错误:

  • 遗漏了一个限界上下文,共享表对应的是一个被重用的服务:买家在查询商品时,商品服务会查询价格表中的当前价格,而在提交订单时,订单服务也会查询价格表中的价格,计算当前的订单总额;共享价格数据的原因是我们遗漏了价格上下文,通过引入价格服务就可以解除这种不必要的数据共享。
  • 职责分配出现了问题,操作共享表的职责应该分配给已有的服务:舆情服务与危机服务都需要从邮件模板表中获取模板数据,然后再调用邮件服务组合模板的内容发送邮件;实际上从邮件模板表获取模板数据的职责应该分配给已有的邮件服务。
  • 共享表对应两个限界上下文的不同概念:仓储上下文与订单上下文都需要访问共享的产品表,但实际上这两个上下文需要的产品信息是完全不同的,应该按照限界上下文的边界分开为产品建表。

为什么会出现这三种错误的设计?根本原因还是在于我们没有通过业务建模,而是在数据库中隐式地进行建模,因而在代码中没有体现正确的领域模型,从而导致了数据库层面的耦合或共享。

部分PPT内容

184c9933887a3ad5818adf7e9ea07341ea3efb9b


原文发布时间为:2017-12-14

本文作者:张逸

本文来自云栖社区合作伙伴“中生代技术”,了解相关信息可以关注“中生代技术”微信公众号




相关文章
|
4月前
|
机器学习/深度学习 移动开发 自然语言处理
【YOLOv8改进 - 注意力机制】ContextAggregation : 上下文聚合模块,捕捉局部和全局上下文,增强特征表示
【YOLOv8改进 - 注意力机制】ContextAggregation : 上下文聚合模块,捕捉局部和全局上下文,增强特征表示
|
3月前
|
机器学习/深度学习 自然语言处理
上下文无关与上下文相关
上下文无关与上下文相关
|
Java Spring
你动了别人的代码边界
前段时间呢,需要和xx公司进行对接。由于手上活比较多没忙不过来,领导就先帮我把接口调试完成了,并写好了相关的demo。然后我根据demo把代码整合进业务系统,并重写了相关代码。后来领导看了我写的代码,发现和他写的的demo不太一样,然后就问我为什么要重写?在一番争论后,领导对我说了句:你到底懂不懂抽象啊,你动了别人的代码边界。
|
安全 搜索推荐 领域建模
一文说透子域和限界上下文
一文说透子域和限界上下文
558 1
|
消息中间件 设计模式 测试技术
阐释限界上下文
阐释限界上下文
阐释限界上下文
|
消息中间件 缓存 前端开发
DDD 实战 (4):战略设计之系统上下文和限界上下文
DDD 实战 (4):战略设计之系统上下文和限界上下文
DDD 实战 (4):战略设计之系统上下文和限界上下文
|
机器学习/深度学习 人工智能 网络架构
CVPR‘2023 | PIDNet: 基于注意力机制引导的实时语义分割网络架构,完美解决空间细节、上下文和边界信息
CVPR‘2023 | PIDNet: 基于注意力机制引导的实时语义分割网络架构,完美解决空间细节、上下文和边界信息
1042 0
|
存储 运维 监控
限界上下文的边界
限界上下文的边界
限界上下文的边界
|
数据采集 监控 算法
调整限界上下文边界
调整限界上下文边界
调整限界上下文边界
|
存储 算法
趣学算法之分枝限界法
趣学算法之分枝限界法
166 0