diff --git a/docs/issues/rust-rewrite-feasibility.md b/docs/issues/rust-rewrite-feasibility.md new file mode 100644 index 000000000..8b0ec1d18 --- /dev/null +++ b/docs/issues/rust-rewrite-feasibility.md @@ -0,0 +1,490 @@ +# Issue Draft: 评估将 PocketBase 从 Go 全量重写为 Rust 的可行性 + +## 背景 + +当前 PocketBase 明确定位为 **“open source Go backend”**,同时既提供独立可执行程序,也提供可嵌入的 Go library/framework 用法。仓库主干实现、扩展机制、CLI、数据库接入、OAuth provider、JS VM 插件与测试资产都明显围绕 Go 生态构建。 + +本 Issue 旨在回答三个问题: + +1. **如果把仓库中的所有 Go 代码替换为 Rust,工作量大约有多大?** +2. **Rust 生态里是否存在足够完备、可一一对应的替代模块?** +3. **从产品、维护、兼容性与投入产出比角度,是否值得这样做?** + +--- + +## 一、仓库现状与迁移边界 + +### 1) 代码规模(基于本仓库当前扫描) + +- Go package:**44 个**。 +- Go 文件:**442 个**。 +- 其中生产代码:**262 个文件 / 37,922 行**(按“非空且非注释行”粗略统计)。 +- 测试代码:**180 个文件 / 56,569 行**。 +- 代码热点主要集中在: + - `core/`:数据模型、事件系统、数据库抽象、迁移、设置、字段系统。 + - `tools/`:OAuth、文件系统、路由、搜索、定时任务、安全、模板、mailer 等。 + - `apis/`:HTTP API、认证流程、realtime、备份、安装器、中间件。 + - `plugins/jsvm/`:JavaScript VM、类型导出、文件监听、脚本绑定。 + +### 2) 当前实现的“Go 原生性”非常强 + +从仓库结构看,这不是一个“把几层 HTTP handler 翻译一下”就能完成的迁移,而是一个 **平台级重写**: + +- **应用入口和 CLI**:`pocketbase.go`、`cmd/` 深度依赖 `cobra` 风格的命令行模型。 +- **数据库层**:`core/db_connect.go` 直接使用 `modernc.org/sqlite`,并配置了一组 SQLite pragma;大量 `core/`、`apis/`、`tools/search/` 代码依赖 `dbx`。 +- **可扩展性**:仓库 README 明确支持“作为 Go framework/toolkit 使用”,这意味着外部用户可能直接把 PocketBase 当 Go 库嵌入自己的应用。 +- **JS 扩展系统**:`plugins/jsvm/` 使用 `goja` + `goja_nodejs` + `fsnotify`,并向 JS 注入大量 Go 绑定,属于高度定制、非通用型能力。 +- **OAuth provider**:`tools/auth/` 下已有 **30+** provider/适配器文件,当前抽象是围绕 `golang.org/x/oauth2` 组织的。 +- **文件系统与图像**:本地/S3 存储、blob 抽象、mimetype 探测、图像处理/缩略图等都已经有较成熟实现。 + +### 3) “全量替换为 Rust”在语义上包含三件事 + +如果按最严格定义理解“全部替换”,实际不是语言重写这么简单,而是: + +1. **运行时代码重写**:服务端、数据库、API、认证、文件系统、后台任务、realtime、CLI、测试。 +2. **扩展/嵌入模型重写**:从 “Go library + JS VM” 转向 “Rust crate +(新的脚本机制)”。 +3. **生态与兼容性重建**:构建发布链、迁移机制、用户二次开发接口、文档与示例全部重做。 + +也就是说,最终交付物将不再是“同一个产品换实现”,而更接近 **“一个 Rust 版、兼容性有限的新 PocketBase”**。 + +--- + +## 二、Rust 生态是否有“一一对应”的替代? + +结论先说: + +> **Rust 生态足够强大,可以覆盖 PocketBase 的绝大多数能力域;但并不存在“完备、低风险、可直接一一替换”的全套对位模块。** +> +> 更准确地说,是“**功能域可覆盖**,但**语义、成熟度、集成成本、兼容性形态并不一一对应**”。 + +下面按能力域拆解。 + +### 1) Web/HTTP 路由、中间件、WebSocket/SSE + +**可替代程度:高。** + +Rust 侧可用组合: + +- `axum`:HTTP 路由、提取器、中间件、WebSocket 支持。 +- `tokio`:异步 runtime、signal、fs、task、channel、net。 +- `tower-http`:CORS、compression、trace、static files 等中间件。 + +**判断**:这一层不存在决定性阻碍,功能上完全可做,甚至某些并发/类型安全体验会更强。 + +**但注意**: + +- PocketBase 当前很多 API 代码是围绕自定义 `core.RequestEvent` / hook / router 抽象组织的,不是简单 `net/http -> axum` 机械替换。 +- 需要先重建事件模型、hook 生命周期、请求上下文与错误模型,再迁移路由。 + +### 2) CLI / 命令系统 + +**可替代程度:高。** + +Go 中当前主要对应 `cobra`;Rust 侧 `clap` 已足够成熟,命令、子命令、flag、帮助信息等能力都能覆盖。 + +**判断**:这块是低风险迁移区。 + +### 3) SQLite / 数据访问 / 迁移 + +**可替代程度:中等,且是核心风险之一。** + +Rust 侧常见方案: + +- `rusqlite`:同步 SQLite 访问,功能丰富,支持 `backup`、`blob`、`load_extension` 等特性。 +- `sqlx`:异步 SQL toolkit,支持 SQLite,带迁移与编译期查询校验。 + +**问题不在于“有没有 SQLite 库”,而在于“是否能无损替代当前设计”。** + +当前 PocketBase 用的是: + +- `modernc.org/sqlite`:README 里明确提到它是 **pure Go SQLite driver**,并且列出了当前支持的构建目标。 +- `dbx`:仓库内大量查询构建、事务、模型、搜索能力都建立在其 API/表达式模型上。 + +Rust 这边的现实情况: + +- `rusqlite` 本质上围绕 SQLite C API / `libsqlite3-sys` 构建;可通过 `bundled` 编译内置 SQLite,但这和当前 **pure Go 驱动** 的发布、交叉编译、静态分发体验并不是同一种语义。 +- `sqlx` 的 SQLite 很强,但它偏 async + SQL toolkit,不是 `dbx` 的直接对位替身。 +- 如果要保留 PocketBase 现有查询/迁移/动态 schema 体验,**很可能需要自研一层新的数据库抽象**,而不是简单换库。 + +**结论**: + +- SQLite 能做。 +- 但 `modernc + dbx` 这一组没有现成的 Rust “等价平替”。 +- 这部分不只是换 crate,而是 **重写数据库基础设施**。 + +### 4) OAuth2 / OIDC / 多 provider 接入 + +**可替代程度:中等偏高。** + +Rust 有成熟通用库: + +- `oauth2`:标准 OAuth2 流程支持。 +- OIDC 也有可选 crate 生态。 + +**但现实问题是**: + +- PocketBase 当前不是只支持“OAuth2 抽象”,而是已经内置大量 provider 适配器。 +- Go 侧 `golang.org/x/oauth2` 对很多 provider endpoint 有现成子包/约定,仓库内部也已经围绕这些 provider 写好了统一接口与归一化用户结构。 +- Rust 通常更偏“给你通用 OAuth2 能力”,**provider catalog 与现成 endpoint 便利性没有 Go 侧这套现成积累那么直接**。 + +**结论**: + +- 能实现,且没有理论障碍。 +- 但要把现有 provider 全量迁过去,需要较多手工校验、回归测试和长期维护。 +- 这块是 **“可做,但没有廉价一一替代”** 的典型。 + +### 5) 文件系统 / S3 / MIME / 图片处理 + +**可替代程度:高。** + +Rust 侧可选: + +- 本地文件:`std::fs` / `tokio::fs` +- S3:`aws-sdk-s3`,或统一抽象如 `opendal` +- MIME 检测:有对应 crate 可选 +- 图像处理:`image` + +**注意点**: + +- PocketBase 现有文件系统实现不是简单上传下载,而是带 blob 层、缩略图、属性、文件复制、reuploadable file、metadata 管理等行为。 +- 图片处理虽然可替代,但要核对格式支持、缩略图算法一致性、性能与内存占用。 + +**结论**:这块 Rust 生态成熟度足够,但仍然要做接口重建与兼容性测试。 + +### 6) 邮件 / JWT / 校验 / 模板 / 文件监听 / 定时任务 + +**可替代程度:高。** + +Rust 侧可用: + +- 邮件:`lettre` +- JWT:`jsonwebtoken` +- 校验:`validator` / 自定义 trait +- 模板:`tera` / `minijinja` +- 文件监听:`notify` +- 定时任务:`cron` / `tokio-cron-scheduler` + +这些领域不存在明显生态空洞。 + +### 7) JavaScript VM / Node-like 扩展环境 + +**可替代程度:低到中等,是另一个核心风险。** + +当前 PocketBase 的 JS 插件系统并不是“嵌个 JS 引擎跑几段脚本”那么简单,而是: + +- 用 `goja` 运行 JS; +- 用 `goja_nodejs` 提供 `require`、`process`、`console`、`buffer` 等近 Node 风格能力; +- 注入大量 PocketBase 特定绑定; +- 还处理 hooks、migrations、类型声明导出、目录监听、自动重启等行为。 + +Rust 侧候选: + +- `boa_engine`:纯 Rust JS 引擎。 +- `rquickjs`:QuickJS 的高层 Rust 绑定。 + +但问题在于: + +- `boa_engine` 虽然很有前景,但要达到 `goja + goja_nodejs + 定制绑定` 现有体验,工程量大,且 Node 风格兼容需要自行补很多层。 +- `rquickjs` 绑定 QuickJS,能力上更现实,但其 runtime/线程模型与当前 Go 并发风格不同,集成方式也需要重新设计。 +- 两者都**不是** `goja_nodejs` 的直接镜像替代物。 + +**结论**: + +- 如果要保留现有 JS 扩展体验,Rust 迁移的真实难点很可能首先出在这里。 +- 如果放弃现有 JS 扩展模型,代价则是对现有用户生态造成明显破坏。 + +--- + +## 三、工作量评估 + +## 结论版 + +如果目标是: + +- **功能基本对齐**(不是完全兼容); +- **重做 Rust 原生实现**; +- **接受部分对外 API / 扩展方式破坏性变更**; + +那么我给出的粗略估算是: + +- **60–100 engineer-weeks**(约 **15–25 engineer-months**)。 + +如果目标是: + +- 保持主要功能对齐; +- 尽量兼容现有数据模型、迁移、OAuth provider、JS hooks 思维模型; +- 达到“可以替代当前 Go 主线版本”的成熟度; + +则更现实的估计是: + +- **18–30 engineer-months**,并且需要至少 **2–3 名对 Rust、SQLite、异步运行时、嵌入式脚本运行时都比较熟的工程师**。 + +如果只有 **1 名工程师** 持续推进,这更像是 **一年级别** 的项目,而不是几个迭代内可落地的重构。 + +## 分阶段拆解 + +### Phase 0:调研 + 架构决策(2–4 周) + +- 选定 HTTP 栈(`axum` / `hyper` 组合)。 +- 选定 DB 栈(`rusqlite` / `sqlx` / 自研抽象)。 +- 选定脚本策略(保留 JS、改成 QuickJS、改成 Lua、或直接砍掉)。 +- 定义兼容性边界: + - 是否兼容当前 Go library 用法? + - 是否兼容当前 JS hook 语义? + - 是否兼容现有配置、迁移、二进制分发方式? + +### Phase 1:核心底座(2–3 个月) + +- app 生命周期 +- hook / event 系统 +- 配置与 settings +- collection / record / field 基础模型 +- SQLite 连接、事务、迁移执行器 +- 基础查询能力与错误模型 + +### Phase 2:API 与认证(2–3 个月) + +- REST API +- auth with password / OTP / MFA / refresh / verification / reset +- OAuth2 provider 框架与至少首批 provider +- realtime / subscriptions +- 中间件(CORS、gzip/body-limit/rate-limit 等) + +### Phase 3:文件系统与平台能力(1–2 个月) + +- 本地文件系统 +- S3 存储 +- 图片转换/缩略图 +- 邮件发送 +- backup / restore +- 日志、定时任务、模板 + +### Phase 4:脚本与扩展(2–3 个月,波动最大) + +- JS runtime(若保留) +- 绑定层重建 +- migrations/hook loader +- 文件监听与热重启语义 +- 类型导出/开发体验 + +### Phase 5:测试、兼容与发布(2–3 个月) + +- 补齐/迁移测试体系 +- 性能回归 +- 数据兼容验证 +- 构建产物、交叉编译、发布管线 +- 文档与示例全面更新 + +> 其中 **Phase 4(脚本系统)** 与 **SQLite/数据库抽象** 是两个最大不确定项。 + +--- + +## 四、哪些地方没有“完备的一一对应替代”? + +下面给出更直接的结论表。 + +| Go 侧现状 | Rust 侧可选 | 结论 | +| --- | --- | --- | +| `cobra` CLI | `clap` | **可平替** | +| `net/http` + 自定义 router/event | `axum` + `tokio` + `tower-http` | **能力覆盖,但不是接口级平替** | +| `modernc.org/sqlite`(pure Go) | `rusqlite` / `sqlx` | **功能可覆盖,但不是发布/链接/抽象层的一一对应替代** | +| `dbx` 查询构建/抽象 | 无明显等价物 | **需要自研适配层或重写数据访问设计** | +| `golang.org/x/oauth2` + 多 provider 现成实现 | `oauth2` + 手工 provider 集成 | **标准流程可覆盖,但 provider 便利性和存量适配无法一键平移** | +| `goja` + `goja_nodejs` | `boa_engine` / `rquickjs` | **没有成熟的一一对应替代,尤其是 Node-like 体验与现有绑定层** | +| `fsnotify` | `notify` | **可替代** | +| `mailyak` | `lettre` | **可替代** | +| `imaging` | `image` 等 | **大体可替代,但需核对格式/缩略图行为** | +| Go library 嵌入式用法 | Rust crate 用法 | **技术上可重建,但对现有 Go 用户不是兼容替代** | + +### 最关键的三个“非平替”问题 + +1. **数据库层不是换库,而是要重建抽象。** +2. **JS VM/插件系统没有现成镜像替代。** +3. **PocketBase 当前作为 Go framework 的价值,在 Rust 重写后会直接变成另一种产品定位。** + +--- + +## 五、值不值得做? + +## 我的判断 + +> **以当前仓库与产品形态来看,“全量 Go -> Rust 重写”总体上不值得作为主线投入。** + +### 原因 1:收益不集中,代价却是系统性的 + +Rust 当然有明显优点: + +- 更强的内存安全模型; +- 对并发状态与所有权的约束更严格; +- 某些高并发场景可能更易做极限优化; +- 对部分团队来说,长期维护感受可能更好。 + +但 PocketBase 当前的主要风险与价值,并不集中在“Go 语言本身不够好”。 + +真正昂贵的是: + +- 数据与兼容性; +- 扩展模型; +- 用户生态迁移; +- 文档与 SDK 认知迁移; +- 发布/构建/测试体系整体重做。 + +也就是说,**迁移成本远高于语言本身带来的直接收益**。 + +### 原因 2:会打断当前产品最清晰的定位 + +PocketBase 现在的一个重要卖点是: + +- 单文件可执行; +- 作为 Go backend / Go framework 使用; +- 可用 JS 扩展。 + +如果全面转 Rust: + +- “Go framework/toolkit” 这一产品定位直接消失; +- JS 扩展模型大概率需要降级或重做; +- 现有用户对嵌入方式、插件方式、开发体验的预期都会变化。 + +这不是“内部实现升级”,而是 **产品定义变化**。 + +### 原因 3:最难迁移的恰好是 PocketBase 的差异化能力 + +最容易迁的是:HTTP、CLI、邮件、JWT、文件监听。 + +最难迁的却是: + +- 数据模型与迁移系统; +- JS VM 插件机制; +- Go library 嵌入能力; +- 大量 provider 与运行时细节。 + +这些恰好才是 PocketBase 与“又一个 CRUD 后端框架”拉开差异的地方。 + +### 原因 4:全量重写会长期冻结功能演进 + +在 6–12 个月甚至更长时间内,团队注意力会被: + +- 兼容性缺口; +- bug parity; +- 构建问题; +- 平台差异; +- 生态替代验证 + +持续吞噬。对外看起来就是: + +- 新功能变慢; +- 回归风险增大; +- 用户难以判断该押注哪个版本线。 + +--- + +## 六、什么情况下“值得做”? + +只有在下面这些前提同时成立时,才值得认真立项: + +1. **战略目标明确转向 Rust 平台能力**,例如未来要重点提供 Rust crate 嵌入、Rust 扩展、Rust-first 部署体验。 +2. **愿意接受产品重定义**,不再强调 Go framework 兼容,不再承诺现有 JS 运行时语义保真。 +3. **团队内部已有成熟 Rust 主力**,并能长期投入 2–3 人以上。 +4. **愿意将这件事当作新产品线/新 major line**,而不是普通技术重构。 + +如果做不到以上四条,建议不要把它作为近期主线。 + +--- + +## 七、更合理的替代方案 + +相比“一次性全量 Rust 重写”,更建议以下路线: + +### 方案 A:保持主线 Go,不做全量迁移(推荐) + +继续维持当前 Go 实现,把精力放在: + +- 核心稳定性; +- OAuth/provider 补强; +- JS VM 与文件系统体验优化; +- 性能热点定点优化; +- 文档与 SDK 生态。 + +### 方案 B:只把局部高价值模块 Rust 化 + +例如把这些模块抽成独立服务/库做实验: + +- 图片处理/压缩/缩略图; +- 全文搜索或 query planner; +- 特定 CPU 密集型任务; +- 某些文件处理流水线。 + +这样可以在不颠覆主架构的前提下获得 Rust 的局部收益。 + +### 方案 C:先做“Rust Spike / PoC”而不是立刻重写 + +建议最多只做一个 **2–4 周的 PoC**,范围严格控制在: + +- `axum + tokio + rusqlite/sqlx` 能否快速搭出一个最小 PocketBase 核心; +- 复现最基础的 collection/record CRUD; +- 验证文件上传与 SQLite schema 迁移; +- 单独验证 JS runtime 方案是否可接受。 + +如果 PoC 不能在短时间内证明脚本系统、数据库抽象和开发体验都站得住,就不应继续扩大投入。 + +--- + +## 八、建议结论 + +### 建议结论(供 maintainer 直接采纳) + +- **不建议**把“全量 Go 代码替换为 Rust”作为当前主线 roadmap。 +- **建议**把这件事降级为: + - 一次架构研究;或 + - 一个受限范围的 Rust PoC;或 + - 若未来战略改变,再作为新 major line 单独立项。 + +### 一句话判断 + +> **Rust 生态足够强,能把 PocketBase 再造一遍;但并没有一套完备到可以低风险“一一替换”当前 Go 架构的现成模块,因此全量迁移更像“重做产品”,而不是“升级实现”。从投入产出比看,当前阶段不值得。** + +--- + +## 参考资料(用于本次评估) + +### 仓库内事实依据 + +- README:项目定位、Go framework/toolkit 用法、pure Go SQLite driver build target 说明。 +- `pocketbase.go`:应用启动与 `cobra` 命令结构。 +- `core/db_connect.go`:`modernc.org/sqlite` 与 pragma 配置。 +- `plugins/jsvm/jsvm.go`:`goja`/`goja_nodejs`/`fsnotify`、hooks/migrations/types 绑定。 +- `tools/auth/auth.go` 与 `tools/auth/*`:OAuth provider 抽象与现有 provider 面。 +- `tools/filesystem/filesystem.go`:本地/S3/blob/图像处理集成。 +- `apis/serve.go` / `cmd/serve.go`:HTTP server、TLS、CORS、静态资源、中间件与 CLI 启动路径。 + +### Rust / Go 生态参考(官方文档或项目主页) + +- Axum docs: +- Tokio docs: +- tower-http docs: +- Clap docs: +- Rusqlite docs: +- Rusqlite GitHub README(`bundled`、`backup`、`blob`、`load_extension` 等): +- SQLx docs: +- oauth2 crate docs: +- notify docs: +- lettre docs: +- image docs: +- jsonwebtoken docs: +- AWS SDK for S3: +- OpenDAL docs: +- Boa docs: +- Boa project: +- rquickjs docs: +- rquickjs project: +- Go oauth2 docs: +- modernc sqlite docs/project refs: + +--- + +## 建议的后续动作 + +- [ ] 如果 maintainer 只想做研究:将本 Issue 归类为 **ADR / research**,不进主 roadmap。 +- [ ] 如果仍想进一步确认:立一个 **2–4 周 Rust PoC** 子任务,目标只验证 CRUD + SQLite + 文件上传 + 一个最小 hook 方案。 +- [ ] 如果 PoC 不通过:关闭本方向,回到 Go 主线优化。 +- [ ] 如果 PoC 通过:再另开新 Issue,明确“这是新产品线还是新 major version”。