Skip to content

Commit b474ce8

Browse files
authored
docs: backend/AGENTS.md — 改 DB schema 前必查全新贡献者能否启动 (#45)
* docs: 加 backend/AGENTS.md——改 DB schema 前必查全新贡献者能否启动 把 M0 踩的坑固化成硬约束:三处 schema(schema.sql / docker/init-db/init.sql / test-schema.sql)必须同步(CREATE TABLE IF NOT EXISTS 补不上已存在表的缺列)、 schema.sql 的 DML 必须幂等、不许把 SPRING_SQL_INIT_MODE 改成 never、不许直接 敲生产库;附一次性 throwaway 容器跑 init→schema 两遍的验证配方,杜绝"我这能跑" 式自证。 * docs: 加 backend/CLAUDE.md 转发到 AGENTS.md,让 Claude Code 在全新 clone 也吃到 DB 守卫
1 parent 6ceb739 commit b474ce8

2 files changed

Lines changed: 69 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# AGENTS.md — backend 给 AI / contributor 的硬约束
2+
3+
配合根 `CLAUDE.md`(三仓库架构总览)与 `SECURITY.md`(INV-001.. 安全不变量)阅读。
4+
这里只写**已经踩过、且对新人是毁灭性打击**的反模式。
5+
6+
## 改数据库前,先问"全新的人还能启动吗"
7+
8+
改 DB schema(新增表/列、写回填 DML、动 `schema.sql`)时,**默认受害者不是你**——
9+
你的服务器/本地库早就有那些表和列,任何缺列/缺表的问题在你这儿都看不见。真正
10+
会被打穿的是**第一次 clone 下来、`docker compose up` 的新贡献者**。改动前必须逐条过:
11+
12+
1. **三处 schema 必须同步改**,缺一即分裂:
13+
- `src/main/resources/schema.sql` — 唯一真相源,后端启动时执行(`SPRING_SQL_INIT_MODE=always`
14+
- `docker/init-db/init.sql`**只在数据卷首次创建时跑一次**,给全新 docker 贡献者建库
15+
- `src/test/resources/test-schema.sql` — H2(PostgreSQL MODE)测试镜像,`TIMESTAMPTZ→TIMESTAMP``JSONB→VARCHAR`
16+
17+
致命点:`CREATE TABLE IF NOT EXISTS`**已存在的表是 no-op,补不上缺的列**
18+
所以 `init.sql` 建的表若比 `schema.sql` 少列,schema.sql 永远修不好它——
19+
全新 docker 库会带着残缺表跑,登录 INSERT / 回填 SELECT 直接报
20+
`column "x" does not exist`。init.sql 的每张表列集必须与 schema.sql 逐列一致
21+
(这条正是 INV-004 `user_follows` 事故的教训扩展)。
22+
23+
2. **schema.sql 里的任何 DML 必须幂等**:它每次启动都重跑。回填用
24+
`INSERT ... ON CONFLICT DO NOTHING`,seed 用 `ON CONFLICT DO NOTHING/UPDATE`
25+
非幂等语句会在第二次启动 `spring.sql.init` 报错、后端拒绝启动。
26+
27+
3. **绝不建议把 `SPRING_SQL_INIT_MODE` 改成 `never`**。schema.sql 幂等,`always`
28+
是安全默认(`.env.example` 默认即是)。改 never 后:docker 卷已存在 → init.sql
29+
不再跑,mode=never → schema.sql 不跑,别人**新加的表在你本地既不建也不报**
30+
直到某个登录路径 500。曾经踩过,别把这条建议写回文档。
31+
32+
4. **不要把 DDL 直接敲进正在跑的(尤其生产)数据库**。把改动提交进上面三个 schema
33+
文件,让启动时 reconcile。核查生产只能只读(`to_regclass``SELECT`),
34+
写操作走代码 + 重启。
35+
36+
## DB 改动的验证配方(必须做,不能只信"我这能跑")
37+
38+
**一次性 throwaway 容器**模拟全新贡献者,跑完即弃,绝不碰真实库:
39+
40+
```bash
41+
docker rm -f ih-fresh-test 2>/dev/null
42+
docker run -d --name ih-fresh-test -e POSTGRES_USER=t -e POSTGRES_PASSWORD=t -e POSTGRES_DB=t \
43+
-v "$PWD/docker/init-db:/docker-entrypoint-initdb.d:ro" postgres:18-alpine
44+
# 等 init.sql 跑完(pg_isready 轮询),再把 schema.sql 拷进去执行两遍:
45+
docker cp src/main/resources/schema.sql ih-fresh-test:/tmp/schema.sql
46+
docker exec ih-fresh-test psql -U t -d t -v ON_ERROR_STOP=1 -q -f /tmp/schema.sql # 应 exit 0,无缺列崩溃
47+
docker exec ih-fresh-test psql -U t -d t -v ON_ERROR_STOP=1 -q -f /tmp/schema.sql # 再跑一遍验证幂等
48+
docker rm -f ih-fresh-test
49+
```
50+
51+
通过标准:两遍都 `exit 0`、无 `does not exist`、目标表/列存在。**只在既有服务器库上
52+
测不算数**——它已有那些列,恰好把 init.sql 的分裂藏住。
53+
54+
## 备份
55+
56+
改数据前确认 `involution-pg-backup` 日备在跑(`@daily`,保留 30 天 + 8 周 + 12 月)。
57+
破坏性迁移留回滚兜底;`docker compose down -v` 只在**本地**开发库用(会清空数据)。

CLAUDE.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# CLAUDE.md — backend
2+
3+
Claude Code 加载本文件;其它 agent 加载 `AGENTS.md`。两者的硬约束以
4+
**`AGENTS.md` 为准**,本文件只做转发,避免双份维护漂移。
5+
6+
**动手前必读:**
7+
8+
- `AGENTS.md` — 改 DB schema 前的三处同步 + 全新贡献者启动检查等硬约束
9+
- `SECURITY.md` — INV-001.. 安全不变量(改 auth/角色/密码/关注/端口前必读)
10+
-`../CLAUDE.md`(本机存在时)— 三仓库架构总览与跨服务边界
11+
12+
backend 架构目前在根 `CLAUDE.md` 里成篇覆盖。

0 commit comments

Comments
 (0)