浅议.NET遗留应用改造
TLDR:本文介绍了遗留应用改造中的一些常见问题,并对改造所能开展的目标、原则、策略进行了概述。
一、背景概述
1、概述
或许仅“遗留应用”这个标题就比较吸睛,因为我听过太多人吐槽了。Robert Martin在《修改代码的艺术》这本书中做的一个比喻:
“遗留应用使人联想到在黑暗、乱糟糟的灌木丛中艰难跋涉,脚下是吸血的蚂蝗,身边飞的是蛰人的昆虫,空气中满是黑暗、黏糊糊、沉重、腐烂垃圾一样的气味”。
虽然似乎有点夸张,但也说明遗留应用对开发者来说有多痛苦了。
根据维基百科的定义,遗留系统是一种旧的方法、旧的技术、旧的计算机系统或应用程序,“属于或与以前的、过时的计算机系统有关” ,但仍在使用中。通常,将系统称为“遗留系统”意味着它可能已经过时或需要更换。
如同人脑会随着时间的推移不断的老化一样,软件系统也会由于人员流失、开发者逐渐年长、摩尔定律等因素衰老。随着市场不断发展的软件系统,在越来越庞杂的需求面前,原本可能设计优雅的代码也将变得越来越复杂,由于缺乏动态维护的文档和测试用例,使其修改越发不易,变成“老态龙钟”、“焦油坑”“屎山”般的“遗留系统”。
当然,“遗留应用”并非都是历史演进的产物,时间不是影响其衰老的唯一因素。
有一些应用,也许用的模式或技术本身还是比较先进,但代码中过多的运用远超过当前开发人员水平的技术,导致维护成本比较高,也可能同样会被扣以“遗留应用”的帽子。
还有一些用古老技术,如基于 COBOL 开发的上古应用,虽然经过改造后也能在新的云原生平台上运行,”强行刷一波技能点“,但毋庸置疑,也会被认为是遗留应用。
若是无脑的将某些应用判定为“遗留应用”,其实是一种主观判断,或是由于信息偏差所造成的刻板印象。
当今时代开发者们面临的信息偏差问题随着信息传播问题越发严重,在我们所能接触到的资讯领域内,往往受限于“信息茧房”“回音壁”的影响,举目看到的也多是某些新技术,两相比较之下,我们再来看身边那些采用老技术打造的某些产品时,更容易感到焦虑,于是既会逐渐对“遗留应用”失去信心,甚至可能会对行业前景充满悲观心态。
二、绿地应用和棕地应用
我们或许可以试着忘掉“遗留应用”这个容易主观评判的名词,改用更加客观的概念来理解我们所参与的某些产品或项目,例如,“绿地应用”和“棕地应用”,这种“无副作用”的行业术语将引导我们以更加积极的心态来面对一切。
正在上传…重新上传取消
1、绿地项目开发
绿地开发是在软件工程等其他学科中,绿地也是一个缺乏先前工作所施加的任何约束的项目。类似于在绿地上施工,无需改造或拆除现有结构。“一张白纸好作画”,开发者能够在这个过程中,从无到有的实践一些新技术或解决方案,又无需过度担忧实施了哪些东西会对其他组织、组件、技术或甚至代码风格形成依赖,放开手脚的释放自己的能力,在充分享受创造的过程中实现个人的某些追求。
绿地项目虽好,但往往可能缺乏明确的方向,有的甚至连基础成熟的商业模式都没有,在摸着石子过河的情况下,往往需要具备较强的“开拓能力”才能做好一个新项目。
许多开发者事实上都不具备从一开始就把事情做对的能力,可能会需要走更多的弯路。如果不能明确目标,理顺需求,选择适宜的开发模式,有可能会建立起一座脆弱的空中楼阁,一旦面临需求发生某种变化就可能导致整体崩塌。
2、棕地项目开发
棕地开发,用于描述在现有(遗留)软件应用程序/系统存在的情况下,需要开发和部署新软件系统的问题空间。这意味着任何新的软件体系结构都必须考虑到现场已有的线上系统并与之共存。棕地项目由于功能模块比较成熟,往往其已经能够稳定运行,对其的任何更改都需要针对性制定策略。
相较于绿地项目, 棕地项目具备许多优势[1]。开发者无需盲目寻求出路,只需基于一个预定的方向进行扩充。开发过程中将更多的依赖对现有技术方案的改良来完成任务,支持使用定义的业务流程和技术解决方案,依赖大量的重用现有代码以添加新功能。
当然,开发者对系统的任何修改都应建立在对现有系统、服务和数据有透彻的了解的基础上,可能需要重新设计现有复杂环境的很大一部分,以便它们对新的业务需求具有操作意义。需要详细准确地了解现有业务和IT的约束条件,这样新项目才不会失败。处理遗留代码不仅会减慢开发过程,还会增加整体开发成本。
“棕地”和“绿地”这样从城市规划行业借用的名词或许会使我们想到一个城市不同的区域,如老城区和新城区。
长沙的河东就是老城区,可以理解为“棕地”,以前的河东,道路逼仄,路面斑驳,车流量又比较大,稍不留心就发生比较严重的拥堵,有的小区也绿化面积不多,街边的房屋上也浸透了历史感。有时一些小区仅有的公共区域,也经常看到许多老人聚集一起打牌、下棋或跳广场舞 , 年轻人相对较少。河东虽然夜生活繁荣,但酒吧也大多是基于老建筑物改造而来,迎合了许多人另类的需求。
而河西作为“绿地”的新城区,公园或湿地众多,建筑物新颖时尚,装饰尽显现代风格,绿化率高,空气质量优良,生机勃勃,道路宽敞,尤其是靠近IT产业聚集地附近,街上到处都是满怀梦想的年轻人(加班族)。
当然,长沙河东和长沙河西不见得从综合指数上有多大区别,老城区商业区居多,人流量大,且最近几年基础设施建设过程中大修地铁或管廊,经常稍不留心就拥堵不堪,但若是基建竣工了,想必状态会大有好转。而河西事实上早年工业比较发达,留下过不少铁锈地带,由于经过了更好的规划,并在十年的造城运动中被不断改造,逐渐改造成现在这种宜人的生活环境。
正所谓“三十年河东,三十年河西”,绿地项目并不总是绿地项目,棕地项目却一定是棕地项目。不管是何种类型的项目,其存在的意义均只有一个,能够持续创造价值。即便是绿地项目,若不能上线运行,就连一个玩具都不如。而一个棕地项目,哪怕它的功能代码再烂,若是能响应业务的变化,也是优秀的产品,同样其若无法匹配时代的发展,我们也该果断的采取措施。
“与天斗其乐无穷,与地斗,其乐无穷,与棕地应用斗,其乐亦无穷”。
三、如何改造“棕地应用”
1、概述
坦率而言,目前的IT市场,除了创业公司或发展较快的公司外,成熟 IT 企业从零开始实施一个绿地应用的机会不多,当代开发者大部分的时间,其实都需要基于棕地项目进行改造维护。
尤其是对于.NET应用开发者来说,我们参与的许多成熟项目,可能都是基于.NET Framework的传统应用,使用的也是相对比较陈旧的特性。也有许多应用,虽然看似功能完备,但实际上只是具备了基础功能的非完全版本。
有时开发人员面对这些项目做出某些技术决策时,可能会遇到一些困难。简单的代码改造或小范围的重构不足以成为更好的选择,小范围代码改造或重构只能头痛医头,脚痛医脚,不能解决长期存在的问题。重写更非万试万灵的良药,若是想当然进行重写,为了实现某些新的特性采取了某些激进的改造措施,看起来不会存在很大的工作量,实际上实则可能陷入更大的泥坑。笔者有一位朋友他们家公司就曾经尝试过用微服务架构重写原来一款成熟销售的产品,在投入了二十多人花费了将近一年的时间后虽然把产品基本上做出来了,但做出来的新东西连原来老产品的许多基本特性都没达到,最终前功尽弃。
当今时代每一款成熟产品,其背后的业务代码逻辑可能远远不止我们表面上看起来的那么简单,有许多在当今的我们看起来毫无逻辑、有悖常理的代码实现,也是蕴含着前人很多智慧的结晶,虽然有可能许多实现在后来者来看有点像是投机取巧,但由于缺乏当时的背景、环境、资源因素,很难从事后对这些需求进行复盘。
因此改造固然很有必要,但如何才能在恰当时机,采取最合适的方法,完成看似这些棘手的任务?这就需要我们在每次改造前认真规划目标,定义原则,并建立配套的改造策略。
2、目标
应用改造,其作用是在不影响主体业务的情况下,完成架构或技术路线的升级改造,通过对陈旧的应用进行刷新,使其能够以最小的代价完成改造,并使业务具备不断的演进能力。
为此,我们应确定明确的目标。参考《聊聊遗留系统改造的“道”与“术”》这篇文章,总结了一些可供参考的目标。
1、既要确保业务的稳定,又要保证需求的快速响应。
改造只是过程,不是目的。架构改造需采取的措施,应立足于当前,着眼于未来。对于稳定项目的更改,需确保现有业务的稳定,并能满足新需求的快速响应。
2、通过系统改造,实现运营效率和交付效率的提升。根据企业核心需求出发,通过一系列改造,解决运营或交付过程中的痛点,提升效率和提升产品的核心竞争力。
3、发掘核心需求,“重新开发”“持续演进”。
遗留应用虽然稳定,但时代变化太快,应对需求的能力可能越来越弱,通过改造,开发者们可不断的发掘关键特性,可能要采取“重新开发”的手段,对某些遗留功能模块进行大刀阔斧的改善,实现关键特性的“持续演进”。
4、实现架构升级,带来资源或成本的降低。
必要的架构升级,可促使资源或成本的降低。
3、原则
同样是《聊聊遗留系统改造的“道”与“术”》这篇文章,也制定了一些原则,其中“演进能力”“适应度函数”“适当耦合”都是来源《演进式架构》这本书。(值得一提的是,这本书也着重强调了演进式架构千万要避免陷入“面向简历开发”“避免镀金”的误区,或可以为广大焦虑读者提供参考。)
1、将演进能力作为一种架构特征。
应用架构有许多种度量标准,比如安全性、可靠性、应用性等等,每个不同的阶段,企业对于架构或许有不同的追求,如追求灵活性,配置性,安全性,可靠性等,有时追求了灵活性,就降低了可用性,追求了安全性,就降低了便利性。演进式架构支持跨多个维度的引导性增量变更,作为一种度量指标,其有别于能直接看到利益的功能性指标,如配置性,便利性,维护性,其更关注于未来的长期演进。企业架构有时总容易陷入要全,要是要快的艰难抉择之重,而若把演进能力当作一种架构特征,搭建演进能力出更强的架构,也将更好的适应未来形势的发展。
2、关注“适应度函数”[2],并用其牵引架构的演进。
适应度函数借鉴自进化计算,其含义是“架构的适应度函数为某些架构特征提供了客观的完整性评估”,常被用来衡量方案对满足目标的适合度,有助于自动确定系统在多大程度上符合特定的架构目标和限制条件。如软件开发过程中,可能关注功能的快速实现,而忽略了运营或运维的便利性,若引入适应度函数,则可根据当前运营或运维的实际能力,设计一些人机交互操作+自动化处理的操作相结合的手段,来提升系统相关能力。日志也是如此,过量的日志设计或过少的日志设计对系统来说都是负担,通过建立适应度函数,评估动机,可确保日志具有良好的结构化和埋点,并包含足够的有用信息。
3、“小步快跑,增量变更”。
对于遗留系统来说,步子迈太大,容易摔跤,应用改造过程中,不能为了贪图快速完成而冒进,如果能以增量变更的方式来做改造,每个增量都能够回滚,这种改造基本上风险是可控的,团队若建立敏捷式管理手段,完善迭代式开发的规则,确定每个迭代版本需交付的小目标,则可以更好的达到这个目标。
4、策略
1、组建敏捷项目管理团队
敏捷旨在打破组织的现有沟通体系,建立起人与人之间互相协作的氛围。团队共同拥有代码,并共同为项目成败负责。敏捷团队采用迭代式开发方法,通过规划版本、小步快跑的形式逐步推进产品的改造过程。敏捷项目团队采用循序渐进的方法进行软件开发,把一个大项目分为多个相互联系、但也可独立运行的用户故事,在此过程中软件一直处于可使用状态。敏捷团队强化面对面的沟通,依托站立会议,评审会,反思会,卡片估算等机制确保团队内部的良好沟通,并通过可视化看板的形式对任务的即时状态进行跟进。
虽然敏捷项目管理常用于新项目的开发过程,但也同样可用于遗留应用改造。在遗留应用改造过程[3]中,建立起各类角色,组织头脑风暴或研讨会等形式,对该系统存在的问题进行深入讨论,并积极思考解决策略,然后通过阅读文档或在可用环境中演示来得到更具象的认识,最终通过阅读代码[4]来解除剩下的疑问,完成对系统上下文需求的获取,并将现存问题作为backlog列入迭代计划,引入持续集成等策略, 提升产品交付过程中的效率及质量。
碍于篇幅,此处省略一些大家普遍熟知的知识。
2、GitFlow版本流程
而在改造过程中,将敏捷项目管理流程与git flow[5]相结合则更能体现效率。
例如,可将既有应用的某些改造过程划分为未来需完成的若干个版本开发分支,在完成一个版本的改造后,就将其merge到主分支进行测试,预发布和上线,则能更好地实现“小步快跑”。
git-flow 模式会预设master和develop两个长期分支,前者为产品代码,后者则为新开发的基础分支。
开发过程中,开发人员不断地基于develop分支创建新的分支作为特性分支,并对其进行某些特性改造,改造完成后,将代码通过pull request的形式合并到develop分支中,并删除该特性分支。
在对develop分支进行完整测试后,对该分支进行发布构建,并将代码提交到master分支,使其成为包含最新代码的稳定分支。如此这般,通过一个又一个的特性分支,实现了应用的改造过程。
介绍一个使用git的小技巧:在代码改造过程中,一般都会涉及到对源码的文件迁移。随着git等源码管理工具等广泛使用,开发者可能更习惯于使用git来管理提交信息,往往忽略了注释,若直接移动文件或文件夹可能会造成源码提交记录的丢失,而能灵活的运用git mv
工具,则可以避免此类问题。由于git本身是基于文件来进行版本追踪的,使用了该工具可以实现对文件进行源码跟踪,方便后续通过文件来查看相关需求提交记录。
git mv [-v] [-f] [-n] [-k] #将 重命名为 ,它必须存在,并且可以是文件,符号链接或目录。
3、封装。
封装是面向对象编程的特性,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。
对于企业应用而言,封装则意味着将某些具有关联性的功能模块或代码进行封装,这就可能牵涉到需要将某些具备关联性的业务逻辑或对象,按照某种形式整合,使其能够按照合适的形式来响应业务变化。通过封装数据和功能可实现对现有业务代码的利用,也可通过暴露API的形式将它们作为服务对外提供。
而基于依赖注入框架提供的特性,在进行业务封装时,可按照业务的层次,将代码按文件的形式拆分成高层或低层模块,再从现有代码中,抽出基础设施层、领域层、业务层代码,再按照依赖倒置原则,通过注入的方式,实现高层模块对底层模块的引入。
领域驱动设计也是一种典型的业务分析手法,由于业务逻辑的复杂性,或可通过从现有说明文档中提取统一语言的形式,实现对遗留应用的逆向工程处理,将其拆分成独立的限界上下文来实现对现有业务的封装,并在后期迭代中不断的强化某些特定的限界上下文,使之能够独立演进为粒度更具体的模块或服务。
碍于篇幅,此处省略一些大家普遍熟知的知识。
4、迁移。
迁移是指针对某个特定基础组件的迁移改造,常见的有数据库的迁移、基础设施的迁移等。
可根据需要建立对应的迁移流程,大体可分为如下几个步骤[6]:
1、设定迁移的目标;2、制定迁移的计划;3、迁移计划的验证(PoC);4、实施应用程序的迁移;5、校验迁移结果。
当然,迁移或许是最充满风险的一部分。以数据库等迁移为例,虽然近些年来ORM已经得到了广泛使用,基于EF等Linq技术,实现数据库等迁移还是比较简单的。但若是由于遗留应用很少使用这些新特性,可能还需要从存储过程中将部分业务迁移过来,实现对存储过程代码的业务映射,将其用ORM技术进行实现,此工作量有时则想都不敢想。若是基于某种考虑,必须将海量历史数据进行迁移,此时若按照上述步骤建立周详的方案,则可有效地避免由于仓促动手造成的被动性。
在数据迁移过程中,数据模型映射、脚本转换、数据准确性校验是比较重要的三大问题。
数据模型映射:由于新老系统可能不仅仅只是数据库引擎不同,连数据库的字段也有可能会进行小范围的修改,则需要开发做好模型映射规则,此过程需要既熟悉新系统,又熟悉老系统,并且还要熟悉数据库设计。建议安排多人协同参与,新老搭配,共同制定计划、确定范围。
脚本转换:不同的数据库会使用不同的数据库脚本如存储过程来完成某些批量操作,则需要安排专业人员对脚本进行转换。
数据准确性校验:数据库迁移过程中若发生了数据丢失,可能会引起比较重要的生产事故, 有时还需通过开发专用的工具来转换,并从老系统中提取样本数据来反复演练、测试验证,确保正式转换时的高可用性。有时演练阶段还需刻意寻找一些脏数据来进行验证,并对该数据的成因和解决方法进行反复讨论调整,避免由于实际生产环境下存在某些幽灵数据污染了全局目标数据的情况。
有条件的情况下,或可参考如何做好大型遗留系统的数据迁移[7]来建立一个更复杂常见下数据迁移的思路。
5、特性升级。
特性升级是指针对某些功能性或非功能性指标的升级,该升级可能包括多种类型:
1、如包括某个基础包的升级,如将数据库访问ORM组件从低版本升级到高版本的升级,可能不会升级较大范围的变更,做好版本测试很重要。
2、某些基础特性的升级,如认证机制从传统的表单认证升级到oauth2的认证机制或jwt的认证机制,有可能牵涉面比较广,则可能需要运用到灰度发布策略。灰度发布会通过基于老版本构建一个新的特性分支,在其上实现特性升级后,同时部署新版本和老版本,并让其中一部分用户使用老版本,而另一部分则使用新特性,并根据应用范围逐渐扩大,最终实现所有用户的迁移。灰度发布开始到结束期间的这一段时间,称为灰度期。
3、环境的升级。如从宿主机迁移到k8s环境,需要的更多的是运维能力建设。
6、代码重构。
重构就是通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。
在开发过程中,定期对某些维护成本较高的代码进行重构,可促使其能更好的维护,进而延长代码的生存周期。重构并非无限制的重构,而应该建立在合理的测试边界设计的原则上。有条件的情况下,为代码添加足够的单元测试代码,构建代码的安全网,可有效地确保代码改造过程与原来的预期匹配。若是无法依托单元测试,端到端测试或接口测试也是非常必要的保障措施。
业界也有许多介绍重构的书,其中尤其以《重构改善既有代码的设计》这本书最为著名,在这本书里面介绍的众多手法都比较适合开发者练习。由于知识密度比较大,许多开发者表示不太理解如何实际操作。译者熊节老师曾经分享过一种策略:先概览一下全书,对基本的模式有一个初步的认识后,再找一些与书中模式类似的代码,按照介绍的模式进行一些实际操作,实现对重构的手法有了初步的认识,并通过不断的练习,形成肌肉记忆,以后如果遇到类似的代码,也就能手到擒来了。
在《修改代码的艺术》中也介绍了许多手法,如在每次修改前,梳理相应的修改点,进行特征测试,形成影响图、特性图等方式,或是使用mock、建立接缝、面向差异编程等常见的测试驱动开发的手段。重构过程中,依赖注入也是一种非常重要的手法,用依赖注入取代创建可以解耦对象和它们所使用的服务,这会让软件更容易测试和扩展。如果不注入真实的依赖而是注入模拟依赖,就很容易测试代码。依赖注入也会使代码更容易维护,它有助于将业务决策集中化,简化对象使用方式。通常[8],为了理解一个新系统,首先看的就是对象实例化的地方。我们查看工厂对象,依赖注入框架,或者其他进行对象实例化的地方。这会告诉我们很多关于系统的信息,让我们知道如何改进它。
若是没有足够的精力按照书中的方法来学习重构,倒是可以通过《代码整洁之道》中的原则来建立起较为初级的手法。
参考一些通用做法,或可以采用如此操作:
1、代码扫描工具辅助。
运用sonarqube工具进行代码扫描,检查代码中的异味,并按照操作说明对代码进行适度的重构。
2、拆大为小。
在visualstudio中,无论是项目、还是解决方案文件夹或代码文件夹,其命名都应该遵循一定的原则,即为解决某些特定的问题,或者是“单一职责”,若是在上述文件夹中存在了多个职责,则意味着其具备重构的目的,此时我们可以在建立充足的业务知识的前提下,对解决方案文件夹、项目、代码文件夹进行清理,使其满足“单一职责”的目的。
3、改善大类和大函数。
在《代码整洁之道》中,作者认为函数应该足够“短小”,如20行左右为最佳,当然类的代码行也不能太多,尽量控制在一个范围,如一个类包含10个左右的方法,约为200行左右,这就是比较合适的类。若是代码行超出这个值,则说明函数或类可能存在优化的成本。尽量使每个类、每个函数“只做好一件事”。将方法按照职责进行组合,并逐步将方法迁移到独立的partial类,并最终实现用依赖注入的方式将类独立。
4、改善命名。
命名是软件开发最为重要的一件事情,代码写得好不好,首先取决于命名是否符合业务需求。参考《代码整洁之道》
“优秀的程序员或设计师,其工作之一就是分离解决方案领域和问题领域的概念。与涉及问题领域更为贴近的代码,应当采用源自问题领域的命名” 开发者有时候会经常会纠结于代码如何命名,事实上可能是未能建立起业务的概念。所以,当我们无法给某些函数、类、变量命更好的名时,回到对应的业务领域,深挖业务的价值。
4、改善代码的结构。
常见的代码结构问题包括缩进、复杂度过高,过多的使用三元表达式、递归、过多的For 循环。缩进问题可通过VisualStudio快捷键来进行改善,尽量按照VisualStudio提供的默认缩进,反而更适合阅读。复杂度过高表现在判断语句过多的情况,有时开发者会写几重判断语句,或将严重影响类代码的可读性。三元表达式虽然在复杂判断语句的情况下颇有益处,但过于复杂的三元表达式,远不如卫语句更适合程序员阅读。递归虽妙,但往往除了作者当时还能理解,是否几乎都不能理解。For循环可以使用Linq替换,将极大地改善可读性。
6、重新开发。
重新开发旨在将逆向工程、重构和正向工程组合起来,将现存系统重新构造为新的形式,以开发出质量更高、维护性更好的软件。
近几年来,非IT行业的“重新开发”成为了一个新的潮流,如某气森林就在跨国公司的层层壁垒、拦腰堵截之下,“重新开发”了果味汽水等多款爆款产品,从饮料快消市场撕开了一道口子,创造出了不错的业绩。在该品牌的带动下,快消行业也掀起了一波“重新开发”的热潮,许多以前被视作铁板一块的市场,如酒水、零食、奶茶等行业,一度都被大厂垄断多年,却也被国人创造的许多新兴品牌打开了新的商机。
相较而言,IT行业的“重新开发”或许早已是司空见惯的事情,由于IT技术发展飞速,人们经常要面对各种不同的形式的“重新开发”,简单的有某些接口的重新开发、模块的重新开发,复杂的有子系统的重新开发,甚至整体业务的重新开发。有时不管某些业务系统是运行的好还是坏,总会有一些由头将其废弃,并“重新开发”为另外一套与之相差无几的新系统。此种情况在政务信息化领域尤其常见,早年我参加过的某些政务系统,十年后再跟业主交流,他们基本上都说已经都更换了,少的换了一两波,多的可能一年一换。从这个意义上来说,“重新开发”也许是一种促进共同富裕最好的方式。
当然,相较于从零开始开发的“筚路蓝缕”,有了之前老系统的成功的先例,站在其巨人的肩膀上重新开发一套,显然更容易实现。那么换一个角度来理解,“重新开发”实则是一种快速试错的手段,通过不断的打造阶段性可用的版本跑通模式,再通过后续迭代不断的完善自我,在必要的时候采取主动的措施,对不能更好迭代的系统进行“重建”,这是当下应对复杂市场环境最为明智的选择。
当然,“重新开发”不是“绿地应用开发”,其更应该抓住一些核心要素[9],以最小的成本实现目标的达成。
1、识别遗留应用资产。
从现有遗留应用中总结形成组织过程资产,这些资产包括说明文档、包括遗留系统的描述和流程图以及灾难恢复计划;公司内部数据中心所在的设施;与遗留系统有关的利益相关者参与情况:这包括当前用户(包括高管)、开发人员、系统管理员和业务分析人员;遗留系统运行在上面的IT基础设施;以及开发人员的技术技能。也包括可用于指导未来业务开发的模式、知识和其他文档,总结遗留应用的问题或风险,使之形成新应用开发的机遇等。
2、发现可复用价值点。
对源码或数据库建模及其他研发交付过程资料进行梳理,识别具有复用价值的组件,分析和用图表描述组件之间的依赖关系及数据流转模型。
3、组件改造。
运用一些手段对组件进行改造,可以采用诸如修建者,绞杀者,挎斗[10]等方式进行改造。修建者、绞杀者、挎斗常用于微服务架构改造过程[11],不过事实上也并非只能在微服务架构改造[12]过程中才能使用,传统的应用架构重新开发过程中都可以运用。
在修建者模式中,开发者需将待修缮部分进行隔离,并用新的方式加以实现,修建过程中,需确保除被修改内容外,其他部分功能仍然正常使用。得益于.NET Core依赖注入框架的便利性,开发者可通过对遗留应用功能的识别,梳理出有价值的ServiceProvider,并对其进行修建,这部分改造工作也是前面所提到的“封装”模式的运用。这也是本文第三次提到依赖注入。
绞杀者模式则是基于现有应用的外围进行新模块的开发,用新的方式将不断地开发新的组件。由于在开发过程中,暂不涉及核心功能的修改,实质上是对原业务的重写,逐步实现对老系统的替换。随着时间的推移,模块改造过程也会逐渐地从外围深入到内部,最终实现对老系统的整体替换。
挎斗模式更类似于适配器的模式,通过接入功能代码集中在一起,作为一个独立的进程或服务,为不同语言的遗留系统提供一个同构的接入接口。在部署结构上,挎斗服务与原遗留系统紧密相关,原遗留系统在哪里它就在哪里。对于原遗留系统应用程序的每个实例,
4、评审。
改造过程中定期组织相关方进行评审,确保价值的有效传承。
四、总结
遗留应用改造相较于新项目开发,虽然可能会接触各种屎山,但也有望站在成熟业务的巨人肩膀构建自己的业务体系,也同样能够实现技能的长期积累,值得开发人员围绕其投入更多精力。
当今时代,技术发展日新月异,我们总能接触到许多新的知识体系或方法,但盲目的沉迷于新技术的“船货崇拜”或执着的坚守某些老技术,都不可取。
我们在新技术的同时,也应该关注产品的本身价值,通过各种形式的改造,丰富其产品特性,使之能够适应当前和未来形势的变化,不断焕发出新的生机。
References
[1]
绿地项目, 棕地项目具备许多优势
[2]
关注“适应度函数”
[3]
遗留应用改造过程: https://insights.thoughtworks.cn/legacy-system/
[4]
阅读代码: https://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052
[5]
git flow: https://www.git-tower.com/learn/git/ebook/cn/command-line/advanced-topics/git-flow
[6]
如下几个步骤: https://new.qq.com/omn/20200630/20200630A0SRPE00.html?pc
[7]
如何做好大型遗留系统的数据迁移: https://cloud.tencent.com/developer/article/1877779
[8]
通常: https://blog.51cto.com/u_14977574/2546965
[9]
核心要素: http://www.d1net.com/cloud/tech/336477.html
[10]
挎斗: http://m.blog.itpub.net/31562044/viewspace-2650826/
[11]
微服务架构改造过程: https://servicecomb.apache.org/docs/how-to-reform-a-legacy-system/
[12]
微服务架构改造