Macro 的技术选型:Rust 后端 + Solid 前端 + Loro CRDT,这套组合带来哪些工程约束
看一个刚开源不久的协作平台,最先该看的其实不是功能列表,而是它的技术选型。因为选型决定了两件很实际的事:你想给它提 PR,本地要付出多大代价才能把整套东西跑起来;你想自托管,会连带背上哪些外部依赖。
Macro 这个项目在这方面信息给得算多。README 里有仓库目录树,文档站首页有一句技术栈自述,概念文档里把文档协同的实现路径写到了服务名和 WebSocket 路径这一层,另外还有一份专门的本地运行指南。把这几处拼起来,大致能看清它的骨架。
先把话说在前面:本文所有内容都来自 Macro 的公开文档和 README,我们没有安装、没有部署、没有跑过它的任何一个服务。凡是涉及”快不快”的说法,一律是转述官方的表述,不是我们的实测结论。
官方自述的那一句:Solid 前端 + Rust 后端
文档站首页的原话是,Macro 用 Solid 前端和 Rust 后端构建,“所以它很快”(so it’s fast)。README 里的说法是”用 SolidJS 和 Rust 构建,为了速度和可靠性”。首页的”Built for speed”一节把这个说法展开成三点:搜索是即时的,协作是实时的,所有操作都有键盘快捷键。
这是官方文档的说法,我们没有实测,也无法验证”快”到什么量级——文档里没有给任何基准数字、延迟指标或并发上限,所以这里也没有数字可以引用。
文档里唯一带对照意味的一句在 README 的文档模块部分:官方说编辑内容是”近乎即刻”到达,而不是像 Google Docs 那样”一顿一顿地”卡。这同样是官方文档自己的措辞,我们如实转述,不作优劣判断。
顺带说一句团队背景,README 里写的是:产品由团队在纽约和多伦多设计,约 15 人的团队自己内部用了两年。这是一个能解释很多设计取向的信息——很多设计是先服务自己团队的工作流,再对外开放的。产品层面的定位可以参考Macro 是什么。
仓库是怎么切的:42 个服务、167 个 crate
README 底部给了完整的目录树,这部分信息比一句技术栈自述有用得多:
| 目录 | 官方描述 |
|---|---|
apps/web/ | SolidJS 客户端——浏览器、Tauri 桌面端、移动端 |
apps/docs/ | docs.macro.com 文档站 |
services/ | 42 个可部署的服务、worker 和 Lambda handler |
crates/ | 167 个 Rust 库——领域逻辑、模型、数据库客户端 |
packages/ | 共享 TypeScript——collaboration、lexical-core、loro-mirror |
infra/ | Pulumi 定义 |
docker/ | 本地 Compose 栈 |
nix/ | 固定版本的开发 shell 与构建输入 |
tooling/ | 仓库脚本与代码生成器 |
有几点值得单独拎出来。
一是前端只有一份。apps/web 这一行同时挂着浏览器、Tauri 桌面端和移动端三个目标,也就是说桌面端不是另写的原生壳里再塞一套代码,而是同一个 SolidJS 客户端通过 Tauri 打包。
二是后端切得很碎。42 个可部署单元、167 个 Rust crate,这个粒度意味着领域逻辑是按库拆的,服务只是把库组装起来对外暴露。README 里补了一句服务的组织约定:服务遵循六边形架构(hexagonal layout)——入站适配器、带端口的领域核心、出站适配器。约定写在仓库的 docs/STYLE_GUIDE.md 里。
三是 packages/ 里那三个名字。collaboration、lexical-core、loro-mirror——loro-mirror 这个名字直接指向了下一节要讲的 CRDT 实现。这块是前后端共享的 TypeScript,说明协同这条链路不是纯后端的事。
文档协同这条链路:Loro CRDT + Cloudflare Durable Objects
Macro 的概念文档里,把实时协作的实现写得相当具体,这在开源项目文档里不算常见。原文的说法是:可协作的块(文档、PDF 批注)通过 Loro CRDT 同步,后端跑在 Cloudflare Durable Objects 上;Rust 写的 sync-service 为每个文档拉起一个 Durable Object “房间”,客户端通过 WebSocket 连到 /document/:id。官方说,这套东西支撑的是多人同时编辑、在线状态光标(presence cursors)和完整的离线支持。
这段话里信息密度最高的是”每个文档一个 Durable Object 房间”。Durable Object 是 Cloudflare 的有状态执行单元,一个 ID 全局对应一个实例——把文档 ID 映射成房间 ID,等于把”同一份文档的所有并发编辑必须落到同一个协调点”这件事直接交给平台去保证。CRDT 负责合并语义,房间负责收敛点。
再往上一层是 Agent。README 里写,Agent 的文档编辑能力是”以对等身份加入 CRDT 协作系统”实现的,跟人类协作者一样;Agent 可以编辑打开着的文档,也可以编辑没打开的文档。官方举的例子是一个每天跑的 Automation,扫描所有频道更新一份台球赛程 markdown 文档,如果有人已经改过,它能知道并跳过更新;冲突由 CRDT 协作系统原生处理。
这个设计的工程含义很直接:Agent 写文档不需要另开一条”服务端直改数据库”的旁路,它走的是和浏览器客户端同一条同步通道。协同机制的更多细节可以看Macro 的文档协同,块模型这一层则在Macro 的数据模型 blocks。
需要提醒的是,版本控制这块官方说得很保守:历史和分叉功能”还是 v1,要做到接近 git 还有很多事要做,或者我们最终可能加上 git 兼容”。也就是说 git 兼容目前只是官方提及的可能方向,尚未提供。移动端方面,iOS 应用已有,Android 应用官方标注为”即将推出”,目前尚未提供。
这套组合让本地开发变成什么样
选型的代价在本地运行指南里体现得最清楚。Rust 后端加上多服务架构,意味着本地不是 npm run dev 就能起来的。官方要求先装两样东西:Nix 包管理器,以及带 Compose v2 插件的 Docker。
git clone https://github.com/macro-inc/macro.git
cd macro
nix develop
Nix shell 里带的是 just、Cargo、Rust 工具链、Bun、sqlx、zig 和 cargo-zigbuild,官方明确说不需要另外单独装 just 和 Cargo。如果 nix develop 失败,是实验特性没开,可以临时加参数,或者写进 ~/.config/nix/nix.conf:
experimental-features = nix-command flakes
Tauri 相关的平台依赖不在默认 shell 里,官方给的理由是它们体积太大,所以放在各自独立的 shell:Linux 桌面端开发用 nix develop .#tauri-linux,x86_64 Linux 上做 Android 开发用 nix develop .#tauri-android。
启动整栈的命令,对没有 Doppler 权限的贡献者是这条:
just run_local --no-doppler
这条命令做四件事:构建 Rust 后端服务、拉起本地基础设施、启动后端服务、启动本地代理和前端。本地基础设施跑在 Docker 里的有 Postgres、Redis、LocalStack、OpenSearch、Kafka 和 FusionAuth——六个组件,这是”多服务 + Rust”这套选型摊到本地机器上的实际重量。
编译方式也值得注意。官方写的是:Rust 服务在宿主机上用 cargo zigbuild 构建,产出的二进制挂载进一个共享运行时镜像,正常的 run_local 过程中 Docker 并不编译这些服务。这解释了为什么 Nix shell 里要带 zig 和 cargo-zigbuild——交叉编译产出的是能塞进容器的二进制,而不是在容器里从头编译。
但有三个服务是例外,它们的镜像是 Docker 构建的,默认不会重建:
| 例外服务 | 官方说明 |
|---|---|
sync_service | 改动后需 --build-aux-services 才会重建镜像 |
lexical_service | 同上 |
websocket_service | 同上 |
三个例外里有两个(sync_service、websocket_service)正好落在协同这条链路上。也就是说,如果你要动的恰好是 CRDT 同步或 WebSocket 这块,本地循环会比改普通 Rust 服务慢一档——官方也直说了这个标志位”更慢,除非你在改这几个服务,否则别开”。
日常控制方面,run_local 挂在终端上时按 r 重建有改动的 Rust 服务并热重载,按 q 停止并退出。官方特别提醒用 q 而不是直接关终端窗口,因为 q 会一次性停止并移除容器,下次启动不用先清理残留。
另外还有一条无终端的路径:
just stack up
just stack status --json
just stack update
just stack down
这条路径没有热键循环、没有 dev server,前端只构建一次并由代理静态提供,整个产品跑在同一个 origin 后面。更完整的本地栈操作、多实例与端口冲突处理,见Macro 自托管与本地跑通。
这套选型没解决的、以及不适合的场景
先说文档里明确留白的部分。性能这条线,官方只给了定性表述,没有任何基准数据,所以”Rust 后端到底带来多少收益”这个问题,公开材料回答不了,我们也不打算替它回答。
再说依赖。协同链路绑在 Cloudflare Durable Objects 上,这是文档写明的实现方式。至于自托管场景下这一块具体怎么处理,官方把自托管的说明放在了 FAQ 页面,不在我们这次读的四份材料里,所以这里不作推断。许可证方面 README 写得很清楚:AGPLv3,官方强调是”完全开源,不是 open core”,可以按 AGPLv3 条款自托管。
哪些团队不适合直接上手改这个仓库?从选型看,大概是三类。第一类是团队里没有 Rust 经验的——167 个 crate 的领域逻辑不是靠读几天文档能接上的。第二类是不接受 Nix 的——整个开发环境固定在 Nix shell 里,绕开它意味着自己复刻 Rust 工具链、Bun、sqlx、zig 这一整套版本组合。第三类是机器资源紧张的,本地要同时跑六个基础设施组件加一堆 Rust 服务。
反过来,这套选型对哪类人是加分项也很清楚:想读一份工业级 Rust 后端如何按六边形架构组织的人,想看 CRDT 协同在真实产品里怎么落地的人,以及想搞明白”Agent 以对等身份接入协同系统”这条路子怎么走通的人。这三件事在这个仓库里都有可读的实现路径,而不只是博客上的架构图。
最后留一个我们没能从这四份材料里读到的问题:packages/lexical-core 和 Loro 之间是什么关系。名字上看,一个是编辑器内核,一个是 CRDT 运行时,中间还夹着 loro-mirror,但文档没写这三者的分工。想弄清楚只能去读代码,这一步我们没有做,所以不写。
延伸阅读
- 从头读起:Macro 是什么:邮件、任务、文档、CRM 共用一个双向数据库的开源工作区
- 本专题共 40 篇,完整分组目录见专题页
- Macro 十五分钟上手:官方 Get Started 的七步里,哪几步是真卡点
- Macro 邮箱怎么接入:Gmail 多账号合成一个收件箱,以及它管不管你的邮箱
本文依据 Macro 官方仓库(github.com/macro-inc/macro,AGPL-3.0 协议)的 apps/docs/ 产品文档、
MCP 工具参考与自托管说明整理,核对日 2026-08-17。
我们没有注册或运行过 Macro,因此不涉及界面外观与操作手感;
官方标注为计划中的能力文中已如实标明,不代表当前可用。
价格与额度以官网 macro.com 最新页面为准;许可证相关问题请咨询专业人士并以官方许可证原文为准。