限界上下文的边界

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

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

逻辑边界

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

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

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

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

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

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

物理边界

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

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

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

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

数据库共享

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

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

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

  • 耦合:虽然业务上限界上下文之间是解耦的,但是在数据库层面依然存在强耦合关系
  • 水平伸缩:部署在应用服务器的应用服务可以根据限界上下文的边界单独进行水平伸缩,但是在数据库层面却无法做到

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

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

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

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

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

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

原文发布于微信公众号 - 逸言(YiYan_OneWord)

原文发表时间:2017-12-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏编程

器—术—道:程序设计教材建设经验谈

《计算机教育》2017年第11期 封面文章 引 言 程序设计的境界有3种:器—术—道。在程序设计能力培养方面,一般由“器”入门,通过熟悉“术”,最终达到“道”的...

1849
来自专栏海天一树

结构化、半结构化和非结构化数据

结构化的数据是指可以使用关系型数据库表示和存储,表现为二维形式的数据。一般特点是:数据以行为单位,一行数据表示一个实体的信息,每一行数据的属性是相同的。举一个例...

972
来自专栏编程

老丁独家!前方高能,与“程序崩溃”的第一次邂逅!

本阶段课程适合2年级以上刚开始接触EV3机器人的同学们。 本篇请下载 LEGO Mindstorms教育版软件 有条件的话,请为孩子添置一套EV3套装,让课程内...

1689
来自专栏祝威廉

流式数据Pipeline质量控制浅谈

以我们公司的实时带宽计算为例,就是一个长长的Pipeline,中间经过的组件其实是很多的,比如解析模块,指标拼接模块,累加器模块等,这些模块可能是独立部署,也可...

734
来自专栏wym

南昌大学航天杯第二届程序设计竞赛校赛网络同步赛 水题

链接:https://www.nowcoder.com/acm/contest/122/C 来源:牛客网

622
来自专栏企鹅号快讯

干货!大数据专业词汇整理,不知道容易被别人喷晕!

随着大数据技术的不断提高,大数据应用的不断普及,大数据与各行各业的关系越来越紧密。大数据行业充斥着大量的专业词汇,准确掌握和了解这些词汇的含义,有助于更好的理解...

1826
来自专栏水击三千

数据库设计(一) 需求分析

目前,大多数的应用系统都属于数据库应用程序,都离不开数据库的支持。数据库设计方案的优劣对于应用程序的运行至关重要。数据库设计过程就是针对具体的应用环境,设计优化...

23210
来自专栏诸葛青云的专栏

系统学习C语言方法大全

很多人对学习C语言感到无从下手,经常问我同一个问题:究竟怎样学习C语言?我是一个高级编程师,已经开发了很多年的程序,和很多刚刚起步的人一样,学习的第一个计算机语...

160
来自专栏AI科技评论

动态 | 码农福音!CASIL开发代码移植系统,CTRL+C/V快速编程不再是梦想

问:对于码农来说,有哪些可以提高开发效率的技巧? 答:Ctrl+C、Ctrl+V。 ? (图片来源:知乎) AI科技评论发现:近日,麻省理工学院计算机科学与人工...

3419
来自专栏专知

【观点】漫谈推荐系统及数据库技术

点击上方“专知”关注获取更多AI知识! 【导读】推荐系统和数据库技术,一个是偏机器学习数据挖掘相关的应用,一个是偏系统存储相关的技术,这两者在实际中有很大的应用...

3589

扫码关注云+社区