美文网首页
绞杀者模式 (一)

绞杀者模式 (一)

作者: anOnion | 来源:发表于2021-04-23 22:04 被阅读0次

    背景

    随着系统老化、开发工具逐渐落伍、bug 堆积,项目会变得及难维护。所以“腐烂”是所有遗产项目不可避免的一环。一般企业基本不会再去碰遗产项目,但是现代很多公司却喜欢另辟蹊径——每两三年用新技术重构一遍代码。众所周知,遗产代码很难改动,那它们这么做的自信来自何处?

    故事要从一个叫 Martin Fowler 的老头说起。有一次,他在热带雨林里旅游,无意间发现了一种叫“绞杀者藤蔓”的植物:

    绞杀者藤蔓

    绞杀者藤蔓会沿着其他树干一路向上生长,以获取光线资源;随着藤蔓的不断生长,之前的寄生树会被这种植物整个包裹,并随着阳光资源的枯竭而死亡;最后树木腐烂而逝,但是原地却留下了一颗树状的巨大藤蔓。

    绞杀过程

    Martin 老头回去后就把这个故事写在了博客上,并提出了一个叫“绞杀者模式”的策略:通过逐步重构单体应用(而不是推到重来的方式),逐渐构建出一个新的应用程序。这也成为了后来开发人员对遗产系统进行现代化改造的基本方针。

    绞杀者模式

    讲完植物,我们从实现策略上探讨一下“如何绞杀”;绞杀的大体操作如下:

    1. 创建一个门面拦截后端遗产系统的请求
    2. 通过一定的规则,门面会将特定请求分别路由到新旧系统上——反向代理
    3. 保留遗产系统的原有功能,同时在新系统中重写旧的模块,并逐渐地将请求倾斜到新系统中;迁移过程中,由于门面的隔绝,消费者照常使用现有功能,并不会有任何后台重构的感知
    4. 完成迁移后,所有请求将路由到新系统上,遗产系统被“绞死”
    绞杀者模式

    绞杀者的好处是:保留了遗产系统的代码,以平滑迁移的形式朝着新应用迈进;这确保了每一步前进都有回退的机会。渐进迁移的时间可能会很长,甚至可以永久性的保持部分遗产系统的功能。

    实操

    OK,理论很简单,实操中会有各种各样的问题,重构过程通常要持续数月甚至数年。仅仅宣称“通过绞杀者模式可以实现系统迁移”,这种话是没用的:一想到你要动所有代码,大家立马就慌了,决策者(不管懂不懂技术)都不会如此草率地行动。所以最好能有更细节的拆分步骤。所谓的“拆分”,可以从表现层、服务层、持久层等等方面入手;只要记住从最简单的部分开始,当显现出价值后,就可以持续加速改造了。

    通常来说,表现层的拆分是最简单的,比如将 JSP 或 tymeleaf 这种后端渲染框架拆成 restful api + react 的形式——动静分离——就可以很快出货。我们看看具体的改造过程。

    反向代理

    实现绞杀者模式的第一步自然是加个门面啦。我能想到最最最简单的门面就是:专职的反向代理工具 nginx 了。反向代理我之前写过一篇文章,大家可以点这里查看。务必确保团队成员已经有了最基础的重定向知识;第一步就遇到认知障碍,这事就非常令人沮丧了。

    言归正传,通常来说,第一步的反向代理无须对请求做任何处理,只要简单穿透:

    Reverse Proxy

    如果使用 nginx 的话,配置也很简单,把根路劲代理到遗产系统的服务上即可:

    # nginx.conf
    
    server {
      location / {
        proxy_pass https://legacy.com;
        ...
      }
    }
    

    迁移功能

    一旦 HTTP 反向代理就绪,我们就可以开始抽取功能代码了。当然,迁移方式上又有好多策略,比如分离表现层、重构数据库、提取领域服务等等;由于篇幅限制,本期只讲最简单的表现层分离。

    以比较原始的 JSP 应用为例,通常可以将 JSP 做“动静分离”的重构:

    • 动态部分:即原先绑定在 JSP 上的 model 数据。将它们以 Rest API 的形式暴露出去,JSP 以下的业务逻辑保持不动
    • 静态部分:即 JSP 的 HTML 模版(UI)部分。这部分以现代前端框架——如 reactJS——重新实现。重写的 UI 代码全部放到新的系统中,遗产系统中的 JSP 代码保持不动
    Split

    题外话,利用新技术栈实现功能模块后,对应的 CI/CD 也应在第一时间跟上;一系列 UI 测试也要在第一行代码起开始编写。不然几个月后,你会发现新系统并不会比老系统强健多少。

    重定向

    如果 CI 配置得当,react 代码的修改从 Merge PR 到完成新系统模块更新——现阶段事实上只有静态文件——应该可以控制在几分钟内完成。

    新系统怎么集成呢?我们需要把浏览器请求的特定资源重定向到新系统上:

    Redirect

    新系统需要一个新的代理路劲(如/modern/)以便与旧系统区分,代理配置上加个 location 即可:

    # nginx.conf
    
    location /modern/ {
      ...
      proxy_pass http://modern.com/;
    }
    

    当然,这时候新系统依旧不会起任何效果的。原因也很简单,重构初期,html 入口基本都在遗产系统里,除非代码里 hard code /modern/ 相关请求,否则 UI 不会与新系统产生关联——当然这种修改是我们不愿意看到的。

    怎么办呢?这里讲一个小技巧,利用 nginx 给所有的 html 注入一条指向 modern 的 js,配置大体如下所示:

    # nginx.conf
    
    location / {
      proxy_pass https://legacy.com;
      sub_filter '</body>' '<script type="module" src="/modern/app.react.js"></script></body>';
      sub_filter_once on;
    }
    

    只要给遗产系统的 html 注入 app.react.js,所有 UI 相关操作就可以在新系统代码内修改了;而遗产系统的 JSP 无需做任何改动。

    数据绑定

    上面提到了“动静分离”,那数据和模版分离后,两者怎么互相绑定呢?可能有些朋友会有疑惑,这里补充说明一下,react.js 通常利用异步请求 Rest API 的形式获取数据,并在它自己的模版上实现绑定。这是所谓的MVVM 模式,也是现代化前端框架优于 JSP 框架的重要原因。

    我们看看加了 react 重构后的页面请求顺序:

    1. 浏览器提请页面
    2. 由于 nginx 默认设置,请求都会路由到遗产系统上
    3. JSP 生成 html 后返回页面
    4. nginx 会给所有 html 返回注入脚本标签<script type="module" src="/modern/app.react.js"></script>
    5. 浏览器解析到上述标签后,再发起/modern/关联的 js 的请求
    6. 这个请求会被路由到新系统上
    7. 新系统返回 app.react.js
    8. 浏览器执行上述 js,发现有 Rest API 的请求,于是再发起数据请求
    9. 数据请求也是走默认配置路线,被 nginx 指向遗产系统
    10. 遗产系统返回 Rest API 的 JSON 数据
    动静分离

    最后,就是在浏览器上 react 框架自动实现数据绑定了。相较于 JSP 那种一股脑后端渲染并返回一个巨大的 HTML,“动静分离”的模式在过程中相对复杂一点,新入门的朋友可能要增加点认知成本了。但最终体现在代码上的话,其实更简单;这东西写过一两个页面就能感觉到了,我这里不深入讲解了。

    小结

    OK,UI 重构的基本框架大致搭建完工了,之后就是根据特定业务逐步地迁移各个前端模块。
    推荐在初始阶段可以将 react 当 jQuery 用——就是当 lib 用啦:通过 selector 找到遗产代码的特定 DOM,重构之;一个页面完工后,再修改 nginx.conf,用以劫持新页面到 modern 系统。如果进度顺利,在第一个页面完工后就可以体现出重构效果了——肉眼可见的加载速度。

    前端迁移完后,就是后端服务的拆分了,我会在《绞杀者模式(二)》里进一步讲解,敬请关注!

    碎言碎语

    我曾参与过一个单体架构的遗产项目,积年累月堆积了几万个 bug;然而,作为开发人员平均每人每周的 bugfix 量仅为 1(时常还能引入点新 bug)。我想不出任何方式能在这个项目彻底玩完前减少一些账面的 bug 数,所以就专程前往厂里的一位领导干部那里请示。他告诉我,“重新定义 bug,那些 bug 就没了!”

    听完后,豁然开朗:Bug 是绝对修不完的,所以不要再纠结每周修 1 个 bug 或是 2 个 bug 这种问题了。分一点时间出来——比如 20%的人力——迁移产品,在迁移的过程中逐步捋顺业务逻辑,当迁移完工后,遗产系统的 bug 就不再是 bug 了。

    相关文章

      网友评论

          本文标题:绞杀者模式 (一)

          本文链接:https://www.haomeiwen.com/subject/fmvhrltx.html