如何开启一个遗留系统现代化项目?
Hi,我是阿昌
,今天学习记录的是关于如何开启一个遗留系统现代化项目?
的内容。那如何启动一个遗留系统现代化项目。
一、项目背景
说来有点唏嘘,国内遗留系统的重灾区,恰恰是那些最早拥抱信息化的行业,比如电信、银行、保险、民航等。
它们早年身先士卒,投资金、投人力,建设了信息化系统,没想到多年以后反而成为了限制业务发展的遗留系统。
这些遗留系统都在各方面都存在着许多共性:
- 代码量巨大且质量不高
- 前端普遍使用用 ASP、JSP 等服务端渲染技术,在页面中内嵌了大量业务逻辑
- 数据库中存在大量存储过程和函数
- 单体“大泥球”架构
- 系统缺乏文档和知识,新人很难上手
- 几乎没有 DevOps
虚拟案例是一个车险行业的业务系统,它具备以上所有特点,使用 JSP 技术,数据库是 Oracle,存储过程的代码量占整体代码量的三分之一左右。
那么在开始现代化之前,需要做哪些前期准备呢?
二、业务梳理
作为架构师,可能并不熟悉每一个业务模块的具体内容,但不了解业务是无法设计出合理架构的。
因此,需要先对整体业务进行梳理
,划分出业务的边界
,才能进一步设计组件和服务。还需要沟通好业务方
,请他们派出各个业务模块的领域专家跟你一起梳理。也同样需要业务分析师、质量分析师和资深开发人员。
有很多梳理业务的方法和工具,像用户旅程、用户故事地图等,它们可以帮助理解业务流程、梳理业务架构。这其实也是一个降低外在认知负载、提升相关认知负载的过程。
梳理好的投保和理赔业务的用户旅程(这里是一个简化版的用户旅程,忽略了痛点和心情曲线,目标是梳理业务流程,而不是寻找用户痛点)大致如下所示:
三、战略建模与架构设计
接下来,需要以用户旅程为蓝本
,对整个系统进行战略建模,目的是设计出目标架构。
战略建模同样有很多工具可用,常见的有事件风暴、动名词法,以及刚刚兴起的领域故事会。在这个案例里,使用动名词法。
动名词建模法 是指通过梳理业务需求、识别关键领域名词、识别命令动词,并将名词动词进行关联,从而形成统一语言、提取模型的建模过程。
其中,领域名词是指在业务操作中出现的名词,通常是业务操作的对象,比如“订单”,“商品”等。 而命令动词是指作用于领域名词的动作,使用业务语言描述(区别于 CRUD),比如“下单”,“订购”等。
在实战中,将整个建模过程分为以下七个步骤。
其中,前四个步骤属于业务梳理,后三个步骤属于架构设计。
步骤一:识别动名词
在这一步中,跟领域专家一起,进一步详细分析用户旅程中的每一个业务阶段。
按照业务时序,讨论业务步骤,以达成一致的理解。
梳理完毕后,将动词和名词按照业务相关性组织到一起。
在这一过程中,可以和领域专家澄清很多缩略的业务术语。
比如核保,拆分成命令动词和领域名词,应该是审核投保单,承保应该是承接投保单,缴费应该是缴纳保费,报案应该是申报案件申请,结案应该是结束案件……澄清这些术语很重要。
比如核保和承保,字面上理解是审核和承接保单,但实际业务中却是投保单。
通过和领域专家的沟通,才会明白,保单才是保险公司和投保人之间的合同,而投保单只是一个投保申请。
因此,保单和投保单很明显是两个领域名词,建立的模型也肯定是不一样的。
再比如缴费这个术语,在投保上下文里代表的是缴纳保费,但在其他上下文里,可能是缴纳其他费用。
越早澄清这些容易引起歧义的名词,就越容易形成统一语言,避免误解。
步骤二:识别角色
角色 是命令动作的发起者,比如“代理人”、“承保岗”、“查勘岗”,也可以是一个系统,比如微信小程序、支付宝等。
通过识别角色,可以进一步了解命令动作是如何参与到业务中的。
另外,不同角色的需求和变化频率往往不同,这有助于设计边界更加合理的架构。
步骤三:寻找缺失概念
缺失的概念是 指业务人员没有明确提到的概念,但是缺失后很可能影响业务的完整性和可追溯性。
缺失的可能是名词,比如已经识别出来了某个动词,但却没有找到与之对应的名词,需要找到这个名词,并补充到模型中;
缺失的也可能是动词,业务人员没有明确提到,但缺失了某一动词后,名词的生命周期就不完整,这样的动词也需要补充到模型中。
比如在步骤一提到的投保和缴费,就都是行为,没有对应的名词,找到投保单和保费的概念,就弥补了缺失的名词。
再比如赔款这个名词,只有“支付”这一个动词与之对应,显然生命周期是不完整的,应该补充“生成赔款”这一动作。
步骤四:去除噪音
与前一步“寻找缺失的概念”相反,这一步是要去除或忽略无用的信息(噪音)。
通常需求⽂档或者业务人员的描述会涉及到很多细节,但并不是所有的内容都和建模相关。在实施过程中需要有针对性地甄别,避免噪音对模型的干扰,降低后续设计过程的复杂度。在建模时不需要关注的噪音通常包括:
- 无需记录的线下操作:有些行为并不会影响系统的数据或状态,因此不需要被系统记录。比如投保人提供投保单材料、上级人工核保、打印保单、清分单据等。
- 查询操作:和数据查询相关的操作,如数据展示、数据导出、数据过滤查询等。
- 字段说明:业务验证错误时的提示语、出错信息等。
经过这四个步骤,完成了业务梳理,部分的领域名词和命令动词如下所示:
步骤五:区分基础能力与运营能力
对于一个大型的复杂系统,需要将它分解
成更小、更简单的部分。
借鉴 Unix 操作系统“分离策略与机制”的设计原则和 DDD 战略设计方法,将业务能力分为基础能力和运营能力。
基础能力通常提供原子能力
,它们不依赖于编排能力,且变化的频率很低;
运营能力是在基础能力之上,企业想要健康运作而需要的能力,它们的变化频率很高。
在这一步中,要区分已经汇总的动名词对儿,并将它们按照基础能力和运营能力分成两层。
步骤六:识别核心基础能力
对于像保险业务系统这样的大型复杂系统,其基础能力可能仍然过大,需要进一步分解。
按照 Gartner 的 PACE Layered Application Strategy,对于基础能力里稳定性(或变动频率)不同的部分,可以再次划分,识别出核心基础能力。
核心基础能力是指反映业务本质,实现业务价值所必须的最小能力集合。
在识别核心基础能力的时候,可以遵循这样三个原则:
- 稳定性原则:即找出反映业务本质的部分。业务本质通常是最稳定的,而与用户的交互通常是不稳定的。
- 最小化原则:即尽可能做减法,非必要不做加法。
- 完备性原则:即核心基础能力应该是完备的,能够独立实现业务价值。
以保险业务为例,不论其渠道端(柜台、互联网、移动端)怎么变化,不论实现技术怎么变化,其业务本质仍然围绕保单展开的,包括保单的费用和服务(案件、赔款)。
在识别出业务本质后,业务名词就可以分解为这样的三层。
步骤七:设计分层架构
当把业务名词分层之后,就可以着手设计目标架构了。
在这里,把这些业务名词理解为一个业务模块或组件中的核心模型,并以这些名词作为模块或组件的名称。
对于企业的业务系统,可以粗略地设计为三个层次:接入层、运营作业层和核心能力层。
- 接入层 一般为 UI 系统、API Gateway 或 BFF 等,比如手机 App、微信小程序等。
- 运营作业层是指围绕业务价值所展开的运营活动和作业管理。在这里把前面的运营能力层和基础能力层合并,来作为运营作业层。
- 核心能力层是实现业务价值所必须的最小能力集合,是企业运营的本质。比如保险行业就是保单和保费,而银行则是围绕账户所展开的金额和状态的管理。
- 除此之外,业务系统还需要对接一些企业已有的公共服务,比如产品中心、银行对接、客户管理、权限管理等,也需要与一些外部系统进行交互。最终的目标架构如下图所示:
当然,这只是一个假想的保险业务系统,仅仅做了其中部分业务的粗略梳理,并不是什么行业标准。
四、选择试点
有了战略模型和架构设计后,接下来要选择一个试点进行遗留系统现代化。
在做数字化转型时,常常会选择一个**精益切片(Thin Slice)**作为试点,而不是全盘地转型。
遗留系统现代化同样可以借鉴这种方法,对架构进行垂直的切割,将业务的所有元素整合成一个价值交付部分,能够提供完整的价值。
除了业务价值,还需要考虑试点的复杂度
。可以使用一些工具,全盘分析整个遗留系统,一方面可以得出一些质量指标,比如缺陷数、坏味道数等,评估代码质量。
另一方面也能得出模块间的依赖关系,了解系统的复杂度。对于代码质量,推荐你使用流行的 SonarQube。
对于架构,推荐公司的开源工具 ArchGuard。
什么样的切片才适合做试点呢?这需要权衡业务价值和复杂度。
业务价值高的,往往很复杂,不好做;而简单的,又可能没有价值。
所以要尽量选择既能提供一定业务价值
,复杂度又不是很高的切片
。
因为复杂度太高的话,进展会很慢,容易让团队和各方干系人失去信心和耐心。
相对简单的模块则更容易成功,可以给业务带来实惠,给团队增加信心,同时可以总结出来各种经验和套路,供下一个切片使用。
在综合评估了业务价值和复杂度这两个维度后,选择核保模块作为试点
。
下图中阴影覆盖的部分,就是一个切片。
五、以假设驱动为指引选择现代化方向
选择完试点,你还需要选择要对试点所采取的遗留系统现代化方向。
是重构代码,还是添加测试?是拆分模块,还是拆分服务?这些都是可行的方向,需要选择的是最可能实现更大业务价值的那些。
这时候需要以假设驱动为指引
,找到价值最大的方向。比如从业务敏捷这个维度,需要缩短需求交付周期、扩大需求吞吐量。
以前 30 人天开发完成的需求,能否缩短到 20 天?以前一个迭代交付 5 个需求,能否再增加 1 个?如果业务方更关注业务响应力,可能需要将这个模块拆分成相对独立的服务
。
另一方面,如果平时这块业务 Bug 比较多,需求方更希望改善它的质量,那可能需要对代码添加各种级别的测试。
指标的重要性和优先级确立以后,需要与业务方达成一致,并选择可以支撑这些指标的工作。
就本案例而言,业务方更关注业务响应力,因此选择将核保模块拆分为微服务
。
六、确定目标架构
对于试点的目标架构,其实无需关注太多细节。虽然通过战略建模,梳理出了整个系统的大致架构分层,但这其实还是对于问题域(Problem Space)的分析。
具体解决方案域(Solution Space),这些模块是微服务,还是几个模块合并成基于服务的架构,其实都不需要在现阶段给出答案。
遗留系统是复杂的,架构应该是慢慢演进的。一个负责任的架构师,是不可能仅仅在几天的业务梳理和战略建模之后,就给出最终的确切架构的。
对于核保模块这个试点,目标架构十分简单,就是把它拆分
出来,形成一个微服务
。
七、制定架构演进计划
虽然目标架构的形态十分直观,但演进计划却并不简单。
这是一个将遗留系统的微服务拆分这个复杂问题,拆解成多个简单问题的过程,最终让任何一个普通的开发人员,都能够胜任其中的一个简单任务。
在制定计划时,需要考虑以下几个方面:
-
首先,代码如何拆分。在遗留系统中,各个业务模块的边界是否清晰?是否可以使用基于组件的分解?或者是一个结结实实的大泥球,只能使用战术分叉?最终决定采用
战术分叉方式
。 -
其次,数据如何拆分。是暂时不拆分,和遗留系统共享单体数据库?还是使用单体封装的数据库服务?或者变更数据所有权,并在应用中同步数据?关于这部分,你可以复习第十五节课的内容。本案例最终选择的是拆分数据。
-
第三,如何增量迭代。按照增量演进原则,不能采取长期改造、一次性上线的交付方式,而应该是每个迭代都交付一部分增量。那么对于服务拆分,应该如何迭代?是按页面交付?还是按 API 交付?在这个案例里,考虑到页面过于复杂,包含很多按钮,而每个按钮都对应一个后端 API,选择按 API 交付,这样粒度更小。
-
第四,组建拆分小组。你需要和项目负责人沟通好资源,给你一个 5~9 人的开发团队。如果遗留系统的团队结构是业务组件团队,则最好就是从维护核保这个组件的团队中抽调一些人。如果是特性团队,则最好是经常做核保特性的团队。最糟的情况是技术组件团队,你需要选择一些对核保业务比较熟悉的开发人员。另外,一定要配备一到两名专职测试拆分结果的测试人员,不要给他们分配其他测试工作。我们在第十八节课中介绍过,这样会让测试人员在不同上下文之间频繁切换,增加他们的认知负载。
-
第五,干系人如何管理。需要与业务方、项目负责人、业务分析师、其他架构师、核心开发人员、测试人员建立紧密的联系,获得他们的支持和帮助。做好这几个方面,你就可以开始按迭代增量演进了。
八、按迭代增量演进
迭代 0 是增量演进中最重要的迭代,有很多事情要在这个迭代完成。
- 第一,创建全新的核保代码库。按照战术分叉的方法,将遗留单体的代码全量复制到核保代码库中,并将与核保无关的代码删除。
- 第二,创建全新的核保数据库。对于数据,也可以使用战术分叉方法,将全量数据复制过来再删除掉无关的表,或者利用 Oracle 的特性,使用 DB Link,先远程访问单体数据库中的内容。具体方法我们后面的课程再展开。
- 第三,搭建核保服务的持续集成流水线。如果遗留系统已经有了持续集成流水线,可以复用。如果没有,可以只搭建一个最简单的流水线,只包含构建和打包功能。
- 第四,将持续集成流水线打出来的包部署到各个环境中。部署完之后,可以通过 API 工具测试部署是否成功。
- 第五,建立开关机制。将流量引流到新的服务中,以验证真实场景下的连通性。
- 第六,计划迭代 1 要改造的 API。从迭代 1 开始,增量演进就将进入一个稳定的交付阶段,你需要在每个迭代结束前,计划好下个迭代要完成的工作。
- 第七,制定开发工序。要将复杂的任务简单化,你需要制定一个普通开发人员可以遵循的开发清单。
比如用活文档工具进行业务梳理、添加 API 的开关并验证、在单体中的老 API 上添加 @Deprecated 注解、Java 代码怎么拆分、存储过程怎么拆分、JSP 中的代码如何处理、如何测试、如何寻求帮助等等。开发工序应该尽可能详细,以降低普通开发人员的认知负载。
由于迭代 0 的工作量巨大,可以相对延长迭代 0 的时间。比如一个迭代是两周,可以把迭代 0 延长到一个月,以确保这些内容顺利完成和交付。
从迭代 1 开始,就可以按增量来演进了。
九、关于估算
在改造时,一个绕不过去的话题就是需要多长时间。对于正常的功能开发,工作量的估算可能还比较准确,但是对于遗留系统现代化,想准确估算出具体的时间节点是难上加难。
因为遗留系统现代化是一个复杂问题,会有很多不确定的东西时不时冒出来,比如人员变动、临时添加的紧急需求、代码和架构本身未知的复杂度等等。不过,这和业务方的诉求是矛盾的,业务方想知道到底需要多少预算,来评估风险、衡量 ROI。
然而,如果迫于业务方的压力而随意拍脑袋给一个人天(比如 10 人天就是 1 个人工作 10 天,或者 2 个人工作 5 天),反而是不专业的。
这时最好的答案就是坦诚地说:我还不知道。
正因为如此,才更应该选取一个小的精益切片,并且将功能开关锁定在一个小的 API 上。因为开发的规模变小了,估算可能还相对容易一些。你应该快速做出一个小的增量,证明这是可行的,给团队、领导和业务方以信心。
然后根据做出的这个增量的工作量去估算其他增量。也可以让业务方来比较一下,希望开发给出一个一年的承诺,最后一次性上线全部承诺,还是不做出具体承诺,但是一年内每个月都能看到一个具体的进展,并且随时可以中止(因为是增量且可回退的)。
如果业务方选择后者,那么他们就不会再纠结于一次性知道全部费用了。
十、总结
讨论了如何开启一个遗留系统现代化项目。
花了不少篇幅去梳理开启项目的步骤,需要先对不熟悉的业务进行梳理,得到初步的用户旅程或用户故事地图;
再通过动名词法等工具,对系统进行战略建模,并设计出目标架构;
然后选择一个端到端的业务进行试点,并以假设驱动的方式寻找合适的现代化方案;
确定目标架构以及制定演进计划,并按照计划逐个迭代地增量演进;
最后,每个迭代都需要得到充分验证。
目标很明确,就是要把核保业务从单体大泥球架构中拆分出来,形成具有独立数据库的微服务
。这已经是个非常艰巨的任务了。
因此一定要记住,一次只做一件事。有时候你可能既想拆分微服务,又要进行代码重构,或者既要拆分数据,又想重新设计不合理的数据库,这些都是我不推荐的。
可以把它们排进计划,等一件事彻底做完再做另一件,而不要企图并行完成
。
因为认知负载太高了,什么都想做,最终什么也做不成。