You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

257 lines
22 KiB
Markdown

2 years ago
# 20启动如何开启一个遗留系统现代化项目
你好,我是姚琪琳。
从今天开始,我们的课程将进入一个全新的环节。之前,我们学习了遗留系统四个现代化的诸多模式和理论,然而纸上得来终觉浅,现在的你一定摩拳擦掌跃跃欲试,准备在项目中大显身手了吧?
不过别急,你可以先跟着我,一起在一个虚拟的遗留系统中实践一番,将三大原则和各种模式一一落地,并结合实际情况做出调整,以适应项目和团队。
今天是这个实践系列的第一节课,如何启动一个遗留系统现代化项目。
## 项目背景
说来有点唏嘘,国内遗留系统的重灾区,恰恰是那些最早拥抱信息化的行业,比如电信、银行、保险、民航等。它们早年身先士卒,投资金、投人力,建设了信息化系统,没想到多年以后反而成为了限制业务发展的遗留系统。
这些遗留系统都在各方面都存在着许多共性:
* 代码量巨大且质量不高
* 前端普遍使用用ASP、JSP等服务端渲染技术在页面中内嵌了大量业务逻辑
* 数据库中存在大量存储过程和函数
* 单体“大泥球”架构
* 系统缺乏文档和知识,新人很难上手
* 几乎没有DevOps
我们这个虚拟案例是一个车险行业的业务系统它具备以上所有特点使用JSP技术数据库是Oracle存储过程的代码量占整体代码量的三分之一左右。那么在开始现代化之前你需要做哪些前期准备呢
## 业务梳理
作为架构师,你可能并不熟悉每一个业务模块的具体内容,但不了解业务是无法设计出合理架构的。因此,你需要先对整体业务进行梳理,划分出业务的边界,才能进一步设计组件和服务。你还需要沟通好业务方,请他们派出各个业务模块的领域专家跟你一起梳理。当然,你也同样需要业务分析师、质量分析师和资深开发人员。
有很多梳理业务的方法和工具,像用户旅程、用户故事地图等,它们可以帮助你理解业务流程、梳理业务架构。这其实也是一个降低外在认知负载、提升相关认知负载的过程。
梳理好的投保和理赔业务的用户旅程(这里是一个简化版的用户旅程,忽略了痛点和心情曲线,我们的目标是梳理业务流程,而不是寻找用户痛点)大致如下所示:
![图片](https://static001.geekbang.org/resource/image/51/88/5121cee08137263a89aa96fc5ba3e288.jpg?wh=1920x821)
![图片](https://static001.geekbang.org/resource/image/1f/61/1f92c3aca86ac3095cb0a73190aef861.jpg?wh=1920x821)
## 战略建模与架构设计
接下来,我们需要以用户旅程为蓝本,对整个系统进行战略建模,目的是设计出目标架构。
战略建模同样有很多工具可用,常见的有事件风暴、动名词法,以及刚刚兴起的领域故事会。在这个案例里,我们使用动名词法。
**动名词建模法**是指通过梳理业务需求、识别关键领域名词、识别命令动词,并将名词动词进行关联,从而形成统一语言、提取模型的建模过程。
其中,**领域名词**是指在业务操作中出现的名词,通常是业务操作的对象,比如“订单”,“商品”等。 而**命令动词**是指作用于领域名词的动作使用业务语言描述区别于CRUD比如“下单”“订购”等。
在实战中,我们将整个建模过程分为以下七个步骤。
![图片](https://static001.geekbang.org/resource/image/c7/f3/c7eff9cee380bea989305258764f50f3.jpg?wh=1920x679)
其中,前四个步骤属于业务梳理,后三个步骤属于架构设计。
### 步骤一:识别动名词
在这一步中,我们跟领域专家一起,进一步详细分析用户旅程中的每一个业务阶段。按照业务时序,讨论业务步骤,以达成一致的理解。梳理完毕后,将动词和名词按照业务相关性组织到一起。
![图片](https://static001.geekbang.org/resource/image/0a/9b/0ayya883fc0348ef99be627ca316d59b.jpg?wh=1895x746)
在这一过程中,我们可以和领域专家澄清很多缩略的业务术语。比如核保,拆分成命令动词和领域名词,应该是审核投保单,承保应该是承接投保单,缴费应该是缴纳保费,报案应该是申报案件申请,结案应该是结束案件……
澄清这些术语很重要。比如核保和承保,字面上理解是审核和承接保单,但实际业务中却是投保单。
通过和领域专家的沟通,你才会明白,保单才是保险公司和投保人之间的合同,而投保单只是一个投保申请。因此,保单和投保单很明显是两个领域名词,建立的模型也肯定是不一样的。再比如缴费这个术语,在投保上下文里代表的是缴纳保费,但在其他上下文里,可能是缴纳其他费用。
越早澄清这些容易引起歧义的名词,就越容易形成统一语言,避免误解。
### 步骤二:识别角色
**角色**是命令动作的发起者,比如“代理人”、“承保岗”、“查勘岗”,也可以是一个系统,比如微信小程序、支付宝等。
通过识别角色,我们可以进一步了解命令动作是如何参与到业务中的。另外,不同角色的需求和变化频率往往不同,这有助于我们设计边界更加合理的架构。
### 步骤三:寻找缺失概念
**缺失的概念**是指业务人员没有明确提到的概念,但是缺失后很可能影响业务的完整性和可追溯性。
缺失的可能是名词,比如已经识别出来了某个动词,但却没有找到与之对应的名词,你需要找到这个名词,并补充到模型中;缺失的也可能是动词,业务人员没有明确提到,但缺失了某一动词后,名词的生命周期就不完整,这样的动词也需要补充到模型中。
比如在步骤一提到的投保和缴费,就都是行为,没有对应的名词,找到投保单和保费的概念,就弥补了缺失的名词。再比如赔款这个名词,只有“支付”这一个动词与之对应,显然生命周期是不完整的,应该补充“生成赔款”这一动作。
### 步骤四:去除噪音
与前一步“寻找缺失的概念”相反,这一步是要去除或忽略无用的信息(噪音)。通常需求⽂档或者业务人员的描述会涉及到很多细节,但并不是所有的内容都和建模相关。在实施过程中需要有针对性地甄别,避免噪音对模型的干扰,降低后续设计过程的复杂度。
在建模时不需要关注的噪音通常包括:
* 无需记录的线下操作:有些行为并不会影响系统的数据或状态,因此不需要被系统记录。比如投保人提供投保单材料、上级人工核保、打印保单、清分单据等。
* 查询操作:和数据查询相关的操作,如数据展示、数据导出、数据过滤查询等。
* 字段说明:业务验证错误时的提示语、出错信息等。
经过这四个步骤,我们完成了业务梳理,部分的领域名词和命令动词如下所示:
![图片](https://static001.geekbang.org/resource/image/1d/48/1d99b7f961791e4c93b870f7ff681648.jpg?wh=1920x641)
### 步骤五:区分基础能力与运营能力
对于一个大型的复杂系统我们需要将它分解成更小、更简单的部分。借鉴Unix操作系统“分离策略与机制”的设计原则和DDD战略设计方法我们将业务能力分为基础能力和运营能力。
**基础能力**通常提供原子能力,它们不依赖于编排能力,且变化的频率很低;**运营能力**是在基础能力之上,企业想要健康运作而需要的能力,它们的变化频率很高。
在这一步中,我们要区分已经汇总的动名词对儿,并将它们按照基础能力和运营能力分成两层。
![图片](https://static001.geekbang.org/resource/image/e2/3e/e242bdb9c3638d711b04d0410267353e.jpg?wh=1920x775)
### 步骤六:识别核心基础能力
对于像保险业务系统这样的大型复杂系统其基础能力可能仍然过大需要进一步分解。按照Gartner的 [PACE Layered Application Strategy](https://www.gartner.com/en/information-technology/glossary/pace-layered-application-strategy),对于基础能力里稳定性(或变动频率)不同的部分,我们可以再次划分,识别出核心基础能力。
**核心基础能力**是指反映业务本质,实现业务价值所必须的最小能力集合。在识别核心基础能力的时候,你可以遵循这样三个原则:
* 稳定性原则:即找出反映业务本质的部分。业务本质通常是最稳定的,而与用户的交互通常是不稳定的。
* 最小化原则:即尽可能做减法,非必要不做加法。
* 完备性原则:即核心基础能力应该是完备的,能够独立实现业务价值。
以保险业务为例,不论其渠道端(柜台、互联网、移动端)怎么变化,不论实现技术怎么变化,其业务本质仍然围绕保单展开的,包括保单的费用和服务(案件、赔款)。在识别出业务本质后,我们的业务名词就可以分解为这样的三层。
![图片](https://static001.geekbang.org/resource/image/de/4b/dedbc2c3b34bb3d2fd1bc18d4b7fc14b.jpg?wh=1920x877)
### 步骤七:设计分层架构
当我们把业务名词分层之后,就可以着手设计目标架构了。在这里,我们把这些业务名词理解为一个业务模块或组件中的核心模型,并以这些名词作为模块或组件的名称。
对于企业的业务系统,我们可以粗略地设计为三个层次:接入层、运营作业层和核心能力层。
接入层一般为UI系统、API Gateway或BFF等比如手机App、微信小程序等。运营作业层是指围绕业务价值所展开的运营活动和作业管理。
我们在这里把前面的运营能力层和基础能力层合并,来作为运营作业层。核心能力层是实现业务价值所必须的最小能力集合,是企业运营的本质。比如保险行业就是保单和保费,而银行则是围绕账户所展开的金额和状态的管理。
除此之外,业务系统还需要对接一些企业已有的公共服务,比如产品中心、银行对接、客户管理、权限管理等,也需要与一些外部系统进行交互。
最终的目标架构如下图所示:
![图片](https://static001.geekbang.org/resource/image/4f/e2/4fa6d7e272ffa17b6cf5d59004feb2e2.jpg?wh=1920x840)
当然,这只是一个假想的保险业务系统,我们仅仅做了其中部分业务的粗略梳理,并不是什么行业标准。你要重点学习的是这种战略建模和架构设计的方法。
## 选择试点
有了战略模型和架构设计后,我们接下来要选择一个试点进行遗留系统现代化。
在做数字化转型时,我们常常会选择一个**精益切片Thin Slice**作为试点,而不是全盘地转型。遗留系统现代化同样可以借鉴这种方法,对架构进行垂直的切割,将业务的所有元素整合成一个价值交付部分,能够提供完整的价值。
除了业务价值,你还需要考虑试点的复杂度。你可以使用一些工具,全盘分析整个遗留系统,一方面可以得出一些质量指标,比如缺陷数、坏味道数等,评估代码质量。另一方面也能得出模块间的依赖关系,了解系统的复杂度。
对于代码质量推荐你使用流行的SonarQube。对于架构向你推荐我们公司的开源工具 [ArchGuard](https://archguard.org)。
什么样的切片才适合做试点呢?这需要你权衡业务价值和复杂度。业务价值高的,往往很复杂,不好做;而简单的,又可能没有价值。
所以要尽量选择既能提供一定业务价值,复杂度又不是很高的切片。因为复杂度太高的话,进展会很慢,容易让团队和各方干系人失去信心和耐心。
而相对简单的模块则更容易成功,可以给业务带来实惠,给团队增加信心,同时可以总结出来各种经验和套路,供下一个切片使用。
在综合评估了业务价值和复杂度这两个维度后,我们选择核保模块作为试点。下图中阴影覆盖的部分,就是一个切片。
![图片](https://static001.geekbang.org/resource/image/9d/0c/9d850bb4a7bca8d894e131f0b626e00c.jpg?wh=1920x858)
## 以假设驱动为指引选择现代化方向
选择完试点,你还需要选择要对试点所采取的遗留系统现代化方向。是重构代码,还是添加测试?是拆分模块,还是拆分服务?这些都是可行的方向,你需要选择的是最可能实现更大业务价值的那些。
这时候我们需要以假设驱动为指引,帮我们找到价值最大的方向。
比如从业务敏捷这个维度我们需要缩短需求交付周期、扩大需求吞吐量。以前30人天开发完成的需求能否缩短到20天以前一个迭代交付5个需求能否再增加1个如果业务方更关注业务响应力你可能需要将这个模块拆分成相对独立的服务。
另一方面如果平时这块业务Bug比较多需求方更希望改善它的质量那你可能需要对代码添加各种级别的测试参考[第八节课](https://time.geekbang.org/column/article/512658))。
指标的重要性和优先级确立以后,你需要与业务方达成一致,并选择可以支撑这些指标的工作。就本案例而言,业务方更关注业务响应力,因此我们选择将核保模块拆分为微服务。
## 确定目标架构
对于试点的目标架构你其实无需关注太多细节。我们虽然通过战略建模梳理出了整个系统的大致架构分层但这其实还是对于问题域Problem Space的分析。
具体解决方案域Solution Space这些模块是微服务还是几个模块合并成基于服务的架构其实都不需要在现阶段给出答案。
遗留系统是复杂的,架构应该是慢慢演进的。一个负责任的架构师,是不可能仅仅在几天的业务梳理和战略建模之后,就给出最终的确切架构的。
对于核保模块这个试点,我们的目标架构十分简单,就是把它拆分出来,形成一个微服务。
![](https://static001.geekbang.org/resource/image/85/a0/85715a7757924b077741385aafec3aa0.jpg?wh=1920x957)
## 制定架构演进计划
虽然目标架构的形态十分直观,但演进计划却并不简单。这是一个将遗留系统的微服务拆分这个复杂问题,拆解成多个简单问题的过程,最终让任何一个普通的开发人员,都能够胜任其中的一个简单任务。
在制定计划时,你需要考虑以下几个方面:
首先,代码如何拆分。在遗留系统中,各个业务模块的边界是否清晰?是否可以使用基于组件的分解?或者是一个结结实实的大泥球,只能使用战术分叉?对于这部分内容,你可以复习一下[第十二节课](https://time.geekbang.org/column/article/515274)的内容。本案例的模块边界十分模糊,所以我们最终决定采用战术分叉方式。
其次,数据如何拆分。是暂时不拆分,和遗留系统共享单体数据库?还是使用单体封装的数据库服务?或者变更数据所有权,并在应用中同步数据?关于这部分,你可以复习[第十五节课](https://time.geekbang.org/column/article/517959)的内容。本案例最终选择的是拆分数据。
第三如何增量迭代。按照增量演进原则我们不能采取长期改造、一次性上线的交付方式而应该是每个迭代都交付一部分增量。那么对于服务拆分应该如何迭代是按页面交付还是按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上。因为开发的规模变小了估算可能还相对容易一些。
你应该快速做出一个小的增量,证明这是可行的,给团队、领导和业务方以信心。然后根据做出的这个增量的工作量去估算其他增量。
你也可以让业务方来比较一下,希望开发给出一个一年的承诺,最后一次性上线全部承诺,还是不做出具体承诺,但是一年内每个月都能看到一个具体的进展,并且随时可以中止(因为是增量且可回退的)。如果业务方选择后者,那么他们就不会再纠结于一次性知道全部费用了。
## 小结
总结一下今天的内容。这是我们实战篇的第一节课,我们一起讨论了如何开启一个遗留系统现代化项目。
我们花了不少篇幅去梳理开启项目的步骤,你需要先对不熟悉的业务进行梳理,得到初步的用户旅程或用户故事地图;再通过动名词法等工具,对系统进行战略建模,并设计出目标架构;然后选择一个端到端的业务进行试点,并以假设驱动的方式寻找合适的现代化方案;接下来是确定目标架构以及制定演进计划,并按照计划逐个迭代地增量演进;最后,每个迭代都需要得到充分验证,我将在第二十五节课详细介绍这一部分内容。
我们的目标很明确,就是要把核保业务从单体大泥球架构中拆分出来,形成具有独立数据库的微服务。这已经是个非常艰巨的任务了。因此你一定要记住,一次只做一件事。
有时候你可能既想拆分微服务,又要进行代码重构,或者既要拆分数据,又想重新设计不合理的数据库,这些都是我不推荐的。你可以把它们排进计划,等一件事彻底做完再做另一件,而不要企图并行完成。因为认知负载太高了,什么都想做,最终什么也做不成。
[下节课](https://time.geekbang.org/column/article/522486),我们即将开启具体的现代化工作,敬请期待。
## 思考题
感谢你学完了今天的内容,今天的思考题是,如果按照假设驱动的原则,我们最终选择的不是微服务拆分而是代码重构,你应该制定什么样的演进计划?如何增量迭代?
期待你的分享,如果你觉得这节课对你有帮助,别忘了分享给你的同事和朋友,我们一起开始一个遗留系统现代化项目吧。