mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-06 15:46:37 +08:00
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:
@@ -222,21 +222,24 @@ func (Order) TableName() string {
|
||||
package order
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"github.com/Rain-kl/Wavelet/core"
|
||||
"embed"
|
||||
"github.com/Rain-kl/Wavelet/core"
|
||||
)
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var orderMigrations embed.FS
|
||||
|
||||
func (p *Plugin) Apply(ctx *core.Context) error {
|
||||
// 注册本插件的专属迁移(系统启动时自动按版本号执行)
|
||||
ctx.Migrations().Register("order", orderMigrations)
|
||||
return nil
|
||||
// 注册本插件的专属迁移(系统启动时自动按版本号执行)
|
||||
ctx.Migrations().Register("order", orderMigrations)
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
#### SQL 迁移脚本规范 (`plugins/order/migrations/20260827000001_create_orders_table.sql`):
|
||||
#### SQL 迁移脚本规范 (`plugins/order/migrations/00001_initial.sql`):
|
||||
|
||||
每个插件只需维护一个 `00001_initial.sql`,包含该插件的全部建表语句与种子数据。
|
||||
|
||||
```sql
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
@@ -246,10 +249,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);
|
||||
|
||||
-- 种子数据(使用 ON CONFLICT DO NOTHING 保证幂等)
|
||||
INSERT INTO w_orders (id, user_id, amount, status, created_at, updated_at)
|
||||
VALUES ('init_001', 'system', 0, 'completed', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
@@ -258,6 +265,25 @@ DROP TABLE IF EXISTS w_orders;
|
||||
-- +goose StatementEnd
|
||||
```
|
||||
|
||||
#### 版本管理机制
|
||||
|
||||
所有插件共享一张 `w_schema_versions` 表,以 `plugin_id` 区分:
|
||||
|
||||
```
|
||||
w_schema_versions (plugin_id, version_id, applied_at)
|
||||
```
|
||||
|
||||
启动时,引擎遍历每个插件:
|
||||
1. 查询 `w_schema_versions WHERE plugin_id = 'order'` 获取当前最大版本号
|
||||
2. 扫描插件 `migrations/` 目录下的 `.sql` 文件
|
||||
3. 如果存在未应用的版本号 → 执行迁移
|
||||
4. 如果全部已应用 → 跳过
|
||||
|
||||
```sql
|
||||
-- 查看全局迁移状态
|
||||
SELECT * FROM w_schema_versions ORDER BY plugin_id, version_id;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 场景 10:如果有多个业务插件需要读写同一张表怎么办?
|
||||
|
||||
@@ -91,7 +91,9 @@ Wavelet 是面向未来 5 年生产级云原生与高并发业务中台的 **微
|
||||
3. **`core/contracts/` 契约隔离防线 (100% Pass)**:
|
||||
- 所有跨插件交互严格基于纯 Interface 和 DTO 定义(如 `contracts.DBService`, `contracts.AuthService`, `contracts.UserService`, `contracts.StorageService`),消除了 package 级别的强耦合。
|
||||
4. **`plugins/` 单所有者原则与数据迁移独立性 (100% Pass)**:
|
||||
- 每个业务插件自包含专有 `migrations/*.sql`,彻底消除单体大迁移目录合并冲突,杜绝 GORM AutoMigrate。
|
||||
- 每个业务插件自包含专有 `migrations/00001_initial.sql`,通过 `go:embed` 注册。
|
||||
- 所有插件共享 `w_schema_versions` 表,以 `plugin_id` 列区分版本,彻底消除单体大迁移目录合并冲突,杜绝 GORM AutoMigrate。
|
||||
- `pkg/migrator/` 全局迁移目录已物理删除,26 个全局 SQL 文件全部分配至对应插件。
|
||||
5. **并发与生命周期析构安全 (100% Pass)**:
|
||||
- 全局遵循 LIFO (后进先出) Disposer 逆序优雅注销机制。在开启 `-race` 竞争检测下,所有事件并发广播、多协程注入与读写均 0 数据竞争。
|
||||
|
||||
@@ -136,12 +138,15 @@ Wavelet 是面向未来 5 年生产级云原生与高并发业务中台的 **微
|
||||
|
||||
| 数据表 | 唯一所有者插件 | 数据结构与仓储位置 | 跨插件交互方式 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `w_users`<br>`w_access_tokens` | `plugins/domain/user` | `models.go`<br>`repository.go` | `core/contracts.UserService`<br>`contracts.UserDTO` |
|
||||
| `w_auth_sources`<br>`w_external_accounts`<br>`w_passkeys`<br>`w_oauth_states` | `plugins/domain/auth` | `models.go`<br>`repository.go` | `core/contracts.AuthService`<br>`contracts.AuthRegistry` |
|
||||
| `w_users` | `plugins/domain/user` | `models.go`<br>`repository.go` | `core/contracts.UserService`<br>`contracts.UserDTO` |
|
||||
| `w_auth_sources`<br>`w_external_accounts`<br>`w_access_tokens` | `plugins/domain/auth` | `models.go`<br>`service.go` | `core/contracts.AuthService`<br>`contracts.AuthRegistry` |
|
||||
| `w_uploads`<br>`w_upload_stats` | `plugins/domain/upload` | `models/models.go`<br>`repository/repository.go` | `core/contracts.StorageService`<br>`upload.Ingest` 流水线 |
|
||||
| `w_system_configs`<br>`w_templates` | `plugins/domain/admin` | `models.go`<br>`repository.go` | `ctx.Settings()` / `contracts.ConfigService`<br>Redis Pub/Sub 广播 |
|
||||
| `w_message_channels`<br>`w_message_bindings`<br>`w_message_pairing_codes`<br>`w_push_events`<br>`w_push_channels`<br>`w_push_histories` | `plugins/domain/message_gateway` | `models.go`<br>`repository.go` | `EventBus` 强类型事件广播订阅 |
|
||||
| `w_task_executions`<br>`w_schedules` | `plugins/drivers/driver_asynq_*` | `types.go`<br>`schedule.go` | `ctx.Task()` 与 `ctx.Schedule()` 扩展点 |
|
||||
| `w_user_access_logs` | `plugins/domain/risk_control` | `logstore/` | `logstore` 门面<br>ClickHouse PG/SQLite 回落 |
|
||||
| `w_schedules` | `plugins/drivers/driver_asynq_cron` | `schedule.go` | `ctx.Schedule()` 扩展点 |
|
||||
| `w_task_executions` | `plugins/drivers/driver_asynq_worker` | `types.go`<br>`executor.go` | `ctx.Task()` 扩展点 |
|
||||
| `w_schema_versions` | **系统内部** | `cmd/app.go` sharedStore | 自动管理,不归属于任何插件 |
|
||||
|
||||
### 5.3 架构防线与单向依赖保障
|
||||
1. **测试脚手架绝对解耦**:底层通用的 `pkg/testhelper` 严禁反向引用任何上层业务插件。`testhelper` 维护轻量自包含的测试表脚手架,彻底杜绝包导入循环(Import Cycle)。
|
||||
|
||||
@@ -222,21 +222,24 @@ func (Order) TableName() string {
|
||||
package order
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"github.com/Rain-kl/Wavelet/core"
|
||||
"embed"
|
||||
"github.com/Rain-kl/Wavelet/core"
|
||||
)
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var orderMigrations embed.FS
|
||||
|
||||
func (p *Plugin) Apply(ctx *core.Context) error {
|
||||
// 注册本插件的专属迁移(系统启动时自动按版本号执行)
|
||||
ctx.Migrations().Register("order", orderMigrations)
|
||||
return nil
|
||||
// 注册本插件的专属迁移(系统启动时自动按版本号执行)
|
||||
ctx.Migrations().Register("order", orderMigrations)
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
#### SQL 迁移脚本规范 (`plugins/order/migrations/20260827000001_create_orders_table.sql`):
|
||||
#### SQL 迁移脚本规范 (`plugins/order/migrations/00001_initial.sql`):
|
||||
|
||||
每个插件只需维护一个 `00001_initial.sql`,包含该插件的全部建表语句与种子数据。
|
||||
|
||||
```sql
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
@@ -246,10 +249,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);
|
||||
|
||||
-- 种子数据(使用 ON CONFLICT DO NOTHING 保证幂等)
|
||||
INSERT INTO w_orders (id, user_id, amount, status, created_at, updated_at)
|
||||
VALUES ('init_001', 'system', 0, 'completed', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
@@ -258,6 +265,25 @@ DROP TABLE IF EXISTS w_orders;
|
||||
-- +goose StatementEnd
|
||||
```
|
||||
|
||||
#### 版本管理机制
|
||||
|
||||
所有插件共享一张 `w_schema_versions` 表,以 `plugin_id` 区分:
|
||||
|
||||
```
|
||||
w_schema_versions (plugin_id, version_id, applied_at)
|
||||
```
|
||||
|
||||
启动时,引擎遍历每个插件:
|
||||
1. 查询 `w_schema_versions WHERE plugin_id = 'order'` 获取当前最大版本号
|
||||
2. 扫描插件 `migrations/` 目录下的 `.sql` 文件
|
||||
3. 如果存在未应用的版本号 → 执行迁移
|
||||
4. 如果全部已应用 → 跳过
|
||||
|
||||
```sql
|
||||
-- 查看全局迁移状态
|
||||
SELECT * FROM w_schema_versions ORDER BY plugin_id, version_id;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 场景 10:如果有多个业务插件需要读写同一张表怎么办?
|
||||
|
||||
@@ -123,13 +123,30 @@ type RouterExtension interface {
|
||||
|
||||
### 4.2 数据迁移扩展 (`ctx.Migrations()`)
|
||||
每个插件通过 Go 内置 `embed.FS` 打包专属的 Goose SQL 文件,彻底消除单体大迁移目录的合并冲突:
|
||||
|
||||
```go
|
||||
type MigrationExtension interface {
|
||||
// Register 注册插件专属的 SQL 迁移文件系统
|
||||
Register(pluginID string, fs embed.FS)
|
||||
Register(pluginID string, fsys fs.FS, dir ...string)
|
||||
}
|
||||
```
|
||||
|
||||
**版本隔离机制**:所有插件共享一张 `w_schema_versions` 表,以 `plugin_id` 列区分。运行时引擎(`gooseEngine`)实现 `goosedb.Store` 接口,对该表执行 `plugin_id` 限定的 CRUD 操作,确保各插件的版本互不干扰。
|
||||
|
||||
```sql
|
||||
w_schema_versions (
|
||||
plugin_id VARCHAR(64) NOT NULL,
|
||||
version_id BIGINT NOT NULL,
|
||||
applied_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (plugin_id, version_id)
|
||||
)
|
||||
```
|
||||
|
||||
**启动流程**:
|
||||
1. `ApplyPlugins()` 阶段:各插件调用 `ctx.Migrations().Register("order", embedFS)` 收集迁移
|
||||
2. `RunMigrations()` 阶段:引擎遍历所有 `entries`,为每个插件创建 `goose.NewProvider(dialect, sqlDB, entry.FS, goose.WithStore(store))`
|
||||
3. `provider.Up()` 查询 `w_schema_versions WHERE plugin_id = 'order'` 决定版本,执行增量迁移
|
||||
|
||||
### 4.3 异步任务与定时调度扩展 (`ctx.Task()` & `ctx.Schedule()`)
|
||||
```go
|
||||
type TaskExtension interface {
|
||||
@@ -211,8 +228,13 @@ CLI 命令仅作为**切面激活器 (Target Selector)**,业务插件无需感
|
||||
1. App Bootstrap: 加载所有已配置插件并构建 Context
|
||||
↓
|
||||
2. Apply Phase: 执行所有 plugin.Apply(ctx),收集路由、任务、调度与迁移
|
||||
└─ 各插件调用 ctx.Migrations().Register("auth", authMigrations) 等
|
||||
↓
|
||||
3. Migration Engine: 统一执行所有已激活插件的 Goose SQL 迁移
|
||||
3. Migration Engine: 遍历所有 entries,逐插件创建 Goose Provider 执行迁移
|
||||
└─ 每个插件使用独立的 sharedStore(pluginID),共享同一张 w_schema_versions 表
|
||||
└─ provider.Up() 检查 w_schema_versions WHERE plugin_id = 'auth'
|
||||
└─ 未执行过 → 执行 00001_initial.sql → INSERT 版本记录
|
||||
└─ 已执行过 → 跳过
|
||||
↓
|
||||
4. Profile Dispatch:
|
||||
- "api": 激活 DriverTypeHTTP 驱动 (Gin.ListenAndServe)
|
||||
|
||||
Reference in New Issue
Block a user