1.背景
有了前两篇的帐号权限提取和功能设置提取的架构演进后,有一个问题就紧接着诞生了,对于诸多业务方来说,关键数据源的迁移如何在各个产品落地?
要知道这些数据都很关键:
- 对于帐号,获取不到帐号信息是无法让用户约会和入会的;
- 对于权限,用户有没有某个功能,如海外加速,直播、虚拟背景,完全依赖于权限数据;
- 对于功能设置,参会人入会是否要静音,是否能开启视频则取决于用户的功能设置项;
这么关键的数据,又做了如此大的调整,从业务方角度不得不考虑两个问题:
- 如何降低各个业务服务的接入难度?
- 即使各个服务都费力的将代码接入,一旦上线后发现问题如何快速补救?
这是一个全局性的问题,需要统一考虑,否则之前看似合理的架构优化却可能因为对接时的实际困难而落不了地。
也就是这些非功能层面的问题,却有可能让一个架构优化中途夭折,我们需要从软件工程层面解决这些问题。
2. 思路
从业务对接的角度来看,我们要解决两个典型问题:
- 如何减少对接的工作量;
- 如何降低对接后的上线风险;
这两个问题,我们通过以下4步来解决。
2.1 代码调用归一化处理
当系统中各个业务层都直接访问DB时,我们是很难作数据源调整的,所以我们首先要作的是:数据访问的归一化处理
。简单讲:就是把业务层对DB的访问收扰到一个公共库中,包括两个层面:
- 服务内部,所有业务接口访问帐号设置类数据都通过公共库来访问;
- 系统层面,所有服务访问帐号设置类数据都通过公共库来访问;
如下图所示:
在这个收拢的过程中我们主要做几件事:
- 统一封装数据的访问接口,各个业务都走相同的接口来访问数据;
- 统一数据格式,例如:所有业务访问帐号都返回同一套数据结构体;
- 数据源没有改变,但各个服务由直接依赖DB改为依赖公共库;
2.2 接入新数据源
作了代码调用的归一化处理后,我们接入新数据源基本只需要改动公共库。具体工作为:
- 抽象一套数据访问的接口,让新数据源和老数据源都实现同一套接口;
- 从新数据源获取到数据后,可能需要做下数据格式的转换,让新、老数据源返回的数据格式统一;
抽象接口定义类图如下所示:
这套接口,屏蔽了老数据源和新数据源的获取差异,这样业务服务接入新数据源的工作量能降到最小。
2.3 快速回退支持
通过前面两步,公共库已经提供了新、老两套数据访问接口,业务方可能只需要一行代码改动做个选择即可。这里我们想讨论的一个问题是:当接入新数据源上线后,发现问题如何处理?
- 第一种选择:把版本回退,回退到老数据源的版本,这会带来其它修改(如客户故障修复)也被牵连回退;
- 第二种选择:我们做一个配置开关,通过修改配置,快速回退到老版本,其它修改都不受影响;
不难发现,第二种方式明显更稳妥,配置简单示意如下:
# AccountStore的实现版本选择
AccountStoreMode = v2
可能还需要在AccountStore的初始化代码中作一点改动来支持版本选择,比较简单,这里就不示例了。
2.4 降级处理
回退主要是解决已经出故障后如何快速恢复的问题,也就是不论我们恢复多快,至少已经有一部分用户受到了影响,那是否有什么办法来避免故障发生呢?
有,我们可以做数据获取的降级处理,做法也比较简单:当从新数据源获取不到时,可以尝试从老数据源再获取一次。
有同学可能有疑问:降级会不会带来数据的正确性问题?
确实有一定的概率,用户的帐号权限已经在新数据源发生变更,降级访问老数据获取到的还是旧数据,如果用户正好要使用依赖此权限项的功能,则有一定概率与用户预期不符。
但是问题在于:当面临帐号都获取不到的产品可用性问题时,个别功能是否符合预期这类局部问题,还有那么重要吗?而且还是一定概率。
这时候我们就要做权衡,评估哪个问题带来的影响更小,两害取其轻。
软件开发中往往很难有完美的方案,有时候因为技术复杂度,有时候因为成本,这时候做权衡就很重要,选一个
我们能够掌控
、团队有能力实施
、结果有一点折扣但也能接受
的方案,往往更实用。
小结
本文以前面两篇文章账号权限提取和功能设置提取为背景,介绍了当外部依赖发生变化时,如何及时调整业务服务的结构设计。通过引入外部依赖防腐层,来减化架构调整带来的对接工作量,并降低对接新数据源的上线风险。
在软件设计中,防腐层往往被描述为一个适配器,用以将外部依赖和内部业务逻辑解耦。还是之前那个观点,名称叫什么不重要,重在能用它解决项目中的实际问题。
防腐层的适用范围远不止于此,其实从长远角度考虑,产品系统中很多模块都应该设计外部依赖防腐层,像数据库、外部服务接口、消息队列、缓存等。这些看似稳定不变的基础设施,却会在某天因为一个市场风向或客户需求,而被公司要求果断的替换掉,例如:信创带来的技术国产化浪潮……
参考阅读
- 从帐号权限提取的视角来看架构演进
- 从功能设置提取的视角来看架构演进