背景
思考这个问题的时候我翻出了Uncle Bob的clean architecture,我记得这本书里有专门的章节讲组件间依赖程度的度量和管理,希望能有所帮助。结果发现没什么用,这本书里讲的依赖,有点像库和调用库的客户端的依赖,这些不同的块之间可以调用或者引用。这与我想得到的答案不一样。本文这里的组件,指的是部署的独立单元,组件间通过通道进行通信,组件间没有显式的依赖关系,从物理视图方面你可以理解成每个组件都是一个进程。
说清楚问题的边界之后,我们考虑一个问题,怎样的组件设计会导致组件间的耦合?
按理来说,组件间交互只通过通道,组件间的耦合应该是轻微的,甚至是不值一提的。为了理解这个问题的严重程度,我画了一张图。假如只有两个组件:
这是一个典型组件交互视图,主功能流指的是架构最主要需要完成的数据流集合,也是系统最小功能集需要的接口集合,用宽箭头表示。除此之外,A和B之间还有多种顺向消息,多种逆向消息,顺向指的是与主功能流方向相同。
分析
一个消息代表了一个概念在两个组件间进行了维护和抛接,这本身就是一种很危险的信号。两个组件同时维护一个概念必然会带来概念状态不一致、组件间边界争议、故障定位困难、人力浪费严重等问题。从这个意义上,需求应该尽量在一个组件内完成,这应该是一个非常明确的组件划分规则。反过来说,看到一个接口,首先应该问,组件划分是否合理。接口通道的个数,反映了组件间耦合的程度。
有的时候消息抛接无法避免,至少目前已存在的消息80%以上都被认为是无法避免的。那么我们应努力避免同一个概念相关的消息存在多个,这种情况是上一段描述的耦合的进阶版。
有一种接口消息我称之为消息概念环。指的是同一个概念相关的字段,A发给B,B又发给A,这是一种极不稳定的组件设计方法。我们考虑一种情况:组件B因为某些异常情况导致某个消息未成功发送。在消息概念环的设计里,这种情况将导致组件A概念维护异常。状态维护异常对于状态机来说是灾难性的,它将导致组件A无法恢复正常,所以应禁止多组件维护状态机。对于按照现有架构无法避免的消息概念环,应在组件A设计状态回退机制。
有一种接口消息在接收端处理时存在先后关系的绑定。这种绑定关系容易在时序调整、负载平衡等架构变动中产生问题。这种情况如果存在,应该是避免不了的,似乎只能通过某种手段来强化这种先后顺序的管理,如定义一个数组OrderedMsgRcv[MAX_ORDERED_MSG],所有有序消息接收都通过这个数组进行调用。
前面我们提到了应该减少接口消息,那么对于导致概念在全领域蔓延的广播式消息,应该怎么办呢?广播是一种一方提供信息,多方消费信息的方式,其状态维护仍然是统一的。但是由于概念蔓延极容易随着需求的增加而产生环,广播的概念应该引起密切关注。
怎么做
低耦合的组件间设计规则总结如下:
- 需求应该尽量在一个组件内完成;
- 禁止多组间维护状态机;
- 如果无法避免消息概念环,应在状态维护组件设计状态回退机制;
低耦合的接口设计规则总结如下:
- 接口消息应尽量少;
- 同一个概念尽量不要出现在多个消息中;
- 解决消息概念环;
- 警惕广播消息;
最后,在讨论接口新增的时候,应该:work together,而不是fight each other。团队壁垒严重,不同团队只看到自己的一亩三分地,看不到整体的人力空耗。在这种情况下,组件边界定义往往是谁不讲理谁有利,这是不对的。接口定义的时候有必要work together,去看看别人的难点,理解对方强调的重点。