假设有一个农业机械零件的批发商。他们建立了一个 B2B 网上商店,供经销商和机器维修公司订购。在他们无处不在的统一语言与术语中,订单代表了这个自动化流程:它使客户能够挑选产品,应用正确的折扣,并将其推送到 送货。
如果这个批发商与竞争对手合并:他们是老牌企业,拥有稳固的客户群和庞大的目录。后者也有一个订购系统,但它更传统:客户打电话,客户经理输入订单,应用任意折扣,然后将其推送到送货。
合并后的公司仍然只有一个 Sales销售 子域,但它现在有两个 Sales Bounded Contexts销售上下文。它们两个模型中都有像 Order 和 Discount 这样的概念,这些概念的含义基本相同。两个批发商的员工就订单或折扣达成一致。但是他们有不同的使用流程,他们在表单中输入不同的信息,并且有不同的业务规则。
在技术意义上,差异体现在对象设计、方法、工作流、应用折扣的逻辑、数据库表示和一些统一语言与术语中。不过,它运行得更深入:对于软件设计人员来说,要在任一有界上下文中都富有成效,他们必须了解这两种模型之间的许多区别:有界上下文代表可理解边界。( 上下文为王 )
在一个完美设计的系统中,我们理想的有界上下文通常会与子域的边界很好地对齐。但有界上下文实际上是遵循系统演进的。
系统随着公司组织发展。不幸的是,这个发展过程中呈现的新概念经常不以符合我们设计的方式出现,我们对变得更加不一致的代码感到不舒服了,只要我们有时间就想要统一这些新旧概念并制作干净的抽象,因为我们应该创建一个设计良好的系统。
子域与有界上下文区别
上面的例子简单地表明,单个子域实际可以由多个有界上下文表示。
领域与有界上下文区别:有界上下文可能与应用或服务边界一致,也可能不一致。同样,它们可能与领域边界对齐,也可能不对齐。领域存在于问题空间中。领域是组织如何看待其活动和专业领域的方式;而有界上下文是属于解决方案空间的一部分,它们是经过深思熟虑的设计选择。
作为系统设计师,您选择这些边界来管理系统的可理解性,通过使用不同的模型来解决领域的不同方面。
对于上面案例,系统设计者可以:
- 合并两个销售上下文,
- 将一个迁移到另一个里面,
- 建立一个新的销售上下文来取代两者,
- 推迟这项努力,
- 或者不做任何事情并保留现有的两个上下文。
这些都是设计选择,即使 CEO 最终会从这些选项中进行选择(例如,由于预期的投资回报率)。或许,在权衡之后,将两个销售上下文并排放置是目前最好的战略设计选择,因为它允许合并后的公司专注于新的机会。毕竟,现有系统确实服务于它们的目的。这里的要点是,为单个子域设置两个有界上下文可能是一个非常有效的选择。
一个子域有多个上下文
当没有外部触发(如批发商合并)时,您是否还会故意选择将单个域拆分到多个上下文中?
假设你为一位小商品贸易商提供咨询。他们专注于一些特殊商品,由 20 名交易员、一些运营支持角色和大约 10 名开发人员组成。首席工程师参观了该系统,该系统由单个交易域的 20 个有界上下文组成。每个交易者都有一个上下文。
这看起来很奇怪,我们的本能是找出相似之处,将它们抽象出来,并使它们可重复使用。开发人员没有这样做。他们充其量只是复制粘贴彼此的代码块。首席工程师主要关心的是“我们是否针对我们的情况进行了优化设计?” 他们担心自己陷入了大麻烦。
每个交易者都有自己的交易代表。整个公司甚至有多种金钱货币体系。交易者的算法不同,尽管许多人做的事情有些相似。每个交易者都有不同的仪表板。开发人员使用相同的第三方库,但是当他们彼此共享自己的代码时,他们并没有尝试统一它。相反,他们复制了代码,并随着时间的推移按照他们认为合适的方式进行修改。许多工作涉及数学算法,而不是典型的面向业务的 IT。
事实证明,每个交易者都有独特的需求。他们需要快速行动:他们尝试了不同的算法、预测和看待市场的方式。开发人员为交易者服务,并与他们密切合作,不断将他们的想法转化为新代码。交易员受到高度重视,他们是压力大、竞争激烈的环境中的首要人物。如果你想成为其中的一部分,你就不能成为一个缓慢的编码者或一个数学懒鬼。没有(Jira)票,没有功能积压。这是领域专家和程序员之间的终极快速反馈循环。
事情每天都在快速变化。找到正确的抽象需要大量的协调,它会大大减慢开发速度。试图统一代码会毁了公司。
这种设计并不充满技术债务。这也不是遗留问题,多年来设计意外地以这种方式发展。该代码正在工作。这种缺乏统一抽象的设计是一种深思熟虑的设计选择,符合目的,即使起初看起来是一个激进的选择。所有的开发者和交易者都对它感到满意。
这些也不仅仅是具有大量重复代码的独立程序。这是一个单一的领域,分为 20 个有界上下文,每个有自己的领域模型、自己的通用语言和自己的变化率。协调模型中的语言和概念会增加这种高速环境中的摩擦。通过故意选择设计单独的上下文,他们消除了这种摩擦。
权衡
权衡设计选择的后果是:当开发人员需要帮助解决问题时,他们必须让其他开发人员跟上进度。每个开发人员在另一个有界上下文中工作时,都希望他们必须进行上下文切换。毕竟,它们的术语和概念彼此不同,即使它们共享相似的术语。上下文切换是有代价的,如果您整天处理不同的项目,您可能已经体验到了这种代价。但是在这里,因为上下文显然是有界的,所以这并没有引起很多问题。有时,通过向具有相似上下文背景(但有界上下文不同)的另一位开发人员解释问题,解决方案变得显而易见。
一般 IT 系统中的多个有界上下文
交易系统是一个极端的例子,您不会遇到许多环境,其中具有 20 个有界上下文的单个子域是有意义的。但是在许多常见情况下,您应该考虑拆分域。如果在您的公司中,个人客户和企业客户的定价规则不同,那么在单个域模型中统一这些规则的努力可能会花费更多。或者在薪资系统中,受薪员工和计时员工的规则和流程不同,您最好拆分此域。
总结
正确的上下文边界遵循业务的轮廓。不同的区域在不同的时间以不同的速度变化。随着时间的推移,看起来相似的东西可能会以令人惊讶和出人意料的方式产生分歧(如果有机会的话)。将概念压缩到单个模型中是有限制的。通过使其服务于两种不同的业务需求并持续承担协调成本,您正在使模型复杂化。这是一种隐藏的依赖债务。
我们可以在这里推导出两种启发式方法:
- 限界上下文/有界上下文不应该服务于设计师的感性和对完美的需求,而应该提供商业机会。
- 变化率启发式:考虑组织有界上下文,以便它们管理以相同速度变化的相关概念。
由 Mathias Verraes @mathiasverraes和 Rebecca Wirfs-Brock @rebeccawb 撰写。