50 lines
3.1 KiB
Markdown
50 lines
3.1 KiB
Markdown
# ADR-003: 使用本地 JSON 文件作为数据层(非 SQLite)
|
||
|
||
## Status: Accepted (2026-08-26)
|
||
|
||
## Background
|
||
|
||
「备考中枢」是**个人单用户、自用型**工具,数据是用户的长期备考资产(题库/错题本/作答记录/模考/申论/计划/统计)。用户已明确选 **JSON 文件存储**(非 SQLite)。需要数据可读、可迁移、可备份,并支持 Docker volume 挂载持久化。
|
||
|
||
## Decision
|
||
|
||
采用**本地 JSON 文件**作为数据层:每实体一个 JSON 文件(`questions.json` / `wrong-questions.json` / `mock-exams.json` / `tasks.json` / `shenlun.json` / `answer-records.json` / `stats.json` / `settings.json` / `meta.json`),存放在可配置数据目录 `APP_DATA_DIR`(Docker volume 挂载点)。
|
||
|
||
核心设计(见 architecture.md §六):
|
||
1. **每实体一个文件 + 顶层索引结构**:`{ version, updatedAt, index: {id→下标}, items: [] }`。
|
||
2. **数据形状**:全部由 `packages/shared/src/schemas/*.schema.ts` 的 zod schema 定义,经 `z.infer` 产出强类型,无 any;状态用字符串字面量联合。
|
||
3. **原子写入**:临时文件 + `fs.rename` 原子替换(同文件系统 rename 原子)。
|
||
4. **写队列串行**:单进程内所有写操作入 Promise 队列逐条执行,杜绝并发写坏文件。
|
||
5. **防抖批写**:高频写(刷题每道题)合并落盘,内存立即生效。
|
||
6. **zod 运行时校验**:文件是可变外部输入,读入后过 schema 收窄为强类型实体。
|
||
7. **统计用内存聚合 + 快照**:不每次全量扫描文件。
|
||
8. **数据资产能力**:预留一键导出 JSON/CSV + 备份/还原接口。
|
||
|
||
### 为什么对个人工具合理(正面)
|
||
| 维度 | 说明 |
|
||
|---|---|
|
||
| 单机低运维 | 零安装、零连接、零迁移脚本,Docker 一个容器即用 |
|
||
| 数据资产可读可迁移 | 纯文件可 cat/jq 直读、可 git 跟踪、可一键备份,跨设备无锁定 |
|
||
| 开发快 | 无 ORM/迁移/连接池,`data/store.ts` 单点 fs 读写,MVP 工作量最小 |
|
||
| DevOps 极简 | Docker volume 挂载即持久化,备份即拷贝目录 |
|
||
|
||
### 代价与规避(已正视)
|
||
| 代价 | 规避 |
|
||
|---|---|
|
||
| 并发写 | 写队列串行(互斥) |
|
||
| 写入非原子 | 临时文件 + rename 原子替换 |
|
||
| 无事务 | 单文件原子 + 组合操作分解为最小写单元 + 统计可重算(不强一致) |
|
||
| 体积增长 | 按需惰性加载 + 作答记录归档分片(按月切文件) |
|
||
| 统计 O(n) | 内存聚合 + 快照持久化 |
|
||
|
||
## Consequences
|
||
|
||
- 正面:数据资产感强、运维极简、开发快、零基础设施成本。
|
||
- 负面:无事务、无查询索引、并发写入需手动防护、数据量大后需归档分片。
|
||
- 权衡:对个人单用户量级(年 1-2 万题,约 10-20 MB JSON)完全无压力。**本期不需要 SQLite/PostgreSQL**。若未来出现多设备协作 / 超百万条 / 复杂关系查询,再以新 ADR 追加迁移(本期不预留迁移负担)。
|
||
|
||
## Related ADRs
|
||
- ADR-001(Express 5)
|
||
- ADR-004(zod 运行时校验——数据来自 JSON 文件的关键保障)
|
||
- ADR-005(monorepo)
|