变更孤岛
1 定义
变更孤岛,是一种创建可演化的架构的模式。其主旨是将大型系统分割成多个独立可替换的部分,这些可替换的部分在之后的架构演化中逐步替代以适合的架构形态,针对可替代性而不是可重用性设计。
2 上下文和问题
在构建系统的时候,不一定从一开始就能设计出合适的架构。此时需要先构建一个初始的架构,并使其能随对系统理解的加深而逐步进行演化。
3 解决方案
对于上述可演化的架构,存在一种构建方法,使用此方法构建的架构具有适应性,能够在特定的时间点转换为合设的架构。这种构建方法需要遵循以下的原则:
将大型的系统分割成“变更孤岛”
针对可替代性进行设计,而不是可重用性
最小化共享依赖,重点关注自治和冗余,而不是重用
先将系统按变更作用的范围进行大粒度的划分,划分出的各个部分的变更仅影响系统内部,而不会影响彼此。此时划分的部分称为“变更孤岛”。之后的架构演进,围绕替换这些变更孤岛,而不是重用或者重构这些变更孤岛来进行。即设计出提供某个孤岛相同能力的子系统时,对此孤岛进行替换。
而对于子系统,也可采用这种方式进一步进行变更孤岛的划分、替换,直到系统的架构全部清晰。
由于划分的孤岛彼此耦合很小,功能内聚,可到达最小化共享依赖的目的。因此对孤岛进行冗余、替代都非常容易。这样可方便的对某个孤岛的架构和实现进行实验,一旦失败也可以方便的回退到原架构。
比如,在服务的划分中,经常会划分成前端服务、业务逻辑服务和数据服务,它们之间的调用关系如下:
前端服务、业务逻辑服务、数据服务都是功能内聚的部分,可作为变更孤岛,即他们内部的变更在内部消化,不影响到使用方。但如果直接按调用关系组织这些服务,是无法达到变更孤岛的架构目标的,因为前端服务使用了业务逻辑服务,会受业务逻辑服务变更的影响;而业务逻辑服务使用了数据服务,又会受数据服务变更的影响。这种架构是无法实现不同“孤岛”间的松耦合的。
从长期来看,业务逻辑的变化比数据存储技术和前端技术的变化慢得多,依赖应向着更稳定的方向,因此前端服务和数据服务依赖业务逻辑服务是合理的。而数据服务、前端服务应该是能随需求的变化或技术的迭代,快速变更的。这样,我们通过依赖反转方法,将业务逻辑服务对数据服务的依赖关系反向。如下图:
按这种方式进行调整之后,业务逻辑服务只依赖抽象的数据接口,而不再依赖具体的数据服务了。而数据服务只要实现数据接口,无论内部怎样变更,替换成哪种实现技术,都可以支撑业务逻辑服务,并且这种变化不会被业务逻辑服务感知。
而业务逻辑服务只要对前端服务提供的接口不变,也可以有效的对前端服务屏蔽变化。这样,前端服务、业务逻辑服务、数据服务就能达成变更孤岛的架构目标了。
下面扩展一下业务逻辑服务和数据服务之间的关系。在实际的系统设计中,经常出现多个业务逻辑服务都使用数据服务的情形,直接的调用关系如下图:
这种架构中,数据服务被业务服务A和B重用(共享),但却使业务服务A、业务服务B和数据服务产生了强耦合:不但让业务服务A、业务服务B都感知了数据服务的变化,而且从业务服务A出发产生的数据服务变更也会被业务服务B感知,业务服务B不得不因为业务服务A的变更而变更。
从架构层面来看,对服务的重用越多,服务间的耦合就会越重。重用并非服务间良好的组织方式。如果从变更孤岛的思路来思考,业务服务A和业务服务B看数据服务,都应该是可替换的。可以采用依赖反转将重用使用关系改变。如下图:
在这种关系下,业务服务A和业务服务B都不再依赖数据服务,而是依赖彼此定义的抽象数据接口。数据服务的变更可封闭在数据服务内部。而且,可以为业务服务A和业务服务B使用不同的数据服务实现,增强了系统的扩展性。这就是针对替代性而不是可重用性进行设计的意义所在。如下图:
4 优点
架构中的部件功能内聚,彼此松耦合,可逐步演进;
系统各个部分可方便的替换、回退,利于实验性特性的实现,比如A/B测试;
复杂系统架构演进时,可划分成多个变更孤岛各自进行架构演进。由于孤岛之间互不干扰,因此系统演进可从容地分阶段、分层次地开展。
由于孤岛可替代,而不是被依赖,这种架构符合依赖倒置原则(DIP),有利于将具体技术决策延后,增强架构对变化的适应性。
5 问题和注意事项
一开始的划分不宜粒度太细。孤岛的划分时,优先从现有功能可替代角度思考。避免一开始就被架构是否优美、部件如何共享如何重用等无关功能解耦的问题束缚住手脚。
6 应用场景
对大型系统进行微服务划分,但初期无法完全理解系统,不能细粒度的划分微服务时;
将现有大型系统迁移至微服务架构,存在某些因素无法判断微服务拆分是否合理时,可采用这种可演化的架构方式;