在软件开发领域,相信大家对解耦这个词并不陌生。在面向对象的背景下,我们将应用SOLID原则构建高内聚低耦合的应用程序,实现模块之间的解耦;在对复杂业务系统进行分析和建模时,我们会利用DDD的战略战术设计来帮助划分领域,实现分布式系统中服务的解耦;当我们组织大型敏捷开发团队一起工作时,我们可以通过组建自治团队来减少摩擦,从而实现团队层面的解耦。可见解耦无处不在,为此而投资,大家会觉得政治上极其正确,因为实现解耦,我们的系统和应用可以更快的扩展和演进,我们的团队可以更顺畅的协作,实现业务价值更快。然而,当我们暂时抛开将得到的各种好处,思考如何实现时,却发现解耦这个词的含义过于抽象和模糊。它既不描述最终状态也不提供实现方法。那么当我们谈到解耦时,具体内容是什么呢?所谓耦合,从字面上理解就是两个或两个以上的对象或系统相互作用、相互影响。对应以上软件开发的场景,我们可以将其转化为两个或多个模块。/系统/团队之间的交互会相互影响。在软件需要解决的业务问题越来越复杂的今天,单个系统或者团队很难在不依赖外界的情况下实现业务目标,所以我理解的解耦并不是消除耦合(相互之间的作用和影响/依赖)),而是指我们应该如何使用一定的方法和规则来设计和管理上述多个元素之间的依赖关系,降低耦合度,使整个系统运行有序,顺利。本文将从服务上下游的角度,探讨在系统架构演进过程中,如何保持服务之间的松耦合,达到解耦的目的。定义上下游思维定义了服务的上下游。在DDD建模方法中,在确定了限界上下文(boundedcontext)之后,上下文依赖的方向在上下文映射(contextmapping)中使用upstream和downstream来表示,其确定的依据是下游需要了解上游领域知识来实现??业务,反之亦然。扩展的意思就是上游业务能力不需要关心下游服务的存在,下游服务的开发依赖于上游提供的业务能力。下图是有界上下文映射的例子:图片来源:https://www.oreilly.com/library/view/what-is-domain-driven/9781492057802/ch04.html当我们基于以上boundedcontext和落地时,理想的情况是一个boundedcontext对应一个应用服务。参照限界上下文的上下游关系,我将上下游思维定义为:上游服务不受下游服务的业务能力和可用性的影响,反之亦然。我们会发现,服务之间的上下游关系比限界上下文中领域知识的上下游关系要复杂得多,而且上下游关系也会随着集成方式的不同而发生变化。基于上下游思维的耦合层次基于服务的上下游思维,我将服务间依赖的耦合度按照以下维度进行分类:Level4:领域知识互为上下游,业务可用性互为上游anddownstreamLevel3:领域知识相互上下游,业务可用性单向上下游Level2:领域知识单向上下游,业务可用性相互上下游Level1:领域知识单向上下游,业务可用性是上下游单向的因为松耦合的业务模型有利于松耦合的架构设计和业务演进,同时松耦合的架构也有利于形成松耦合的团队结构。业务模型是松耦合设计的基础,上面的层级都是基于这个思想来定义的。一种常见的第4级情况是在伙伴关系的背景下。例如,订单服务和送货服务通过同步API进行通信。当用户下单成功后,通知送货服务,送货服务完成,更新订单状态。这两个服务通过API集成。服务之间需要了解彼此的部分领域知识,才能完成API调用,实现功能。同时,业务的可用性是相互关联的。如果一项服务不可用,整个业务就会中断。如果我们希望耦合度进化到level3,又不希望服务的可用性有直接依赖,通常会通过引入消息中间件来解耦,服务之间通过消息进行集成。由于某些原因,它们都按照彼此的领域模型定义的消息结构进行通信。在这种情况下,服务之间的领域知识相互耦合,业务可用性与具体服务解耦,与消息中间件的可用性耦合。我们需要关注的是如何提高消息中间件的可用性,保证服务的高可用性。级别2的耦合级别基于明确的域边界和上下文边界。在上面包含的订单服务和配送服务业务中,配送服务作为上游,会在配送完成,订单更新业务完成后,将更新后的服务进行下发。内容发送给订单服务,订单服务解析配送更新内容,更新关联的订单状态。那么当它们通过API集成时,就处于一种领域知识单向上下游,业务可用性互为上下游的状态。在具体构建服务时,可以根据团队的组织架构和话语权大小,采用不同的方式进行服务整合。上游服务通常使用开放主机服务(OHS)/发布语言(PL)提供业务能力,下游服务遵循上游领域模型或使用反破坏层(ACL)完成领域模型的改造。这种耦合级别的上游和下游服务可以在不更改开放主机接口的情况下独立迭代更新。否则,需要通知下游服务评估影响并同步进行更改。接下来,我们可以更进一步。我们通常通过引入消息中间件来解耦服务可用性依赖,从而达到Level1。在这一层的服务之间,由于有明确的上下文边界和依赖关系,消息的结构也由上游系统定义和维护。那么如何根据业务场景设计支持兼容的消息结构、集成规则、消息格式更新方式,是这一层需要关注的问题。四个耦合度中,从高到低,对团队业务建模和技术能力的要求越来越高,随着耦合度的降低,适应新业务的能力越来越强。通过耦合层次来进行架构的取舍那么基于以上耦合层次的区分,在设计架构的时候如何取舍呢?对于4级的系统,如果服务在团队职责范围内,在保证高可用和业务需求不频繁变化的前提下,可以暂时工作。如果系统由不同的团队维护,或者需求变化频繁,需要优化业务模型,通过定义清晰的上下游关系来增强架构的适应性,达到Level2。对于处于Level的系统3、由于领域知识的耦合,服务需要其他领域的知识来完成自身的业务能力。随着服务的增加,很容易退化为网络依赖。通常需要同时实施新的业务变更。修改多个服务,异步集成的方式也增加了扩展和维护的难度。对于这一层的系统,优先考虑的还是优化业务模型,明确上下游关系。至于是否采用异步集成,需要综合权衡业务的实时性和一致性要求。权衡到level2还是level1。对于level2的系统,由于系统的上下游关系比较明确,可以重点考虑采用合适的方法来完成上下游系统的集成。一般情况下,上游系统可以通过OHS/PL独立进行迭代更新,不需要改变发布的语言;下游系统通过跟随或增加防腐层来屏蔽上游业务模型变化的影响,取决于业务模型变化的频率和增加新层的成本。通常在绿地项目中,由于业务建模和开发团队可以从零开始,在统一业务语言、明确上下游团队合规关系的基础上,采用新的服务建设技术和实践,同步整合上下游团队。下游服务使用OHS/PL和ACL会更好的隔离相互影响。上游服务侧重于领域能力的迭代,通过OHS/PL发布功能。下游服务使用ACL隔离上游变更对自身领域模型的影响,也可以根据需要使用上游提供的新功能。对于level1的系统,在业务和技术上是松耦合的基础,但是这个时候我们需要警惕一个新的依赖。因为上游系统没有根据使用场景设计消息格式,或者在向前兼容的情况下不能更新消息格式,后果就是上游系统会成为下游新业务的强依赖,因为任何新的需求可能需要上游系统定义新的消息格式来支持,上游系统将成为响应变化的瓶颈。如果服务在不同的团队中维护,结果就是团队之间的冲突。在这个层次的依赖中,合理的消息模式和兼容性设计是迭代演进的关键。消息集成通常分为EventNotification和Event-carriedStateTransfer两种方式,可以扩展为以下几种模式:消息体包含领域事件发生后领域模型的最新状态和变化内容。域事件发生后,消息体包含域。模型最新状态消息体包含领域事件发生后领域模型的变化内容。消息体只包含领域事件发生后领域模型的标识。要求消费者按需通过API获取相关信息。最后,以上是针对分布式系统中的服务。关于解耦的一些思考,希望上下游的思考能给大家在做设计和系统开发时提供参考,帮助我们达到松耦合的目的,也有助于减少团队之间的依赖和摩擦。
