docs(migration): update docs and skills for new migration architecture

Update all relevant documentation and skills to reflect:
- w_schema_versions shared version table with plugin_id discriminator
- Single 00001_initial.sql per plugin (merged from multi-file approach)
- gooseEngine uses goose.NewProvider with goose.WithStore(sharedStore)
- pkg/migrator deleted, all 26 global SQL files moved to per-plugin
- DDL/DML single-file approach (merged seed + schema)

Files updated:
- .agents/skills/database-migration/SKILL.md (full rewrite)
- docs/WAVELET_WHITE_PAPER.md (table matrix + migration check)
- docs/WAVELET_DEVELOPER_GUIDE.md (scenario 9)
- docs/superpowers/specs/2026-08-27-cordis-plugin-architecture-design.md
- docs/superpowers/specs/2026-08-27-cordis-downstream-developer-guide.md
This commit is contained in:
ryan
2026-08-28 12:45:58 +08:00
parent 9bd012a271
commit 33b38f8687
5 changed files with 193 additions and 36 deletions
+92 -14
View File
@@ -19,8 +19,7 @@ plugins/domain/order/
├── plugin.go
├── models.go
└── migrations/
├── 20260827000001_create_orders_table.sql
└── 20260827000002_add_order_discount_column.sql
└── 00001_initial.sql ← 每个插件仅一个初始迁移文件
```
---
@@ -47,7 +46,9 @@ func (p *Plugin) Apply(ctx *core.Context) error {
}
```
### 步骤 2:编写 Goose SQL 脚本 (`migrations/YYYYMMDDNNNN_name.sql`)
### 步骤 2:编写 Goose SQL 脚本 (`migrations/00001_initial.sql`)
每个插件只需维护一个 `00001_initial.sql`,包含其全部建表语句与种子数据。
```sql
-- +goose Up
@@ -58,10 +59,14 @@ CREATE TABLE IF NOT EXISTS w_orders (
amount BIGINT NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMPTZ
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_w_orders_user_id ON w_orders(user_id);
-- 种子数据
INSERT INTO w_orders (id, user_id, amount, status)
VALUES ('init_001', 'system', 0, 'completed')
ON CONFLICT (id) DO NOTHING;
-- +goose StatementEnd
-- +goose Down
@@ -72,39 +77,112 @@ DROP TABLE IF EXISTS w_orders;
---
## 3. 核心设计与防线原则 (Guardrails)
## 3. 版本管理与升级机制
### 3.1 版本表结构
所有插件共享一张 `w_schema_versions` 表,以 `plugin_id` 为区分:
```sql
w_schema_versions (
plugin_id VARCHAR(64) NOT NULL, -- 如 "auth", "user", "admin"
version_id BIGINT NOT NULL, -- 迁移文件版本号 (00001 → 1)
applied_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (plugin_id, version_id)
)
```
### 3.2 升级判定逻辑
启动时,`gooseEngine` 遍历每个已注册的插件:
```
for each plugin:
1. 查询 w_schema_versions WHERE plugin_id = 'auth'
2. 获取该插件的最大 version_id
3. 读取插件 migration/ 目录下的所有 .sql 文件
4. 如果存在 version_id 更大的文件 → 执行升级
5. 如果全部已应用 → 跳过
```
### 3.3 什么情况下升级?
| 场景 | 例子 | 是否升级 |
|------|------|---------|
| 首次部署,插件第一次运行 | auth 插件,表不存在 | ✅ 执行 `00001_initial.sql` |
| 第二次启动,无变化 | 文件未变,版本已记录 | ❌ 跳过 |
| 追加新迁移文件 | 新增 `00002_add_index.sql` | ✅ 执行 `00002_*` |
| 移除一个插件 | 该插件不再注册 | ❌ 其记录在表中被忽略 |
| 新增一个插件 | 新插件有 `00001_initial.sql` | ✅ 执行 |
### 3.4 查看全局迁移状态
```sql
SELECT * FROM w_schema_versions ORDER BY plugin_id, version_id;
```
输出示例:
```
plugin_id | version_id | applied_at
---------------------+------------+---------------------------
admin | 1 | 2026-08-28 10:00:00+00
auth | 1 | 2026-08-28 10:00:00+00
user | 1 | 2026-08-28 10:00:00+00
upload | 1 | 2026-08-28 10:00:00+00
```
---
## 4. 核心设计与防线原则 (Guardrails)
1. **表单一所有者原则 (Single Owner Principle)**:
- 每张数据表归属且仅归属于一个所有者插件(如 `w_orders` 归 `order` 插件)。
- **严禁**插件 B 跨包编写 SQL 直接读写插件 A 拥有的表;必须通过插件 A 暴露的 `contracts` 接口或事件总线进行交互。
2. **表名前缀规范**:
- 所有表名必须带有前缀(如 `w_orders`、`w_auth_users`),杜绝跨插件表名冲突。
3. **DDL 与 DML 分离**:
- 表结构变更(DDL)与初始数据插入(DML/Seed)必须分成两个独立的递增版本 SQL 文件。
3. **单文件初始迁移**:
- 每个插件只维护一个 `00001_initial.sql`,包含该插件所有表的建表语句与初始种子数据。
- 未来如需追加 DDL,新增 `00002_xxx.sql`,Goose 会根据 `w_schema_versions` 判断增量执行。
4. **禁止物理外键**:
- 关系字段统一显式建立单列或联合索引,禁止在数据库中创建物理外键约束。
5. **双方言兼容性(PostgreSQL & SQLite)**:
- 自增主键:PG 用 `BIGSERIAL`,SQLite 用 `INTEGER PRIMARY KEY AUTOINCREMENT`。
- 时间类型:PG 用 `TIMESTAMPTZ`,SQLite 用 `DATETIME`。
- JSON 类型:PG 用 `JSONB`,SQLite 用 `JSON` 或 `TEXT`。
6. **定时调度插入规范**:
- 若迁移中包含初始定时任务插入(`schedules` 表),绝对不能硬编码 `id`,必须依靠数据库自增分配。
6. **幂等性要求**:
- 所有 `CREATE TABLE` 必须使用 `IF NOT EXISTS`。
- 所有 `INSERT` 种子数据必须使用 `ON CONFLICT DO NOTHING`。
- 所有 `ALTER TABLE ADD COLUMN` 必须使用 `IF NOT EXISTS`(如果数据库方言支持)。
---
## 4. ClickHouse 分析库迁移规则 (辅助 OLAP)
## 5. ClickHouse 分析库迁移规则 (辅助 OLAP)
ClickHouse 作为辅助 OLAP 分析存储,采用独立迁移通道:
- 迁移文件位于专属目录(仅单方言 DDL,不创建 SQLite 镜像)。
- 日志/分析用途表必须同时在关系型主库建回落表并接入 `logstore`。
- 迁移文件位于专属目录 `migrations-clickhouse/`(仅单方言 DDL,不创建 SQLite 镜像)。
- 日志/分析用途表必须同时在关系型主库建回落表并接入 `logstore` 门面。
- 分析表高频写入统一接入 `batchwriter` 进行异步批量刷盘。
---
## 5. 质量与验证门禁
## 6. 质量与验证门禁
```bash
make format
make code-check
go test ./plugins/...
```
验证迁移注册完整性:
```bash
# 检查每个有 migrations/ 目录的插件是否同时有 go:embed + Register()
grep -rn 'go:embed.*migrations' plugins/domain/*/plugin.go plugins/drivers/*/plugin.go
grep -rn 'Migrations()\.Register' plugins/domain/*/plugin.go plugins/drivers/*/plugin.go
```