00前情回顾
本系列的上一篇文章阐述了我对限界上下文的理解,概括为:
- 六要素:领域知识、领域对象、知识语境、角色、活动和业务能力
- 两本质:领域模型的知识语境、业务能力的纵向切分
- 四特征:最小完备、自我履行、稳定空间和独立进化
有感于Eric Evans DDD中并没有提出与限界上下文相对应的架构模式,我在整洁架构和六边形架构的基础上提出了菱形对称架构,通过“内外分离、南北对称”的架构模式更好地保证限界上下文的自治性,从而促进架构的演进能力。
当然,也有人提到菱形对称架构有些抽象过度。我不否认这个问题,外部的网关层在隔离变化的同时,自然也引入了许多间接的成本。这本身就是一个it depends的问题。当限界上下文的领域模型相对稳定,自然也可以采用共享内核的方式,将领域模型的这层外壳直接去掉,结构会变得更简单一些。
不管是我提出的菱形对称架构,还是已有的整洁架构、洋葱架构、六边形架构,甚至是分层架构,它实际上都是“关注点分离”原则的体现,是施加到软件系统之上的一种约束规则。规则就是拿来打破的,谁规定一定需要遵循规则呢?前提在于,你有没有做出更佳判断的能力。
我提出领域驱动设计统一过程、业务服务、菱形对称架构,以及后面将要讲到的服务驱动设计,目的就是固化设计流程,将设计的门槛降低,让团队能够实施和落地DDD。自然,这种固化就不可避免会带来设计上某种程度的僵化。
因此,我的一贯主张为:
- 如果你的设计能力很棒,那就抛开规则对你的约束,前提是你能得到一个更佳的方案;
- 否则,那就遵循规则,保证团队交付的质量能够“取乎上而得其中”。
若设计分数满分为100分,没有人能得到满分,90分已是极佳。然则,90分虽好,在不制订约束规则的前提下,交给每个团队成员自行做决定,谁能保证得到高分?与其给出不及格的分数,还不如退而求其次,拿一个马马虎虎的80分,不是很好吗?
01识别限界上下文
既然限界上下文如此重要,如何识别限界上下文就成了重中之重。
识别限界上下文当然不能拍脑袋凭经验,可许多内容又不得不借助经验。我给出的识别方法,无法做到像数学公式那样简单精准,只能适度降低经验在其中扮演的重要作用。
识别限界上下文,不仅仅要获得有哪些限界上下文。在给出的架构方案中,如果你只是画一些框图,说明这个系统有哪些限界上下文,其实对于开发团队而言,并没有价值。因为即便我们知道了有哪些限界上下文,团队也不明确这些限界上下文的职责,边界不清晰,带给设计的约束能力无形中就消失了。
我们必须在识别出限界上下文的同时,还需要明确问题空间中的业务服务与限界上下文之间的映射关系。
识别限界上下文不是一蹴而就的,需要经历多次迭代,也可能在识别之后还要经历不断的演化。识别的过程是一个动态的过程,大体上,需要从领域维度、技术维度和团队维度对限界上下文的边界进行校准。
1
领域维度对限界上下文的识别,其实就是在问题空间获得业务服务,然后针对业务服务根据业务相关性对它们进行归类和归纳,获得业务主体,进而根据高内聚低耦合原则以及限界上下文的本质,进一步对业务主体的边界进行调整,获得初步的限界上下文。
从问题空间分析获得业务服务是一个自上而下的过程,对业务服务进行归类和归纳则是一个自下而上的过程,合起来,恰好形成一个V形,故而,我将这一识别限界上下文的过程称为“V形映射过程”:
业务相关性分为语义相关性和功能相关性。
业务服务以动词短语形式表达,如果描述中包含了宾语,它通常就是一个名词。对名词概念进行分析,只要该名词概念相同或者相似,都可以认为它们具有语义相关性。如下业务服务的列表:
- 查询作品
- 预览作品
- 发布作品
- 阅读作品
- 收藏作品
- 评价作品
- 购买作品
显然,它们都具有“作品”的语义,故而优先考虑将它们归类到一起。
所谓“功能相关性”,并非是指业务服务之间彼此存在功能的调用关系,因为根据业务服务的定义来看,不应该存在彼此互相调用的业务服务,所以这里提到的功能相关性,实际上指的是它们具有共同的业务目标。以下面的业务服务为例:
- 标记精彩内容
- 撰写读书笔记
- 评价作者
- 加入书架
它们具有共同的业务目标,都是为读者提供服务,故而可以考虑将其归类到一起。
完成归类后,再进行归纳。从同一个类别的业务服务中找到一个共同的特征,将该特征用一个简单的名词来表示。如果找不到共同特征,则说明之前的归类过于分散;如果需要多个名词表示,同样说明分类不合理,或者就是概括的特征抽象层次太低,需要建立更高的抽象。
有时候,针对两个不同的分类,在更高的抽象层次上,体现的是相同的特征,则可以将分类合并。例如以下业务服务:
- 建立读者群;
- 加入读者群;
- 发布群内消息;
共同体现了“读者群”的语义。
以下业务服务:
- 实时聊天;
- 发送离线消息;
- 一对一私聊;
- 发送私信;
共同体现了聊天的业务目标。
但在更高的抽象层次上,它们其实都属于“社交”的范畴,可以将其放到同一个类别中,命名为“社交”:
如此归类稍显粗糙,也未体现限界上下文的特征,故而我将其名为“业务主体”,之后还要根据亲密度和限界上下文的本质特点对业务主体进行调整。
亲密度是高内聚低耦合的体现。只要业务服务的归类分配合理,就应该满足“同一个业务主体的业务服务之间,其亲密度必然高于不同业务主体业务服务之间的亲密度”。如果做不到,只能说明分配的不合理,需要进一步调整。
得到调整后的业务主体及其内部的业务服务后,再结合统一语言,询问自己:
- 同一个业务主体内的业务服务中,语义上是否存在知识语境的冲突?
- 同一个业务主体内的业务服务共同表达了相关的业务能力了吗?
这正是根据限界上下文的本质进行的进一步甄别。
由此获得的限界上下文还未必正确,我归纳了四个设计原则用以检验限界上下文的识别是否合理,分别为:
- 单一抽象层次原则:识别出来的每个限界上下文在抽象层次上应该保持同一水平,不允许在抽象层次上出现彼此包含的情况
- 奥卡姆剃刀原则:即“若无必要勿增实体”,这里所谓的“实体”,指的是限界上下文,也就是说,是否要分离出一个独立的限界上下文,需要给出充分的理由
- 正交原则:多个限界上下文之间可以出现依赖,但不能出现不必要的重复,否则就违背了正交性
- 最小惊讶法则:业务服务到限界上下文的映射应该是理所当然,限界上下文的名称和它要履行的业务能力也应该理所当然,如果对限界上下文的识别让人感到很惊讶,说明它存在不合理的地方,需要调整
根据这四个原则对限界上下文一一进行校验和检查后,领域维度识别出的限界上下文就基本合理了。
2
领域驱动设计识别限界上下文,一定是领域维度优先,否则谈什么领域驱动设计呢?根据领域维度识别出限界上下文后,再考虑技术因素。如果确实因为某种技术原因,要求将一些业务服务独立出来,也就相当于给奥卡姆剃刀原则提供了分离的理由。
例如,因为性能或高并发的质量属性需求,要求某些业务服务必须使用专门的资源;又例如因为安全级别的不同,针对某些业务服务提出了非常规的安全访问和控制要求,由于限界上下文是业务能力的纵向切分,其边界内实际上还包括对数据的管理,要满足安全要求,也只能将其单独分离。
由此,可以从技术维度单独分离出限界上下文。这可以认为是技术维度对领域维度的一种干扰,或者说是质量属性对业务需求的影响。考虑到限界上下文属于解空间的范畴,适度考虑技术实现因素,也是合理的行为。
3
团队维度对限界上下文边界的影响,建立在“康威定律”的基础上。一个软件系统架构的组织结构应与开发该软件系统的团队组织结构保持一致,这已经成为大多数软件团队的共识。
一个软件团队到底该多大规模呢?Amazon的CEO Jeff通过形象的two pizzas' team给出了答案,也就是大概7人左右的规模。
不仅要保证团队的规模,针对开发限界上下文的团队而言,还要建立特性团队,即面向垂直领域特性的全功能团队。
将这三个团队原则结合起来,我们就建立了领域特性团队。它与限界上下文的关系如下图所示:
团队维度对识别限界上下文的约束是一个动态的过程,这恰好满足系统范围的特征,即它是在有限边界的界定下无限延申的范围。我们在识别限界上下文时,并不能保证已经识别出整个系统范围的所有业务服务,也不能保证未来不会发生需求的变化或扩充,既然需求要发生变化,限界上下文当然也可能变化。遵循康威定律,我们要保证团队的职责边界不要出现混乱,如此也就能保证限界上下文的边界不会因为变化导致混乱。
由此,可以总结出识别限界上下文的完整过程:
02工作坊演练
毫无疑问,以上总结的识别过程无法做到数学公式一般的准确和客观,它需要结合经验以及对业务的理解进行推演,方可得到不错的设计结果。这样的经验是没法通过文字传递给大家的。最好的方式,就是进行实际案例的演练。
终于轮到技术部落案例的闪亮登场了。我在一文中已经介绍了该案例的业务背景。这里不妨再啰嗦一遍。
技术部落平台是为IT技术人员打造的一个社交和知识分享的平台:
- 每个用户都可以创建属于自己的部落,也可以申请加入到别人的部落,成为部落的会员
- 只有部落的会员(包括创建人)才可以在部落中分享、发布文章、组织活动且发布活动信息、开展直播、提出问题、回答问题
- 每个用户都可以在平台发布求职信息
- 企业用户是付费用户,可以在平台发布招聘信息,平台也可以推荐人才给企业用户技术部落平台
为了提升工作坊的演练效率,我已经识别出该平台的业务服务,并在processon上创建了业务服务列表的模型。下图展示了该列表的一部分: