NDDev 后端是怎样构建的:Rust、单一数据库,以及代替管理后台的智能体
NDDev 的六个五语种网站、询价表单、会议预约、助手和内容发布,都运行在同一个核心之上。下面介绍它背后的技术决策以及我们这样做的原因。
基于 Rust 的模块化单体
核心是一个分为三层的 Cargo 工作区。domain 描述不涉及输入输出的规则:什么是修订、发布或询价,以及允许哪些状态转换。infrastructure 负责 PostgreSQL 和外部服务。api 提供 HTTP、命令行和 MCP 接口。SEO 和可观测性被拆分为独立的 Rust 服务,各自拥有数据库:它们的变更节奏和故障方式都不同。
技术栈是 Rust 1.99、Tokio、axum、SQLx 和 PostgreSQL。标识符采用 UUIDv7:由应用生成,并按创建时间排序。
用智能体代替管理后台
平台没有可视化 CMS。所有者及其智能体通过命令来管理它:命令行、HTTP API 和 MCP 调用的是同一个命令层。每条命令都有类型约束、会检查权限,并接受幂等键——用同一个键重复请求会返回先前的结果,而不是产生重复数据。修改时需要声明期望的文档版本:如果文档已被他人改动,命令会拒绝执行,而不是悄悄覆盖别人的工作。
就连网站的联系方式和界面文案也不是代码里的常量,而是数据库中的修订,通过命令修改,并与内容一起发布。
单一数据库与可靠的后台任务
所有状态都保存在 PostgreSQL 中。后台工作——SEO、翻译、站点构建、邮件——都以任务形式存放在同一个数据库里:outbox 与 inbox、带 fencing 令牌的任务租约、短事务。在这样的负载下不需要单独的消息中间件。我们不承诺外部副作用恰好执行一次:外部服务的结果只有在回应了确实委托过的工作、且基于同一组源数据时才会被接受。
修订与快照式发布
每次修改都会生成一个新的不可变修订。发布会把指定的修订及其 SEO 产物打包,并赋予一个代号。网站依据这个包的清单构建,因此重新构建会得到相同的页面。要恢复某个页面的旧版本,只需重新发布它之前的修订——那份修订一直都在。
SEO 是独立服务
每个可被索引的修订都会向独立服务委托 SEO 工作。模型提出标题和描述,另一个编辑请求将其与原文核对,再由 Rust 检查引文、摘要形态,并确认没有编造的价格或指标。canonical、robots、hreflang 和 schema.org 标记均以确定性方式生成。如果模型不可用,结果会被如实标记为备用方案,只有在明确决定后才能发布。
由发布器掌控的前端
前端是一个独立的 Astro 仓库。核心的发布器在沙箱中构建它:隔离的 Linux 命名空间、Landlock、无网络、无法访问密钥。发布器亲自度量构建产物,然后才把它推送到网站;前端的上一个版本只需一条命令即可恢复。
数据与延迟
核心在阿姆斯特丹写入,数据库的同步副本运行在哈萨克斯坦。我们实测过另一种方案:如果把写入迁到哈萨克斯坦而 API 留在阿姆斯特丹,每条数据库语句都要穿越约 90 毫秒延迟的链路。提交询价会从 119 毫秒增加到 2.58 秒,打开目录从 61 毫秒增加到 558 毫秒,搜索从 55 毫秒增加到 929 毫秒。而同步副本每次提交只需一次往返。
可观测性
追踪和指标以 OpenTelemetry 格式发送,但核心的运行并不依赖接收端:缓冲区是有界的,即使采集不可用,请求也照常处理。
对客户项目意味着什么
我们不会把整套架构照搬到每个项目——小网站并不需要它。但有几条原则处处适用:系统各部分之间有明确的契约,变更既不会丢失也不会被执行两次,生产环境中的行为可度量,并且无需手工修改数据就能回到上一个版本。