上一篇介绍了,通过事件风暴的方式,和领域专家一起来识别业务流程,领域事件以及领域概念。本篇将在上一篇识别出来的业务流程的基础上,识别限界上下文。
下面的内容引用自https://gitbook.cn/gitchat/column/5cdab7fb34b6ed1398fd8de7/topic/5cdad4e234b6ed1398fd8f5a
一、限界上下文的来由理解
之前说了整个DDD是应用于复杂项目设计的,其实核心原则也是常见的高内聚,低耦合原则。主要方法也是控制整个项目的复杂度,对整个业务领域进行子领域的拆分。比如捷信达平台,根据对应的业务,拆分为了酒店PMS系统,餐饮系统,温泉系统,水疗系统等。
但是大家发现,将整个平台拆分成子领域后,子领域内部还是很复杂,每个业务系统还是有很多业务流程,并且有些业务流程可能是在多个业务系统可能都需要的。比如VIP识别通知,比如微信支付,支付宝支付等的支付流程。所以还需要一种方法,继续将子领域的业务流程进行拆分,既达到减少复杂度的目的,又达到功能在各个子领域复用的目的。
这个在子领域中进行继续拆分的方法就是我们今天要讲的主解:限界上下文。
二、限界上下文的定义
前面说了限界上下文是为了继续在子领域的业务流程中进行拆分,将整个业务流程中关联紧密的拆分在一起,形成一个上下文,将不太紧密或者不相关的业务流程拆分到不同的上下文中,使用限界进行分隔。因而,**上下文(Context)其实是动态的业务流程被边界(Bounded)静态切分的产物**。<br />假设有这样一个业务场景:我作为一名咨询师从成都出发前往深圳为客户做领域驱动咨询,无论是从家乘坐地铁到达成都双流机场,还是乘坐飞机到达深圳宝安,再从宝安机场乘坐出租车到达酒店,我的身份都是一名乘客(Passenger),虽然因为交通工具的不同,参与的活动也不尽相同,但无论上车、下车,还是办理登机手续、安检、登机和下机等活动,终归都与交通出行有关。那么,我坐在交通工具上就一定代表我属于这个上下文吗?未必!注意在交通出行上下文中,其实模糊了“我”这个概念,强调了“乘客”这个概念,这是参与到该上下文的角色(Role),或者说“身份”。<br />例如,我在飞机上,忽然想起给客户提供的咨询方案还需要完善,于是我拿出电脑,在一万米高空上继续思考我的领域驱动设计方案,这时的我虽然还在飞机上,身份却切换成了一名咨询师(Consultant)。当我作为乘客乘坐出租车前往酒店,并到前台办理入住手续时,我又“撕下了乘客的面具”,摇身一变成为了酒店的宾客(Guest)。次日早晨,我在酒店餐厅用完早餐后,离开酒店前往客户公司。随着我走出酒店这个活动的发生,酒店上下文又切换回交通出行。当我到达客户所在地时,面对客户,我开始以一名咨询师身份与客户团队交谈,了解他们的咨询目标与现有痛点。我制定咨询计划与方案,并与客户一起评审咨询方案,这时的上下文就切换为咨询工作了。巧合的是,无论是交通出行还是酒店,都需要支付费用,支付的费用虽然不同,支付的行为也有所差别,需要用到的领域知识却是相同的,因此这个活动又可以归为支付上下文。<br />上下文在流程中的切换犹如电影画面的场景切换,相同的人物扮演了不同的角色,在不同的上下文参与了不同的活动。由于活动的目标发生了改变,履行的职责亦有所不同,上述场景如下图所示:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/200534/1629458531081-a8d1fc7f-0ca3-4f77-9d42-1882815bacdb.png#clientId=u425630a0-7c7f-4&from=paste&id=u2b73fb89&margin=%5Bobject%20Object%5D&name=image.png&originHeight=864&originWidth=1540&originalType=url&ratio=1&size=170240&status=done&style=none&taskId=uda006222-a8f7-403e-927c-cb35da80254)<br />整个业务流程由诸多活动(Actions)组成,参与这些活动的有不同的角色。在每一个上下文中,角色与角色之间通过活动产生协作,以满足业务流程的需求。这些活动是分散的,活动的目标也不相同,但**在同一个上下文中,这些活动却是为同一个目标提供服务**。<br />因此,在理解限界上下文时,我们需要重视几个关键点:
- 知识:不同的限界上下文需要的领域知识是不相同的,这实则就是业务相关性,参与到限界上下文中的活动也与“知识”有关。如果执行该活动却不具备对应知识,则说明对活动的分配不合理;如果该活动的目标与该限界上下文保持一致,却缺乏相应知识,则说明该活动需要与别的限界上下文协作。
- 角色:一定要深入思考参与到这个上下文的对象究竟扮演了什么样的角色,以及角色与角色在这个上下文中是如何协作的。
- 边界:限界上下文按照不同关注点进行分离,各自的边界则根据耦合关系的强弱来确定,越是关系最弱的地方,越是需要划定边界。
我们需要根据业务相关性、耦合的强弱程度、分离的关注点对这些活动进行归类,找到不同类别之间存在的边界,这就是限界上下文的含义。上下文(Context)是业务目标,限界(Bounded)则是保护和隔离上下文的边界,避免业务目标的不单一而带来的混乱与概念的不一致。
三、理解限界上下文的价值
对外接口统一
根据识别的业务场景中的角色,从业务的角度来定义角色需要的业务操作接口,只要业务场景中角色需要的业务操作没有大的变动,则可以保持对外的业务操作接口的稳定性。
对内按需进化
在保证对外接口不变的情况下,业务场景内的业务规则的变化或者操作方式的变化,可以按需进行变化,不影响整个上下文对外提供的接口。
并且由于限界上下文拆分后,上下文内的模型相对简单,易于理解,易于维护和扩展。
边界控制
在和其他限界上下文之间有明显的边界,大体可以分为如下三个方面:
- 领域逻辑层面:限界上下文确定了领域模型的业务边界,维护了模型的完整性与一致性,从而降低系统的业务复杂度。
- 团队合作层面:限界上下文确定了开发团队的工作边界,建立了团队之间的合作模式,避免团队之间的沟通变得混乱,从而降低系统的管理复杂度。
- 技术实现层面:限界上下文确定了系统架构的应用边界,保证了系统层和上下文领域层各自的一致性,建立了上下文之间的集成方式,从而降低系统的技术复杂度。
这三种边界体现了限界上下文对不同边界的控制力。业务边界是对领域模型的控制,工作边界是对开发协作的控制,应用边界是对技术风险的控制。引入限界上下文的目的,其实不在于如何划分边界,而在于如何控制边界。
四、限界上下文的要求
限界上下文必须是自治的。所谓“自治”就是满足四个特征:最小完备、稳定空间、自我履行、独立进化。如下图所示的自治单元就是限界上下文,映射到编码实现,则可能是模块、组件或服务:
最小完备
最小完备是实现“自治”的基本条件。所谓“完备”,是指自治单元履行的职责是完整的,无需针对自己的信息去求助别的自治单元,这就避免了不必要的依赖关系。而“最小完备”则进一步地限制了完备的范围,避免将不必要的职责被错误地添加到该自治单元上。对于限界上下文而言,就是要根据业务价值的完整性进行设计。例如,对于支付上下文,其业务价值就是“安全地完成在线支付业务”,那么在确定限界上下文的时候,就应该以完成该业务价值的最小功能集为设计边界。
自我履行
自我履行意味着由自治单元自身决定要做什么。从拟人的角度来思考,就是这些自治单元能够对外部请求做出符合自身利益的明智判断,是否应该履行该职责,由限界上下文拥有的信息来决定。例如,可以站在自治单元的角度去思考:“如果我拥有了这些信息,我究竟应该履行哪些职责?”这些职责属于当前上下文的活动范围,一旦超出,就该毫不犹豫地将不属于该范围的请求转交给别的上下文。例如,在当订单上下文履行了验证订单的职责之后,需要执行支付活动时,由于与支付相关的业务行为要操作的信息已经超出了订单上下文的范畴,就应该将该职责转移到支付上下文。自我履行其实意味着对知识的掌握,为避免风险,你要履行的职责一定是你掌握的知识范畴之内。
稳定空间
稳定空间指的是减少外界变化对限界上下文内部的影响。自治的设计就是要划定分属自己的稳定空间,让自治单元拥有空间内的掌控权,保持空间的私密性,开放空间接口应对外部的请求。划分自治空间,需要找到限界上下文之间的间隙处,然后依势而为,沿着间隙方向顺势划分,而所谓“间隙”,其实就是依赖最为薄弱之处。例如,在电商系统中,管理商品上架、下架与评价商品都与商品直接相关,但显然评价商品与商品的依赖关系更弱。倘若需要分解限界上下文,保证上下文的稳定性,就可以将评价商品的职责从商品上下文中分离出去,但却不能分离商品上架和下架功能。稳定空间符合开放封闭原则(OCP),即对修改是封闭的,对扩展是开放的,该原则其实体现了一个单元的封闭空间与开放空间。封闭空间体现为对细节的封装与隐藏,开放空间体现为对共性特征的抽象与统一,二者共同确保了整个空间的稳定。
独立进化
独立进化与稳定空间刚好相反,指的是减少限界上下文的变化对外界的影响。如果借用限界上下文的上下游关系来阐释,则稳定空间寓意下游限界上下文,无论上游怎么变,我自岿然不动;独立进化寓意上游限界上下文,无论下游有多少,我凌寒独自开。实现上看,要做到独立进化,就必须保证对外公开接口的稳定性,因为这些接口往往被众多消费者使用,一旦修改,就会牵一发而动全身。一个独立进化的限界上下文,需要接口设计良好,符合标准规范,并在版本上考虑了兼容与演化。
自治的这四个要素是相辅相成的。最小完备意味着职责是完备的,从而减少了变化的可能;自我履行意味着自治单元能够智能地判断行为是否应该由其履行,当变化发生时,也能聪明审慎地做出合理判断;稳定空间通过隐藏细节和开放抽象接口来封装变化;独立进化则通过约束接口的规范与版本保证内部实现的演化乃至于对实现进行全面地替换。最小完备是基础,只有赋予了限界上下文足够的信息,才能保证它的自我履行。稳定空间与独立进化则一个对内一个对外,是对变化的有效应对,而它们又是通过最小完备和自我履行来保障限界上下文受到变化的影响最小。
这四个要素又是高内聚低耦合思想的体现。我们需要根据业务关注点和技术关注点,尽可能将强相关性的内容放到同一个限界上下文中,同时降低限界上下文之间的耦合。对于整个系统架构而言,不同的限界上下文可以采用不同的架构风格与技术决策,而在每个限界上下文内部保持自己的技术独立性与一致性。由于限界上下文边界对技术实现的隔离,不同限界上下文内部实现的多样性并不会影响整体架构的一致性。
五、限界上下文与子领域的关系
- 一对一关系
- 当子领域中包含的业务足够简单,不再需要继续拆分时,则子领域就是一个限界上下文
- 一对多关系
- 当子领域中包含的业务流程较多,还是很复杂时,需要进一步进行拆分,并且拆分后的限界上下文只会被这个子领域使用时,则一个子领域就会包含多个限界上下文
- 多对多关系