少即是多。一次架构上的"断舍离",让 RTMate 更轻、更纯粹、更接近"小而美"的初心。
一、先说说发生了什么
就在最近,RTMate 完成了 004-integrate-auth-server 分支的合并——简单来说,我们把原本独立的 rtmate-auth 认证服务,彻底迁移进了 rtmate-server 实时服务中。
这意味着什么?
Before: 你要跑 RTMate,得启动两个进程——一个管认证发 Token,一个管 WebSocket 实时通信。两份配置、两套日志、两条监控线。
After: 一条命令 cargo run -p rtmate-server,HTTP 认证和 WebSocket 实时通信全在一个进程里搞定。
代码结构上,整个工作空间从 3 个 crate(rtmate-common、rtmate-auth、rtmate-server)缩减为 2 个 crate。rtmate-auth 这个目录,连同它的 Cargo.toml、源码、示例,一并从仓库里消失了。
这不是一次简单的"代码搬家"。这是一次面向极简的主动设计。
二、为什么要"合二为一"?不是拆分才高级吗?
你可能想问:微服务不是趋势吗?把认证独立出来,不是更"解耦"、更"云原生"吗?
确实,在很多大型系统里,认证独立部署是合理的选择。但在项目早期,我们其实是有意把 rtmate-auth 拆出去的。当时的考虑很朴素:
-
故障隔离:WebSocket 是长连接、高并发场景,万一连接层出问题,不该拖垮认证接口
-
安全边界:认证涉及密钥校验、签名验证、JWT 签发,独立出来感觉"更干净"
但跑了一段时间后,现实给了更诚实的反馈:
两个服务共用一份 PostgreSQL 数据库,配置项高度重叠,日志格式却要分别采集。每次部署都要起两个进程,排查问题要在两套日志之间来回跳。更关键的是——RTMate 的认证流量并不高,WebSocket 的并发也远没到达需要进程级隔离的程度。为了"可能出问题"而硬拆,结果是解决了不存在的问题,制造了真实存在的复杂度。
这个转变也折射出 RTMate 整体思路的演进:从"先按大型系统标准搭架子",到"根据实际规模做合适的设计"。既然认证和实时通信天然共用数据与协议,何必为了"微服务"的概念,硬拆成两个部署单元?
"如无必要,勿增实体"——奥卡姆剃刀,同样适用于架构设计。
这次合并,让部署方从维护 2 个服务变成维护 1 个服务,配置、日志、监控线全部减半。对一个小而美的实时内核来说,这恰恰是正确的设计。
三、迁移背后的技术坚持:三件事没妥协
虽然做了减法,但有三件事我们一点没含糊:
1. 客户端零感知——协议 100% 兼容
合并是服务端的事,客户端不该为此改一行代码。
POST /api/auth/token 的请求格式没变,响应字段没变,错误码没变,甚至 JWT 的生成逻辑都原样迁移。我们用集成测试逐字段验证了:整合前编写的客户端代码,整合后照样跑通,无需任何修改。
这是架构演进的基本原则:重构可以改内部,但不能破接口。
2. 统一 Repository 层——数据库访问不能散
迁移之前,rtmate-auth 有自己的一套数据库查询逻辑。迁移之后,所有 SQL 操作全部收拢到 rtmate-server 的 infrastructure/persistence 层——也就是所谓的 Repository 模式。
不管是 HTTP 认证 handler 还是 WebSocket 消息 handler,都不直接碰数据库。它们只调用 Repository 接口,由 Repository 统一处理连接池、事务和错误转换。
这样做的好处显而易见:
-
数据访问逻辑集中,改一处全局生效
-
方便写测试,可以 mock Repository 接口
-
防止 handler 里散落 SQL,导致代码腐化
3. 统一响应封套——对外的每一帧 JSON 都长一个样
RTMate 从第一天起就坚持 统一的 JSON Envelope:
{ "code": 200, "message": "success", "data": { ... } }
不管是 HTTP 接口还是 WebSocket 消息,成功或失败,全部用这个格式。迁移后,rtmate-server 里的所有 handler 都遵循这个规范,没有裸返回、没有自定义结构。
这看起来是个小约定,但在实际对接客户端时,它意味着:前端只需写一套错误处理逻辑,就能覆盖所有接口。
四、小而美,不是将就,是选择
这次合并,其实反映了 RTMate 一以贯之的设计哲学:最小抽象原则(YAGNI)。
翻译成人话:不要为了组织代码而创造抽象,不要为了显得专业而拆分模块。
RTMate 的代码量不大,但每一个模块都有明确的存在理由:
-
rtmate-common:放共享契约(DTO、错误类型、响应结构),两个 crate 都需要,所以独立 -
rtmate-server:放运行时逻辑(路由、handler、连接管理、认证服务),全部围绕"一个可部署的二进制文件"来组织
没有为了"可能未来会用到"而预留的接口层,没有为了增加 star 数而堆砌的 feature flag。代码即文档,结构即意图。
这也使得 RTMate 成为一份不错的 Rust 学习材料:
-
想看 WebSocket handler 怎么写?打开
handlers/ws.rs,不到 100 行,Axum 的升级流程一目了然 -
想理解 Rust 所有权在并发中的实践?看
manager/ws_connection.rs,Arc+DashMap的组合清清楚楚 -
想知道多 crate 工程怎么组织?根目录的
Cargo.toml和两个子 crate 的依赖关系,就是最佳示例
小而美的内核,恰好也是最好的教材。
五、Roadmap:轻量之上,还能长什么?
合并之后,RTMate 的架构底座已经敲定:一个进程、两个 crate、统一协议、统一数据访问。
在这个底座之上,接下来的功能演进会沿着"轻量但有料"的方向继续:
-
Channels & Broadcast ✅ —— 基于 tokio broadcast channel 的高性能频道广播,已完成
-
Presence Tracking 🛠 —— 谁在线、在哪个频道,实时状态同步
-
JS SDK 🛠 —— 一行代码接入 WebSocket,connect / subscribe / send 开箱即用
-
Rate Limit & Metrics 🛠 —— 限流保护与用量统计,服务更稳
-
Webhook 🛠 —— 事件外抛,方便与业务系统联动
这些功能都会以"可选、可插拔、不臃肿"的方式加入。RTMate 不会变成 Pusher 或 Ably 的复制品,它会保持自己的调性:够用的功能、清晰的代码、一眼就能看懂的架构。
六、写在最后
做开源项目,很容易陷入"功能竞赛"——别人有频道管理,我也得有;别人有多租户,我也得加。但 RTMate 想走另一条路:先做减法,再做加法。
把 rtmate-auth 合并进 rtmate-server,看起来是少了一个模块,实际上是让项目的边界更清晰、部署更简单、心智负担更低。这是一种主动的克制,也是对"小而美"三个字的认真践行。
如果你也在做一个不那么大的项目,不妨问问自己:现在这些拆分,是真的有必要,还是只是为了"显得专业"?有时候,合二为一,反而是更高明的架构。
RTMate —— Minimal realtime WebSocket core, built with Rust.
📦 GitHub: BruceZhang54110/RTMate
🚀 一键启动: cargo run -p rtmate-server
📖 文档: README.md + docs/rust-overview.md
如果你认同"小而美"的理念,欢迎点个 Star,一起让 RTMate 保持轻盈。