一年内从单体应用到微服务

2025-05-25

一年内从单体应用到微服务

一年前,我正无忧无虑地工作在一个遗留的单体应用上——一个用 CodeIgniter 2 编写的代码库。它虽然不是世界上最好的框架,但至少表面上看,它能用。多年来,我们一直致力于在尽可能短的时间内为客户实现功能,最终却留下了一堆 hack 代码。话虽如此,我想你明白我第一句话只是个玩笑。

在对我们的业务战略进行了一些改变并经过了大量说服之后,IT 部门获得了批准,可以在无期限内完全重写我们的申请人跟踪系统 (ATS) ——这只是我们整体的一部分,但肯定是很重要的一部分。

问题

  1. 添加新功能后,它会进入手动测试阶段。一旦获得批准,你就会将其部署到生产环境中——但经常发生的情况是,你错过了一个测试场景,最终导致生产环境中出现大量 bug。
  2. 这个问题与上一个问题紧密相关:如果你有一个老旧的代码库,它的基础会慢慢崩塌,所以在它上面添加新代码最终会依赖于其他代码。在这样的代码库中,有时你最终会破坏那些与你当前正在编写的代码完全无关的代码。
  3. 当有 11 个人日复一日地在同一个代码库上工作,而没有任何安全措施时,必然会出现问题。

已知解决方案

  1. 自动化测试——即使你没有覆盖所有可能的情况,除了手动测试之外,自动化测试也能很好地解决“生产环境中的 Bug”问题。自动化测试最大的好处之一是,它允许你自由地升级依赖项。即使你只覆盖了部分依赖项,当你执行“composer update”并运行测试时,你也能立即看到依赖项的更改是否会破坏你的代码。
  2. 将庞大的代码库拆分成多个小的代码库(即微服务)。借助微服务,您可以从两个方面解决“老旧代码”问题:一方面,它们允许您将代码保留在原处,无需干扰其他任何功能;另一方面,如果您确实需要重写,微服务允许您一次重写代码库的一小部分,从而使重写过程更加轻松。例如,在我们的旧版应用中,我们使用的TCPDF版本很老,这也是我们无法将其迁移到最新版本 PHP 的原因之一。借助微服务,我们可以将 TCPDF 放入与其所需 PHP 版本匹配的容器中,这样一来,其余代码库就不再需要担心了。
  3. 我们发现,解决这个问题的最佳方案之一是在各个团队之间拆分代码库,并在各个团队内部定期进行代码审查。这样,整个团队都能了解他们正在处理的代码,并且我们可以通过合并请求来避免任何潜在的问题。

我们是如何做到的

这是我们第一次在没有任何具体截止日期的情况下完成这样的项目(你以为我之前这么说是在开玩笑吗?);即便如此,我们也无法重写所有与 ATS 稍有关联的部分,即使我们想这么做——所以,我们决定只重写与 ATS 通信的部分(当然,除了 ATS 本身)。我们开发的第一个微服务是管理招聘广告的微服务——我们称之为招聘服务器。这是我的第一个 Symfony 项目;考虑到这一点,以及我们后来遇到的一些其他问题,我想你应该能理解为什么我不得不在这个项目的过程中重写该微服务三次。因此,我们得出我能给出的第一条建议之一:不要害怕重写你刚刚重写过的东西。即使你有经验,也不可能第一次就能把所有事情都做好。

SDK 满载

如果你看一下“ATS”和“作业服务器”之间的箭头,它现在的绘制方式看起来就像它们在直接通信。我们知道 ATS 不会是唯一与作业服务器通信的服务。我们很早就发现了SDK的需求。除了服务本身带有 SDK 之外,没有更好的与服务通信的方式了。然而,我们处理这个问题的方式却值得商榷……

附注:我们所说的“SDK”是指轻量级、可复用的库,它们能够轻松地与特定服务进行通信。我们发现这些库特别有用,因为我们(几乎)所有的后端代码都是用一种语言(PHP)编写的,因此拥有 SDK 的服务可以轻松集成到任何其他服务中。

有一天,我们发现了一个名为DoctrineRestDriver的包。我们立刻就觉得“天哪,这太棒了!”我们可以做一个 SDK,给你一个 EntityManager,让你像直接与数据库交互一样使用 Doctrine,尽管它实际上是在与作业服务器交互!

我们非常惊讶,因此立即着手将这个包实现到 Job SDK 中。

在项目后期,我们发现它并不能覆盖我们所有的用例,而且使用它的代码维护起来也很困难。DoctrineRestDriver 本身也没有维护,所以我们不得不 fork 它,花费大量时间深入研究并修改库的核心,使其能够适用于我们的用例。当时,我们已经陷入困境,无法回头,所以只能继续使用 SDK 进行开发。关于这部分,我的建议是:构建 SDK 时不要寻找廉价的替代方案。我们后来开发的所有其他 SDK 都采用了不同的方法——简单的描述性方法,用于发出简单的 HTTP 请求;本质上只是 HTTP 客户端的薄包装。虽然代码更多,但从长远来看更容易维护。

测试

到目前为止,我们一直在努力将代码库拆分成更小的块,而且做得还不错;但完成后谁来测试呢?当项目看起来像上图那样时,我们没有任何测试。(好吧,我们确实做了一些测试,但我并不以此为荣。在对测试进行任何真正的研究之前,我开始为作业服务器编写测试。结果,当我试图编写我以为是单元测试的代码时,我意外地编写了集成测试。当我意识到自己犯了什么错误时,我决定观看一些关于如何正确编写单元测试的教程。SymfonyCasts的“PHPUnit:浅谈测试”课程特别帮助我更好地理解了测试。)

经过大量的努力,最终我们在所有微服务上完成了大约 200 个测试。综合考虑各种因素,这个数字仍然不多,但我们希望随着时间的推移,能够逐步增加这个数字。

今天

经过一年时间,我们编写了约 5 万行代码,提交了约 2,600 次代码提交,一个由 3 名开发人员组成的团队(包括一名高级全栈开发人员和两名中级后端开发人员)最终打造出了 18 项服务。如今,我们 ATS 的运行系统大致如下:

想象一下,在第一次投入生产之前,你脑海中浮现出这样的画面。再加上我们对这项技术都缺乏经验,我想你就能理解我对将这一切部署到生产环境中的前景有多么焦虑。最终,除了一些小问题之外,部署过程的顺利程度令我们感到惊讶。如今,在这样的系统中工作真是太棒了,我愿意用任何东西来换取它。

在过去的一年里,我们学到了很多,这篇文章只是我们从这个项目中获得的一小部分知识。说实话,上图中的每个微服务都值得单独写一篇文章来介绍,我和我的团队非常渴望分享所有这些知识——所以,这篇文章只是众多文章中的第一篇。

好了,各位,就到这里吧。我迫不及待想听听你们的想法!

关于我

大家好,我是 Kristijan!旅行爱好者,全职美食啤酒爱好者,偶尔也做软件开发。除了计划下次去日本旅行或看动漫之外,我还在Infostud 集团旗下的poslovi.infostud.com工作。

文章来源:https://dev.to/kristijankanalas/from-monolith-to-microservices-in-one-year-1fff
PREV
学习区块链开发#day1 - 作为前端开发人员。
NEXT
如何在 VSCode 中恢复已删除的文件:你应该知道的一个很酷的技巧🔮