全文约5300字,预计阅读时间13分钟。

ddd编程 承上启下的限界上下文很重要(1)

导读

继上一篇DDD系列文章第4篇:DDD如何做战略设计,做个懂业务的技术后,本篇继续介绍后续步骤,也是DDD里非常重要的限界上下文(英文名Bounded Context,下面简称BC)。BC承上启下了业务分析和技术架构。也是领域模型能够绑定业务知识和技术代码的前提。BC是很什么?如何划分?BC之间如何协作?BC和微服务什么关系?

01

什么是BC?

业务流程会伴随很长的生命周期,随着角色的不同和操作的往前推动进入不同的阶段,进而也会产生很多相似的概念。比如围绕订单串联起了电商购物的大部分流程。往往一个业务流程有时候复杂度太大,夹杂着多个有歧义的业务对象和概念。因此需要BC这个更细的粒度来限制这种复杂度。从字面上就知道限界上下文(BC)有两层意思。一是Bounded即有边界的,二是Context即业务相关的。BC好比国家:

有BC的好处

前面讲业务流程的时候说到了四要素(谁,在什么条件下,做了什么,导致了什么),其实就是业务的上下文信息。日常交流里说到上下文往往指上下文是特定语境的,不同的上下文说出来的同样一句话意思很可能不一样。《茶馆》里有句很著名的台词是这样说的:

宋恩子:我出个不很高明的主意,干脆来个包月,每月一号,按阳历算,你把那点……

吴祥子:那点意思!

宋恩子:对,那点意思送到,你省事,我们也省事!

王利发:那点意思得多少呢?

吴祥子:多年的交情,你看着办!你聪明,还能把那点意思闹成不好意思吗?

这段对话里几处讲到『意思』,但带着不同的语境也表达出了几种不同的含义。划分BC就是要把这种语境固定下来,A语境下的业务不要和B语境的业务混杂到一起,否则就加大了沟通的复杂性,理解起来也很考验每个人的经验,设计和实现的质量必然也会参差不齐。一个BC里的具体业务功能应该准确表达,不带多种语义,不带歧义。反面例子就是中餐烹饪里的某些菜谱,比如『中火,放盐少许,放油适量』等,完全不知道怎么操作,无法量化。

02

如何划分BC?

限界上下文划分得怎么样对后续的架构和编码都很重要。但是就像前面说到的例子,BC的划分没有标准答案,其中有很多经验成分,有没有稍微可以借鉴的方法呢?讲方法前还要再强调下意识问题。实际项目中看到很多人认为划分模块这件事不重要,没必要浪费时间,早点开始写代码才最重要。因此模块的划分环节简单了事,全凭经验,拍脑袋。DDD提出把划分BC作为设计中非常重要的一个步骤,这本身就已经很有警示意义了。

划分BC的前提

前篇提到业务流程的拆解和业务功能的拆解。这是划分BC的一个前提。业务需要拆到什么粒度呢?需要知道业务包含哪些用例(User Case),也有叫着业务接口,业务能力的。用例拆解到『谁操作了什么』就可以。比如讲线索管理业务需要知道有哪些用例:销售员创建线索、管理员分配线索、销售员修改线索、销售员给线索拨打电话、管理员收回线索等等。尽量把所有的用例找出来,至于说会不会遗漏用例,那就是前一篇业务流程分析的质量问题了,可以借助事件风暴等分析方法识别出所有的业务用例。

划分BC的几个步骤

  1. 【从业务视角拆分】从领域/业务视角根据业务相关性对用例做归类,垂直划分出多个BC
  2. 早前都把所有业务放到一个系统,微服务架构后要把业务垂直划分为多个系统,这里的系统跟BC大致相同,后文细说。
  3. 根据用例的功能相关性或语义相关性进行合并,优先考虑功能相关性。创建订单、拆分订单和支付订单都有『订单』语义上的相关性,但往往把创建订单和拆分订单归为一个订单BC,把支付订单归为单独的支付BC。因为业务上差别很大,各自涉及的业务逻辑和角色也不一样。
  4. 【从技术视角合并】以技术相关性合并为技术通用BC
  5. 订单BC和支付BC都需要非功能性需求如监控能力,甚至通用型功能需求如短信通知能力,为了节省建设成本和保持演进的独立性,往往把它们剥离出来,形成对应的技术型BC。
  6. 【从团队视角裁减】以团队粒度对BC大小粒度做裁减
  7. 这一点即前篇讲到的著名康威定律的实践。如果创建订单和支付订单相关的业务都很小,团队资源也少,分开维护浪费成本,往往把它们合并为一个BC,便于团队分工和效率。反之,如果订单BC业务规模大,涉及团队规模也大,应该把订单BC继续拆解。

案例补充

上面几点都是大面的偏原则性,离实际操作层面还有距离。我主张做一件事时如果没有明确的方法(即可以用演绎法推导完成)时,可先通过归纳法习得必要的经验。下面通过一些例子学习上面的原则,沉淀经验。

  1. 有些用例涉及多个名词。如从一个线索转成一个客户。属于线索BC还是客户BC?更多的还是从用例的执行结果看,影响了谁的状态多。这里更多的是一种特殊的客户创建行为,更大概率属于客户BC。再比如用户查询积分,应该属于用户BC还是用户权益相关的BC?同理更应该首先考虑划为后者。
  2. 多聚焦在用例的名词上面,而不是动词。比如把查询订单,查询商品,查询库存。要不要划为查询BC?首先应该看用例的名词,名词如果是个重要的业务概念,那业务相关性取决于名词。
  3. 技术因素先入为主。比如发送短信,发送邮件,发送微信等等,每个用例都涉及外部的技术系统。如果划分为短信BC、邮件BC、微信BC就是没优先考虑业务。从这些用例的业务相关性看更多的是在讲三种触达方式,划为触达BC会更好,当然,除非用例的名词涉及的业务概念变复杂而重要了,有可能出现单独的BC。
  4. 技术因素占上风的时候。
  5. 比如相对创建订单来说,查询订单的请求量大很多,如果放在一个BC内,会影响创建商品的业务能力。这时候可能出现把查询商品单独拆分出去,这也是互联网流量里多数是查询,少数是改写的现状下,可能会采取的划分办法。
  6. 把内部用户操作和外部用户操作分开。比如商品上架(创建商品)属于内部操作,查询商品多属于用户行为,为了不干扰用户查询,可能会分开。包括有些定时运行的用例比如凌晨在系统不繁忙时的清算、盘点类系统用例也可能这么划分。
  7. BC划分粒度太细。比如有些用例属于低频操作并且不是很重要的业务,跟其他用例相关性也少,那么没必要单独划分出BC,带来不必要的维护代价。
  8. 业务重要,但没有BC体现出来。比如给线索添加活动记录,给客户添加活动记录,给订单添加活动记录。活动记录这个业务需要更重要的时候,应该单独拆分出BC

总之,划分BC不是一蹴而就的,应该根据分析、设计、实现不同阶段的反馈结合起来考虑做出决定。同时随着业务发展而调整BC的粒度。

03

BC和统一语言

ddd编程 承上启下的限界上下文很重要(2)

相似概念和歧义概念比比皆是,BC是解决方案

工业设计大师柳冠中曾经说过的四品概念:产品、商品、用品、废品,一个工业品一般都会经历这四个阶段。CRM系统里的线索,营销部门通过投放广告和参加展会能留下线索,客服部门通过在线接待系统回到陌生访客的问题也能留下线索。销售部门通过跟进线索从而发现商机。这里其实出现了三个『线索』概念,看起来都是在讲线索,但其实它们各有各的不同。营销部门的线索可能只是对你的产品有点兴趣,只是留下了一个联系方式(不知道能不能联系上)。客服部门的线索有些只是网民访问你的网站留下来的记录,可能连联系方式也没留下。销售部门面对的线索是能联系的上,甚至对产品有一定购买意向。如果把这三个线索用一个业务概念来表达,那么不同部门人员之间的沟通往往会出现概念之间的歧义甚至冲突,系统的实现也会变得复杂且容易变化。

BC就是用来区分这种特殊性的。通过BC把有歧义的业务概念隔离开,各自回到各自的BC中。在BC里面形成统一的命名。语言交流层面如果发现障碍和歧义也侧面反映了BC划分的不正确性。

统一语言很重要

语言是交流的主要载体,语言不统一交流过程中就出现障碍和歧义。业务逻辑为什么复杂一方面也是因为多个相似概念的共存导致。因此统一语言既是日常交流利器,也承载了需求文档、设计文档、技术代码的可读性和可理解性。DDD能认识到通过统一语言倒逼BC的划分准确,倒逼出较高的设计质量,进而把语言渗透到无形的交流,有形的需求文档、设计文档、技术代码。全方位地降低了复杂度,提高了组织效率。因此在BC内建立起统一语言很重要,很有用。

04

BC和微服务

为什么有微服务架构?也是为了变革以前所有业务都揉在一起(俗称单体服务)维护带来的种种弊端。既然要把大服务变成多个小服务,微服务的核心当然是服务粒度的合理划分。合适大小的组织,采用合适的技术,维护和演进着合适大小的业务。而这就是DDD契合微服务的地方,靠的就是识别BC这个环节。

BC和模块、系统、服务之间什么关系?BC就是微服务吗?

  1. 模块往往是个逻辑概念,是一些业务概念的集合,比如我们口头经常讲的订单模块、财务模块。一个模块也许对应着多个服务,好比我们说的『华东地区』、『华北地区』。
  2. 系统和服务可以近似等价,都是需要部署在计算机里能单独运行的程序,并且向外提供业务能力。服务是有明确边界的,好比有明确行政边界的江苏省、浙江省、上海市。
  3. BC也有明确的业务能力边界。从这个角度看,BC更靠近服务而不是模块,但不完全等同服务。有时候可能因为部署成本考虑,暂不需要一个BC对应一个服务进行部署,因此可能多个BC对应一个部署服务。但反过来不应该出现,即一个BC部署成多个服务。实际情况下,尽量做到一个BC对应一个微服务。
  4. 一个BC也不能由多个团队同时负责,反过来一个团队可以维护多个BC。这跟微服务和团队的关系是一样的。

BC和微服务的共同点

BC和微服务都应该提供完备的业务能力。这也是两者和逻辑上的模块本质不一样的地方。什么叫完备的业务能力?就是说要能提供从调用者视角来说有用的业务能力。比如调用了订单BC的创建订单接口就能把订单创建出来才能说是完备的。创建的订单会存储在哪,需要短信通知谁,都应该是订单BC要负责到底的,对调用者来说,跟订单BC打完交道就放心了。

要做到这点就要求BC内部的技术实现也是完备的。微服务其实就是粒度适中的单体应用。以前单体服务的技术实现包含用户接口层、业务逻辑层、数据存储层、基础设施层。也就是说除了业务语义外,BC内还包含技术语义,即它和微服务一样,它的技术能力也是纵向垂直的。一个BC同时有自己的业务架构部分即提供了什么业务能力,还有应用架构部分即业务逻辑代码,还有数据架构即数据的存储和读取,还有技术架构即基础设施层。有了这个基础,BC和微服务都能做到技术演进的独立性。麻雀虽小五脏俱全,一个小小的BC既有业务边界也有技术边界。

05

BC的关系映射

ddd编程 承上启下的限界上下文很重要(3)

划分完BC后,一个业务流程可能涉及到多个BC,因此BC间的协作关系定义也非常重要。DDD里BC间的关系叫做BC Context Mapping,即上下文之间的关系映射。DDD里讲到了BC间有9种映射关系,一个BC要和另一个BC产生关联时也就出现了上游和下游两种身份,结合微服务架构下面几种是使用的比较多的映射关系:

  1. 防腐层关系。一个BC要调用另一个BC时,调用方为了不把被调用方的细节渗透到自己的业务领域模型,往往会建设一个防腐层,达到如果被调用方有变动也不会影响到自身核心业务领域逻辑的作用。这也是非常通用的一种做法。
  2. API接口关系(开放主机服务、客户方-供应方)。API是最直观的向外提供能力的方式,通过API调用可以复用业务能力。接口形式上通常有http、restful、rpc等不同调用方式。API接口方式的背后也实现了BC间不要直接访问对方存储的数据,而是通过API来交换数据,也就达到了数据归BC私有的目标。
  3. 领域事件关系。一个BC通过发布领域事件,其他BC可以订阅领域事件,进而完成业务流程的后续操作。这个方式需要跟API方式结合起来思考,API强调实时性,发起了调用就会一直等待API接口的返回。领域事件强调连续性,一个BC先做完了一件事,后续的事由其他BC继续做,但不用互相等待。在微服务架构下,提倡尽量通过事件等异步的方式串联业务流程。
  4. 共享内核关系。这个方式下,多个BC可能共同建设和使用同一个公共代码库。这就要求相互间有良好的合作基础。微服务架构下,尽量少用共享,多提倡服务间独立发展演进。

BC间合理的协作关系识别也影响着以后对应团队间的协作,因此BC的粒度划分跟团队分工关系密切,BC间的关系映射也同样影响着团队间的合作关系。因此BC的关系映射也需要合理制定。

06

结语

限界上下文是战略设计和战术设计的关键环节,也是业务和技术的关键衔接点,需要多花些时间考虑BC的粒度大小、边界在哪。以前的中国社会多讲人情,现代越来越强调法治社会。其中的差异跟BC的精髓高度一致,那就是边界的制定。人情社会里有很多说不清道不明的事情,法治社会希望通过契约精神,在明确责任和义务的边界上建立有效的可持续的合作。边界是合作的基础,边界是独立的前提,边界无处不在,边界影响深远。

注:本篇首发于非写不可 - DDD系列文章第5篇:承上启下的限界上下文很重要

,