本文摘自《程序员必备的思维能力》
限界上下文
任何问题都是有边界的,这个问题的边界也叫这领域边界。也就是说,我们要对一个问题进行分析或者建模,一定是在一个特定的上下文(Bounded Context:限界上下文)下,而不可能是漫无边际的乱讨论。
因为语言的多意性和灵活性,会导致同一个概念,在不同的上下文呈现出不同的语义,包含不同的属性。就像Apple,在水果卖场这个上下文下,它代表的是水果;而在手机卖场,它代表的是iPhone。同一个词语因为上下文不一样,其含义完全不一样。
限界上下文告诉我们,同一个概念,不必总是对应于一个单一模型,也可以对应于多个模型。用限界上下文明确模型要解决的问题,可以保持每个模型的清晰。限界上下文是领域模型的边界,也就是领域知识的边界。和上下文主题紧密相关的模型内聚在上下文内,而其他模型被会分到其他限界上下文中。限界上下文内的领域知识是高内聚低耦合的。
比如商品作为电商业务最核心的要素,贯彻整个商业活动始终。在庞大的电商系统做领域化拆分之后,商品的概念也是渗透到各个子域当中,售卖的是“商品”,营销的主体是“商品”,下单是针对“商品”的订单,仓库中存的是“商品”,履约是“商品”的送达等等。然而这些“商品”在不同的上下文下,表达的语义和内涵是不一样的。
上下文映射
每一个上下文都有一套自己的“语言”,如果在该领域要使用其它领域的概念,我们就需要一个“翻译器”,这个在不同领域之间进行概念转化、信息传递的动作就叫上下文映射(Context Mapping)。上下文映射主要有两种解决方案:共享内核和防腐层。
共享内核
所谓的共享内核(Shared Kernel),是指把两个子域中共同的实体概念抽取出来,形成一个组件(java中的jar包),然后通过内联(inline)的方式,分别被不同的子域使用。
还是以电商系统为例,商品域中的“商品”和售卖域中的“商品”虽然都叫商品,但是两者的属性是不一样的,比如在商品管理中,主要关注的是商品的产品属性和产品分类,主要是用来做商品管理,因为还没有上架销售,所以也就没有商家属性(sellerId)。而在售卖域,商品是用来面向消费者销售的,相比较商品域中的商品或者产品,它多了商家属性、售卖价格、营销属性、买点等更多的属性。
鉴于商品域和售卖域中的“商品”大部分属性都是相同的,我们可以考虑使用共享内核的方式。如下图所示,将共同部分抽取成一个组件,由商品域和售卖域共享,这种方式的好处是最大程度的代码复用和能力共享,然而坏处也很明显,即高耦合:任何对于“共享内核”的改动都要小心翼翼的协调两个领域的技术团队,且会影响两个领域。说实话,这个副作用有点伤不起,所以在实践中,更推荐的上下文映射方法是防腐层。
防腐层
所谓的AC(Anti-Corruption:防腐层),在一个领域中,如果需要使用其它领域的信息,可以通过AC进行防腐和转义。实际上,在微服务的环境下,服务调用是一个普遍的诉求,因为没有一个服务是孤立的,都需要借助其它服务提供的数据,共同完成业务活动。
同样是上面的电商场景,如果采用AC的方式进行上下文映射的话,我们需要在售卖域中建一个AC模块。因为售卖域在商品上架销售的时候,需要从商品域获取商品的信息,在分布式环境下,这种信息获取可能是RPC调用,也可能是REST,通讯协议可能不一样,但并妨碍信息的载体都是商品DTO(Data Transfer Object)。AC的作用就是要把外域的DTO转义成自己领域内的概念。
这样做虽然有一定的代码重复,商品信息在商品域和售卖域都有一定的重复。但是解耦非常彻底,售卖域的AC起到了“防腐”的作用,商品域的任何变化并不会直接影响到售卖域,双方都拥有了更大的自主权和灵活度。系统架构就是这样,我们永远要在重复(Duplication)和复用(Reuse)之间去一个折中,进行权衡。关于更多AC的设计细节,我会在COLA一章中进行更加详细的介绍,这里就不多说了。