Skip to content

限界上下文:定义领域边界的利器

在DDD领域建模和微服务建设过程中,会有很多项目参与者,包括领域专家、产品经理、项目经理、架构师、开发经理和测试经理等。对于同样的领域知识,不同的参与者可能会有不同的理解。而且有的时候同一个领域内的名词和术语也可能不统一,团队成员交流起来就会出现障碍,严重时甚至会传达出错误的信息。如果出现这种情况应该怎么办呢?在DDD中有“通用语言(Ubiquitous Language)”和“限界上下文(Bounded Context)”这两个重要概念。两者相辅相成,通用语言用于定义上下文对象的含义,而限界上下文则用于定义领域边界,以确保每个上下文对象在它特定的边界内具有唯一的含义,在这个边界内,组合这些对象构建领域模型。

什么是通用语言

为了更好地理解限界上下文,回答上面的问题,我们先从通用语言讲起。

在事件风暴过程中,通过团队交流达成共识的,能够简单、清晰、准确地描述业务含义和规则的语言就是通用语言。也就是说,通用语言是团队的统一语言,不管你在团队中担任什么样的角色,在同一个领域的软件生命周期里都使用统一的语言进行交流。

那么,通用语言的价值也就很明了了,它可以解决交流障碍的问题,使得领域专家与项目团队以及项目团队内部成员之间,能够用共同的语言进行交流和协同合作,从而确保业务需求的正确表达和系统的正确实现。

但是,对通用语言的理解到这里还不够。通用语言往往跟领域中的名词术语和用例场景相关。通用语言中的名词一般可以给领域对象命名,如商品、订单等,它们对应领域模型中的实体对象。而动词则表示一个动作或领域事件,如商品已下单、订单支付等,它们对应领域模型中的领域事件或者命令。

通用语言贯穿DDD的整个设计过程。作为项目团队沟通和协商过程中形成的统一语言,它通过将领域模型映射到代码模型,可以将这些名词术语直接反映到代码中。基于它,你就能够开发出可读性更好的代码,将业务需求准确转化为代码落地。

在事件风暴过程中,领域专家会和团队设计、开发人员一起通过用户旅程分析或者场景分析,提取出业务领域中的所有领域对象,将这些对象进行聚合,并划定业务边界,然后建立领域模型。

在领域建模的过程中,我们会给这些领域对象统一命名,这个过程就会形成项目团队统一的通用业务术语。所以说事件风暴,是项目团队统一语言和思想的关键过程。

建立了通用语言的领域模型会作为微服务设计的输入,而微服务的代码模型来源于领域模型,代码模型的代码对象会跟领域模型中的领域对象一一映射。这个过程也是通用语言从业务领域传递到系统领域的过程,只有在整个应用建设过程统一了通用语言,并进行了正确的传递和映射,才能确保业务需求与系统落地的一致性。

这里我再分享一条经验,设计过程中我们可以用一些表格来记录事件风暴和微服务设计过程中产生的领域对象及其属性,比如:领域对象在DDD分层架构中的位置、实体的属性、领域对象之间的依赖关系以及代码对象与领域对象的映射关系等。

从这个表格我们可以看到,在DDD分析过程中,领域模型的领域对象以及它们在领域模型中的属性和特征都被记录了下来。除了记录领域模型中的领域对象,我们还记录了在微服务设计过程中,这些领域对象所映射的代码对象。

到这里,我想再强调一次:DDD分析和设计过程中的每一个环节,都需要保证限界上下文内通用语言的统一和正确传递。在代码模型设计的时候要建立领域对象和代码对象的一一映射,从而保证业务模型和系统模型一致,实现业务语言与代码语言的统一。

如果你做到了这一点,也就是建立了领域对象和代码对象的映射关系,就可以指导软件开发人员准确无误地按照领域模型和设计文档完成微服务开发了。而且,有了这种映射关系,即使是不熟悉代码的业务人员,也可以根据这种映射关系很快找到业务逻辑所在的代码位置。

什么是限界上下文

那刚刚上面提到的限界上下文又是用来做什么的呢?

我们知道语言都有它的语义环境,同样,通用语言也有它的上下文环境。为了避免同样的概念或语义在不同的上下文环境中产生歧义,DDD在战略设计上提出了"限界上下文"这个概念,用来确定语义所在的领域边界。

因为"限界"这个词很少见,而在汉语里"限界"一般是指"铁路建筑物及设备不得超过的轮廓尺寸线",所以很多人初次接触限界上下文时,就有点犯迷糊,理解成本似乎有点高。

我们不妨先来看一下限界上下文的英文原文:"Bounded Context",翻译成中文,其实可以理解为"限定了边界的上下文环境"。

为了更好地理解限界上下文的含义,我们可以将它拆解为两个词来理解:"限界"和"上下文"。"限界"是指具体的领域边界,而"上下文"则是业务语义所在的上下文环境。通过限定领域的上下文边界,项目团队就可以在这个特定的业务边界内用无歧义的通用语言进行交流了。

综上,限界上下文就是在限定的上下文环境内,用来封装通用语言和领域对象,保证领域内的一些术语、领域对象等有一个确切的含义,没有语义二义性的一个业务边界。相信你在深入研究DDD后,就能更好地理解它的含义和价值了。它定义了领域模型的边界和业务适用范围,使得团队所有成员能够明确地知道,什么内容应该在领域模型中实现,什么不应该在模型中实现。

如果这个边界限定的是业务领域的边界,那你可以理解为业务上下文边界。

定义限界上下文时通常会考虑领域业务职责单一这个因素。在确定了领域的职责边界后,会将所有与实现该领域职能相关的对象都放在同一个限界上下文边界内,而将所有与该领域职能无关的对象都排除在上下文边界之外。限界上下文就是这样一个强制边界,它可以保证领域职责的单一性和领域模型的纯洁性。

举个例子,其实企业在设置组织架构(如部门、处室等)时,就是在定义企业的限界上下文边界。企业设置组织架构时,往往会从企业的职能边界出发,根据这些职能边界来设置部门,划定部门的边界,比如:可以为人力资源管理相关的职能设置人力资源部,为财务核算管理相关的职能设置财务会计部,还有产品线相关的业务部门以及后勤部门等。

部门的职能边界就是企业组织架构的限界上下文边界。在部门内会聚集与部门职能相关的所有角色,他们在部门内各司其职,共同完成部门所承担的职责。这些角色就类似领域模型中的领域对象。

在确定了部门的职责边界后,不同部门之间的职能就不应该出现重叠和混淆,也不应该将与部门职能不相干的角色放在部门内了,因为这样会破坏组织架构的限界上下文边界。有的企业为了缩减部门编制,会将一些彼此不相干的职能放在一个部门,但是这样在对外提供服务时,就很容易让人产生误解或者困惑,甚至闹出笑话,这就不是一个好的企业限界上下文边界了。

进一步理解限界上下文

我们可以通过一些例子进一步理解限界上下文这个概念。不要小看它,彻底弄懂它会给你后面实践DDD和微服务设计打下一个坚实的基础。

都说中文这门语言非常丰富,在不同的时空和背景下,同样的一句话会有不同的含义。

有一个例子你应该听说过。在一个明媚的早晨,孩子起床问妈妈:"今天应该穿几件衣服呀?"妈妈回答:"能穿多少就穿多少!"那到底是穿多还是穿少呢?

简短的几句对话,如果没有具体的上下文语义环境,还真不太好理解。但是,如果你已经知道了这句话所在的语义环境,比如是寒冬腊月或者是炎炎夏日,那么理解这句话的含义就会容易得多了。

所以,语言离不开它的语义环境。同样的,DDD的通用语言也离不开它的语义环境,这个语义环境就是它的业务上下文边界。

在中台领域建模的过程中,我们不大可能用一个简单的名词术语没有歧义地描述一个复杂的业务领域,因此我们就用子域或限界上下文来细分领域,通过缩小术语的业务语义范围,从而限定通用语言所在的上下文边界。

现在我们用一个保险领域的例子来讲解通用语言的限界上下文边界。保险业务领域有投保单、保单、批单和赔案等保险术语,它们分别作用于保险的不同业务流程边界内。

客户投保时,业务人员会记录投保信息,在这个领域内会有投保单实体对象。

缴费完成后,业务人员将投保单转为保单,在这个领域内会有保单实体对象,保单实体与投保单会关联。

如果客户需要修改保单信息,保单会变为批单,在这个领域内会有批单实体对象,批单实体会与保单关联。

如果客户发生理赔,生成了赔案,在这个领域内会有报案实体对象,报案实体对象与保单或者批单关联。

投保单、保单和批单等这些保险术语虽然都跟保单有关,但由于它们在不同的业务阶段,表现形式不一样,也被赋予了特殊的业务含义,我们需要针对不同的业务阶段加以区分,避免出现歧义。因此我们不能简单地用"保单"这个术语作用在保险的全业务领域。通用术语有它的作用边界,超出这个边界就容易出现理解上的问题。

如果你对保险领域不大了解也没关系,电商领域肯定再熟悉不过了吧?正如电商领域的商品一样,商品在不同的阶段也有不同的表达形式。商品在销售阶段是商品,这是它的原始含义。在销售阶段结束后,商品就进入了运输阶段,这时商品就变成了货物。可见,同样的一件商品,由于业务领域边界的不同,这些通用语言的术语就有了不同的含义。

限界上下文就是用来定义这些通用语言的上下文边界的。这个边界既是业务领域的边界,也是微服务拆分的边界。

看到这,我想你应该非常清楚了,业务领域的边界就是通过限界上下文来定义的。

限界上下文和微服务的关系

接下来,我们来对限界上下文概念做进一步的延伸理解,看一看限界上下文和微服务到底存在怎样的关系?

我们以购买车险为例进行说明。车险承保的流程包含投保、缴费、出单等几个主要流程。如果出险了还会有报案、查勘、定损、理算等理赔流程。

首先,领域可以拆分为多个子领域。一个领域相当于一个问题域,领域拆分为子域的过程就是大问题拆分为小问题的过程。保险领域被拆分为:投保、支付、保单管理和理赔四个基本的子域。

子域还可根据需要进一步拆分为子子域,比如,支付子域可继续拆分为收款和付款子子域。拆分到一定程度后,有些子子域的领域边界就可能变成限界上下文边界了。

子域可能会包含多个限界上下文,如理赔子域就包括报案、查勘和定损等多个限界上下文,这里限界上下文与理赔的子子域领域边界正好重合。也有可能子域的边界正好就是限界上下文边界,如投保子域。

每个领域模型都有它对应的限界上下文,团队在限界上下文内用通用语言交流。领域内所有限界上下文的领域模型构成了整个业务领域的领域模型。

限界上下文是微服务拆分过程中可以参考的业务领域边界。不过,这里还是要提示一下,虽然限界上下文理论上可以作为微服务的拆分边界,但实际落地时,微服务的拆分还是需要结合企业的实际情况,考虑其他非业务因素的限制条件。比如,如果不考虑技术异构、团队沟通等其他外部因素,一个领域模型是可以被设计为一个微服务的。

但需要记住一点:"不宜过度拆分微服务",这样会增加你的集成和运维成本。

限界上下文与子域的关系

有人可能会对子域与限界上下文的关系有些困惑。子域和限界上下文的映射关系到底是什么样的?一对多?或者多对一?还是一对一?

其实,在DDD中包括问题域和解决方案域两个不同的维度。问题域主要从业务视角来考虑,完成从领域到子域的分解,而解决方案域则主要从技术实现的角度,通过划分限界上下文和采用DDD战术设计完成微服务的拆分和落地。"子域"和"限界上下文"这两个概念分别从不同的视角,构建起了DDD处理业务复杂度的根基。

个人认为"子域"和"限界上下文"在大多数情况下是一对一或者一对多的映射关系。从实践角度可以这样理解,我们不妨将业务领域的分解拆分为两个阶段:从领域到子域的粗粒度的分解和从子域到限界上下文的技术实现级的分解。有时候企业的业务领域非常庞大,不太方便用事件风暴对整个领域构建领域模型。所以在领域建模之前,我们先根据业务流程边界或者功能集合等要素,将庞大的领域分解成若干个大小合适的子域,然后根据子域属性划分为核心子域、通用子域和支撑子域。当领域分解到足够小后,我们就可以在这些子域内开展事件风暴,划分限界上下文完成领域建模了。

在对不同属性子域构建领域模型时,我们可能会有不同的关注点,比如在通用子域构建领域模型时,我们会更多关注领域模型的抽象和标准化,以便实现企业级复用,这种设计方法与中台的业务建模思想是一致的。当然,如果你的领域足够小的话,我们就没必要进行从领域到子域的分解和属性归类了,你可以直接开展事件风暴,直接划分限界上下文,完成领域建模。按照这种分解方式,如果子域和限界上下文边界刚好一致,那它们就是一对一的关系,而如果在一个子域内还可以划分为多个限界上下文,那我们最终得到的就是一对多的映射关系。需要注意的是,有些通用子域构建出来的领域模型往往会因为需要复用,可能会跨多个不同的业务子域,组合以后形成企业级能力。

限界上下文本质上就是子域,只不过它会更多地考虑领域对象的语义边界和技术实现细节。限界上下文的划分体现的是一种更为详细的设计过程,这个过程划分了业务的上下文语义边界,完成了领域模型,明确了领域对象以及领域对象之间的依赖关系等。至此,我们依据限界上下文和领域模型就可以完成微服务设计和落地了。

小结

通用语言是项目团队内部交流的统一语言,而通用语言的语义上下文环境则是由限界上下文来限定的,这个边界可以确保通用语言无二义性。在确定限界上下文边界时,会用到领域专家的经验。

领域专家和项目团队的主要工作,就是在业务领域内采用事件风暴,来划分限界上下文,建立领域模型。将领域模型映射到微服务,就完成了从业务领域到系统域的映射和系统落地。

限界上下文确定了微服务拆分和设计边界,是微服务拆分和设计的主要依据。如果不考虑技术异构、团队沟通等其他外部因素,一个限界上下文理论上就可以拆分为一个微服务。

可以说,限界上下文在微服务设计中具有很重要的意义,如果划分限界上下文的方向出现了偏离,那微服务设计的结果也就可想而知了。我们只有理解了限界上下文的真正含义和它在微服务设计中的作用,才能真正发挥DDD在微服务拆分和设计中的价值,这是基础也是前提。

Released under the MIT License.