技术

曾经是一个整体的 Omnilude 后端,如何在 AI 协作下转成 MSA

AAnonymous
12分钟阅读

开始之前

这篇文章是 为了打造 Omnilude,我们正在这样构建后端 的下一篇。上一篇里,我把 Omnilude 当前的后端结构和职责边界,当作一个完整的产品后端来介绍。这一篇,我想继续讲清楚,这套结构究竟是如何从一个庞大的项目,逐步拆成多个服务边界的。

MSA 这个话题经常听起来很抽象。说系统拆分了、可以独立部署了、边界清晰了很容易,但在真实项目里到底发生了什么变化,往往并不直观。

Omnilude 的后端正好有足够具体的材料来说明这件事。现在它已经拆成了 auth-servicebackbone-servicestorage-serviceai-servicegame-serviceblog-servicelegacy-service 等多个服务,但它一开始并不是这样。最早它只是一个很大的 Spring Boot 后端。

这篇文章会尽量用容易理解的方式,说明那个单一项目为什么开始拆分,为什么先抽出 jspring(common),为什么刻意保留 legacy-service,以及 AI 在这个迁移过程中到底起到了什么作用。

一开始,它只是一个很大的后端

在拆分之前,Omnilude 的后端如果简单描述,就是很多东西都堆在同一个项目里。认证在里面,AI 在里面,游戏逻辑在里面,文件存储在里面,实时事件在里面,共通工具也都在里面。

这种结构在一开始其实很方便。加功能很快,改代码也都在同一个地方完成。但项目一大,问题就会慢慢出现。

  • 像认证这样需要谨慎处理的部分
  • 像 AI 这样变化频繁、实验很多的部分
  • 像实时事件这样对稳定性要求很高的部分
  • 像游戏这样领域逻辑会快速膨胀的部分

最终都会承受同一套部署压力。

如果要打个比方,就像把换季衣服、运动服、正装、被子和工具箱全塞进一个衣柜里。最开始会觉得方便,到了某个阶段,连改动其中一件东西都会变得很费劲。

为什么一定要拆?

我并不是一开始就把 MSA 这个形式本身当成目标。真正推动这次拆分的,更多还是很现实的原因。

最重要的一点,是我想把那些需要长时间保持连接的功能,从普通部署的影响中切出来。Omnilude 里有一些非常接近 WebSocket、SSE、事件流和长时间异步任务的部分,尤其是 backbone-service。这些区域如果因为不相关的业务修改一起被牵动,运维体感会立刻变差。一个博客功能或游戏功能的修改,就可能让对连接敏感的服务一起重启,这种结构很难长期维持。

第二个原因是部署时间。哪怕只是一个很小的修改,也要重新构建整个大应用、重新打镜像、再重新部署。项目越大,这个时间就越让人觉得浪费。变化快的部分想提高试验速度,但只要部署单位还是一个,整个系统就会一起变慢。

接下来暴露出来的问题,是职责边界越来越模糊。认证规则、通用异常、内部 HTTP、消息系统、文件存储这些东西混在整个代码库里,久而久之,很难分清什么是共通部分,什么是业务域逻辑。

还有一个更大的原因,就是我已经很清楚接下来想做什么。在 Omnilude 里,我想继续扩展 AI 功能,继续把游戏服务做深,也想不断增加博客和工具这类独立产品。如果所有东西都继续塞在一个后端里长大,到了某个阶段就不再是一个好选择了。

换句话说,这次迁移并不是因为 MSA 看起来很酷才做的。它更像是一次不得不进行的结构调整,为了保护那些需要稳定连接的服务,缩短部署时间,并继续把后面的想法推上去。

第一步不是拆服务,而是先抽出共通规则

这里有一个很重要的点。这次迁移的第一步,不是先把 auth-serviceai-service 拆出来,而是先做了 common

这一点非常关键。表面上看,服务拆分像是在切领域;但真正开始做的时候,第一件事其实是先把什么是共通契约定义清楚,并把它抽出去。

在 Omnilude 里,common 承担了这个角色。

  • 通用异常处理
  • JWT 约定
  • 公共 DTO
  • Web 层工具
  • JPA 与数据访问基础
  • 内部 HTTP 与消息辅助

这些东西先放进 common 之后,后面即使继续拆服务,也不会让每个服务都变成各说各话的独立小应用。

可以把它理解成,在把房间分开之前,先把每个房间都会用到的插座规格和门把手规格统一好。否则,就算房间分开了,住起来也不会更舒服。

拆分其实在很短时间里就迅速完成了

从仓库历史来看,这次迁移推进得非常快。2026 年 1 月 18 日到 1 月 22 日之间,核心骨架基本就搭出来了。

把流程简单概括一下,大致是这样。

  • 抽出 common
  • 拆出 auth-service
  • 拆出 backbone-service
  • 拆出 storage-service
  • 开始分离 ai-service
  • 把剩余区域隔离到 legacy-service
  • 抽出 game-service
  • 再把 blog-service 作为新服务放上去
  • 让 Jenkins 和 Kubernetes 支持按服务选择性部署

这个顺序本身是有逻辑的。

auth-service 的边界相对清晰。认证、账户、权限本身就是其他服务会共同依赖的部分,先拆出来,后面的结构就更容易有参照点。

backbone-service 更偏基础设施,和实时事件、消息系统、DTE 之类的能力联系更紧。把它和高频变化的产品逻辑分开,在运维上会自然很多。

storage-service 则承担多个服务共享的文件存储契约。把文件处理集中到一个地方后,博客、游戏、AI 生成结果都可以走同一条链路。

再之后才是 ai-service。这一块体量更大、依赖更多,难度也更高。但它也是 Omnilude 里最需要频繁实验的区域之一,所以把它看成独立服务是合理的。

为什么要保留 legacy-service

一提到 MSA 迁移,很多人会先想到一种很干净的画面:把单体一次性拆完,彻底结束。但真正做起来会发现,这种结局远没有想象中现实。

所以在 Omnilude 里,那些还没完全迁出的领域,被统一收进了 legacy-service

这个决定并不算后退,更像是一个缓冲区。

legacy-service 的存在让很多事情成为可能。

  • 不需要为了赶进度而强行打碎还没迁完的功能
  • 新服务和旧服务可以在同一个产品下并存
  • 在持续拆分的同时,系统还能继续运行
  • 可以把难的领域往后放,先拆出边界清楚的部分

我反而觉得,这才是现实里的 MSA 迁移核心。真正的迁移,通常不是先得到一个完美拆分,而是先得到一个能稳定运行的边界。

游戏、博客和 AI 在这个基础上变得更清晰了

从这里开始,产品属性更强的服务也逐渐有了清晰轮廓。

ai-service 已经不是一个简单的聊天 API。它里面有 agent 执行、工作流、生成流水线、媒体生成、角色扮演系统、商品 agent 等等。到了这个规模,把它当成一个独立服务反而更自然。

game-service 则更接近我最终最想在 Omnilude 里做大的方向。随着故事问答、剧本、资产工作室等能力叠上去,它逐渐变成了一个把 AI、存储和认证都串起来的领域型服务。

blog-service 稍微有点不同。它并不是从原始单体里直接切出来的领域,更接近一个建立在分离架构之上的新服务。因为部署和共通规则已经先被拆开,所以博客从一开始就能按照独立服务来设计和运作。

也正因为如此,这次 MSA 迁移不只是把旧东西拆开,它同时也为之后的新产品提供了更好的落点。

真正让我觉得变化发生了,是部署这件事

只看代码,有时候项目看起来像是被拆开了。但 MSA 真正开始成立,是在部署结构发生变化的时候。

在 Omnilude 里,这一点清楚地留在了 JenkinsfileJenkinsfile.prdkubernetes/services/** 里。

现在 Jenkins 可以按需选择部署下面这七个服务。

  • auth-service
  • backbone-service
  • storage-service
  • ai-service
  • game-service
  • blog-service
  • legacy-service

也就是说,仓库虽然还是一个,但它已经不再是必须整体一起发布的单一应用,而是一个可以按服务选择发布的结构。

Kubernetes 也是同样的方向。每个服务都有自己的 DeploymentService,而 Ingress 则在 api.omnilude.comdev-api.omnilude.com 下面按 path prefix 做路由。

这一点非常重要。MSA 不是把文件夹拆开,而是把实际运行和部署的单位拆开。对 Omnilude 来说,走到这一步之后,我才真正觉得它已经从单体里走出来了。

最终真正重要的,并不是服务数量

做完这样的迁移之后,很容易把注意力放在到底拆出了多少个服务上。回头看,数字本身并不是重点。

真正重要的是下面这四件事。

  • 是否先把共享规则抽出来,让后续拆分成为可能
  • 是否区分了变化快的领域和变化慢的基础设施
  • 是否先做出了即使不完美也能运行的边界
  • 是否把代码层面的拆分真正连接到了部署层面的拆分

如果用这几个标准来看,我会觉得 Omnilude 的这次迁移相当务实。它也许不是最漂亮的图,但它先做出了一个能继续承载下一个产品和下一次实验的结构。

结尾

Omnilude 后端的这次 MSA 迁移,与其说是一开始就有一份庞大战略文档,不如说更像是一场为了做更大东西而迟早要经历的结构整理。

它从一个大项目开始,抽出 common,拆分 authbackbonestorageaigameblog,把剩余部分放进 legacy-service,最后再让 Jenkins 和 Kubernetes 上的真实部署单元跟着对齐。

我觉得这整个过程很有 Omnilude 的味道。Omnilude 这个名字里同时带着 omni- 的包容感和 -lude 的游戏感。很多功能和服务并不是为了永远分散存在,而是为了最终汇聚成一个更大的游戏体验。从这个角度看,这次迁移和名字本身也很像:因为想去的方向很清楚,所以结构也不断向那个方向调整。

下一篇里,我想再往前走一步,聊一聊我们究竟是怎样在实际层面实现这款游戏的。