aps-agent/docs/adr/0007-storage-and-consistenc...

4.7 KiB
Raw Permalink Blame History

ADR-0007:数据存储选型与双形态一致规则

  • 状态:已接受
  • 日期:2026-09-23
  • 决策人:技术负责人
  • 相关:任务表 A0(决策 D11);设计文档《实施设计》1.2、1.4

背景

平台有两种落地形态:单机版(Electron 桌面端 + 本地平台服务)与远端服务器版。库选什么,决定三件事:单机能不能零运维、远端能不能扛并发写入与防篡改、上层代码会不会因为库不同而分叉。

《实施设计》1.4 已经按「桌面用 SQLite 与 JSONL、服务端用 PostgreSQL」写过一版存储形态表,但没有决策记录,也没定两种库的写法一致性规则。

旧实现的教训很具体:它同时支持 SQLite 与 MySQL,方言差异靠 Float().with_variant(DOUBLE, "mysql") 这类逐处变体修补;时间字段既有 String(32) 存字符串的,也有 epoch 浮点的。差异不写成明文约定,就会在代码里散成几十处特判。

选项

选项 做法 代价 不选的理由
A 单机 SQLite + 服务端 PostgreSQL 一套方言无关 schema,两种引擎各一套驱动实现 要守住一组写法约定 选定
B 两端都用 PostgreSQL 单机版也装 PostgreSQL 单机从「装一个应用」变成「运维一个数据库」 工厂现场的单机部署不接受
C 两端都用 SQLite 服务端也用 SQLite 并发写、在线备份、复制、细粒度权限都受限 多用户共享场景撑不住
D 单机 SQLite + 服务端 MySQL 与旧实现一致,沿用经验 旧实现的 MySQL 是为对接 jms_mes,属外部约束 新平台没有这个包袱;PostgreSQL 在 JSONB、并发写与在线备份上更省事

结论

选 A。同时定下七条写法约定——它们是「一套 schema 跑两种库」的前提,不是风格偏好:

# 约定 原因
1 一套方言无关 schema + 一个迁移工具 两边各写一套必然漂移,等联调才发现对不上
2 时间统一存整数毫秒 避开各库原生时间类型的时区语义差异;旧实现已有两种写法混用
3 主键由应用侧生成(ULID),不用自增 SQLite 与 PostgreSQL 的自增与序列语义不同
4 JSON 列统一按 JSON 处理:PostgreSQL 落 JSONB,SQLite 落 TEXT 上层只认 JSON,不写库特有的查询与字面量
5 金额与工时用整数最小单位 避免小数类型在不同引擎上的精度与舍入差异
6 不用触发器、存储过程、数据库专属函数 逻辑留在代码里,两种引擎行为才一致
7 会话消息不落结构化库 单机走 JSONL、服务端走 PostgreSQL,两种实现同一个 SessionStore 接口,上层代码共用

访问层用 Drizzle ORM:纯 TypeScript,schema 与迁移一体,方言由 schema 定义区分。不选 Prisma——它的查询与迁移依赖额外二进制,对 Electron 打包与离线升级是负担。

存储落点:

数据 单机版 远端服务器版
会话消息 JSONL(~/.aps/sessions/<项目>/…) PostgreSQL
会话索引(列表、搜索、未读) 本地 SQLite PostgreSQL
执行、审批、审计 本地 SQLite PostgreSQL
附件与产物 本地目录,只存引用 对象存储 + 元数据表
导出与交换 JSONL JSONL

单机版的 SQLite 文件放在应用数据目录,与会话 JSONL 同级,备份即拷贝目录。

影响

  • 对代码:server/core 的持久层按七条约定写;schema 与迁移集中在 server/core/storage,本地与远端两套驱动实现同一个 SessionStore 接口
  • 对任务:A1.1 脚手架按此建存储目录与迁移配置;A3 数据层两套实现一起写,不先写一套再补;B2 拆分后 APS 侧的库由 APS 自己选,平台不干预
  • 对文档:《实施设计》1.4 的存储形态表以此为准,补上 SQLite 文件路径与主键、时间约定;改动随 A0.10 文档入库一起做,避免覆盖飞书文档正文
  • 不这么做的后果:两端 schema 各自演进,差异在联调时集中爆发;时间与主键的差异会散进业务代码,改一次要全量回归

遗留

  • SQLite 驱动的最终确认:A1.1 脚手架时实测 better-sqlite3 与 Node 内置 node:sqlite,选不需原生重编译的那个;若 Drizzle 与 Node ≥ 22.19 或引擎子进程模型冲突,退到 Kysely + 手写迁移,代价是改一份 schema 定义
  • 单机版的备份与导出入口:A6 界面设计时定,ADR-0003 的「备份与排查直接用文件」规则要求不依赖数据库工具
  • APS 侧数据库选型:归 APS,B2 只要求两侧库可独立启动