mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-06 23:56:37 +08:00
merge(wavelet): sync upstream changes
This commit is contained in:
@@ -1,332 +0,0 @@
|
||||
# Wavelet Cordis 微内核与全插件化改造实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 将 Wavelet 架构重构为基于 Cordis 理念的微内核与全插件化架构,支持一切能力插件化、自包含数据迁移、多运行切面(API/Worker/Schedule/All)与下游极简二开扩展。
|
||||
|
||||
**Architecture:**
|
||||
- **Core (`core/`)**: 纯净微内核,提供 Context 服务总线、泛型 IoC 容器(`Provide/Inject/Using`)、强类型 EventBus、生命周期状态机与 6 大扩展点协议,零外部业务依赖。
|
||||
- **Drivers (`plugins/drivers/`)**: Gin HTTP Server、Asynq Worker、Asynq Scheduler 封装为标准运行时驱动插件。
|
||||
- **Infra Plugins (`plugins/infra/`)**: 数据库(GORM/DBResolver)、三层缓存(RAM/Redis/PubSub)、日志(Zap/Otel)、对象存储插件化。
|
||||
- **Domain Plugins (`plugins/domain/`)**: Auth、User、MessageGateway、RiskControl、Admin 模块拆分为扁平自包含插件,自带独立 Goose 迁移。
|
||||
|
||||
**Tech Stack:** Go 1.25+, Gin, GORM, Asynq, Redis, Zap, OpenTelemetry, Goose, Viper, Cobra.
|
||||
|
||||
## Global Constraints
|
||||
- 保持 `core/` 绝对纯净,禁止 import Gin、GORM、Asynq 或具体业务包。
|
||||
- 插件之间严禁相互跨包 import 具体实现,跨插件交互一律通过 `core/contracts` 接口或 `ctx.Events()` 事件总线。
|
||||
- 严格遵循 Go 单元测试规范,测试临时目录统一使用 `t.TempDir()`,测试覆盖率严格达标。
|
||||
- 完成每个 Task 后必须确保代码能通过 `go build ./...` 与 `go test ./...` 检验并及时提交 Git。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 微内核基础契约与泛型 Context 服务总线 (`core/`)
|
||||
|
||||
**Files:**
|
||||
- Create: `core/types.go`
|
||||
- Create: `core/manifest.go`
|
||||
- Create: `core/container.go`
|
||||
- Create: `core/context.go`
|
||||
- Test: `core/context_test.go`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `core.Plugin`, `core.Manifest`, `core.Context`, `core.Provide[T]`, `core.Inject[T]`, `core.Using[T]`
|
||||
|
||||
- [ ] **Step 1: 编写 Context 与 IoC 容器的失败测试**
|
||||
|
||||
```go
|
||||
// core/context_test.go
|
||||
package core_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/Rain-kl/Wavelet/core"
|
||||
)
|
||||
|
||||
type SampleService interface {
|
||||
Greet(name string) string
|
||||
}
|
||||
|
||||
type sampleServiceImpl struct{}
|
||||
|
||||
func (s *sampleServiceImpl) Greet(name string) string {
|
||||
return "Hello, " + name
|
||||
}
|
||||
|
||||
func TestContextProvideAndInject(t *testing.T) {
|
||||
ctx := core.NewContext(context.Background())
|
||||
core.Provide[SampleService](ctx, &sampleServiceImpl{})
|
||||
|
||||
svc, err := core.Inject[SampleService](ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Hello, Wavelet", svc.Greet("Wavelet"))
|
||||
}
|
||||
|
||||
func TestContextUsing(t *testing.T) {
|
||||
ctx := core.NewContext(context.Background())
|
||||
var called bool
|
||||
|
||||
err := core.Using(ctx, func(s SampleService) {
|
||||
called = true
|
||||
assert.Equal(t, "Hello, Cordis", s.Greet("Cordis"))
|
||||
})
|
||||
assert.Error(t, err, "service not ready yet")
|
||||
assert.False(t, called)
|
||||
|
||||
core.Provide[SampleService](ctx, &sampleServiceImpl{})
|
||||
err = core.Using(ctx, func(s SampleService) {
|
||||
called = true
|
||||
assert.Equal(t, "Hello, Cordis", s.Greet("Cordis"))
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, called)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行测试验证失败**
|
||||
|
||||
Run: `go test -v ./core`
|
||||
Expected: FAIL with compilation error (package not found).
|
||||
|
||||
- [ ] **Step 3: 实现 Core 核心接口与泛型容器**
|
||||
|
||||
编写 `core/types.go`、`core/manifest.go`、`core/container.go`、`core/context.go`,提供基于反射与类型推导的安全泛型服务存取、Scope 隔离与 Disposer 回调链。
|
||||
|
||||
- [ ] **Step 4: 运行测试验证通过**
|
||||
|
||||
Run: `go test -v ./core`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 5: 提交 Task 1 代码**
|
||||
|
||||
```bash
|
||||
git add core/
|
||||
git commit -m "feat(core): implement context service hub and generic ioc container"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 领域扩展点规范与强类型 EventBus (`core/extpoints/`, `core/events.go`)
|
||||
|
||||
**Files:**
|
||||
- Create: `core/events.go`
|
||||
- Create: `core/extpoints/router.go`
|
||||
- Create: `core/extpoints/migration.go`
|
||||
- Create: `core/extpoints/task.go`
|
||||
- Create: `core/extpoints/schedule.go`
|
||||
- Create: `core/extpoints/setting.go`
|
||||
- Test: `core/events_test.go`
|
||||
- Test: `core/extpoints/extpoints_test.go`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `core.Context`
|
||||
- Produces: `core.EventBus`, `core.RouterExtension`, `core.MigrationExtension`, `core.TaskExtension`, `core.ScheduleExtension`, `core.SettingExtension`
|
||||
|
||||
- [ ] **Step 1: 编写 EventBus 与扩展点测试用例**
|
||||
|
||||
```go
|
||||
// core/events_test.go
|
||||
package core_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/Rain-kl/Wavelet/core"
|
||||
)
|
||||
|
||||
type UserRegisteredEvent struct {
|
||||
UserID string
|
||||
}
|
||||
|
||||
func TestEventBusPublishSubscribe(t *testing.T) {
|
||||
bus := core.NewEventBus()
|
||||
var receivedID string
|
||||
|
||||
bus.On("user:registered", func(ctx context.Context, e UserRegisteredEvent) error {
|
||||
receivedID = e.UserID
|
||||
return nil
|
||||
})
|
||||
|
||||
err := bus.Emit(context.Background(), "user:registered", UserRegisteredEvent{UserID: "u_999"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "u_999", receivedID)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行测试验证失败**
|
||||
|
||||
Run: `go test -v ./core/...`
|
||||
Expected: FAIL
|
||||
|
||||
- [ ] **Step 3: 实现 EventBus 与 6 大扩展点适配器**
|
||||
|
||||
编写 `core/events.go` 及 `core/extpoints/` 下各个领域的挂载收集器(Router 注册收集、Goose embed.FS 聚合器、Task/Schedule 声明表、Setting 模式注册表)。
|
||||
|
||||
- [ ] **Step 4: 运行测试验证通过**
|
||||
|
||||
Run: `go test -v ./core/...`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 5: 提交 Task 2 代码**
|
||||
|
||||
```bash
|
||||
git add core/
|
||||
git commit -m "feat(core): add typed eventbus and domain extension points"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 运行时驱动插件下沉 (`plugins/drivers/`)
|
||||
|
||||
**Files:**
|
||||
- Create: `plugins/drivers/driver_http/plugin.go`
|
||||
- Create: `plugins/drivers/driver_asynq_worker/plugin.go`
|
||||
- Create: `plugins/drivers/driver_asynq_cron/plugin.go`
|
||||
- Test: `plugins/drivers/drivers_test.go`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `core.Plugin`, `core.Driver`, `core.Context`
|
||||
- Produces: `DriverTypeHTTP`, `DriverTypeWorker`, `DriverTypeScheduler`
|
||||
|
||||
- [ ] **Step 1: 编写 Driver 生命周期测试用例**
|
||||
|
||||
测试驱动在接收到 `Start(ctx)` 和 `Stop(ctx)` 信号时的平滑启动与退出状态。
|
||||
|
||||
- [ ] **Step 2: 编写 Driver 实现**
|
||||
|
||||
将 Gin HTTP Server、Asynq Worker Server、Asynq Scheduler 封装为标准 `core.Driver`,并在 `Apply(ctx)` 时挂载到 Context 驱动树。
|
||||
|
||||
- [ ] **Step 3: 运行驱动单元测试**
|
||||
|
||||
Run: `go test -v ./plugins/drivers/...`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 4: 提交 Task 3 代码**
|
||||
|
||||
```bash
|
||||
git add plugins/drivers/
|
||||
git commit -m "feat(plugins): implement runtime drivers for http, asynq worker, and cron"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 基础设施服务插件化 (`plugins/infra/`)
|
||||
|
||||
**Files:**
|
||||
- Create: `plugins/infra/database/plugin.go` (提供 GORM DBService)
|
||||
- Create: `plugins/infra/cache/plugin.go` (提供 RAM/Redis 三层缓存)
|
||||
- Create: `plugins/infra/logger/plugin.go` (提供 Zap/Otel 结构化日志)
|
||||
- Create: `plugins/infra/storage/plugin.go` (提供统一对象存储)
|
||||
- Test: `plugins/infra/infra_test.go`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `contracts.DBService`, `contracts.CacheService`, `contracts.LoggerService`, `contracts.StorageService`
|
||||
|
||||
- [ ] **Step 1: 编写基础设施插件注入与提取测试**
|
||||
- [ ] **Step 2: 实现 4 大基础设施插件并封装现有 pkg 与 infra 底座**
|
||||
- [ ] **Step 3: 运行基础设施测试验证**
|
||||
|
||||
Run: `go test -v ./plugins/infra/...`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 4: 提交 Task 4 代码**
|
||||
|
||||
```bash
|
||||
git add plugins/infra/
|
||||
git commit -m "feat(plugins): package database, cache, logger, and storage as infra plugins"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 业务领域插件化重构 (`plugins/domain/`)
|
||||
|
||||
**Files:**
|
||||
- Create: `plugins/domain/auth/` (认证、Session、Passkey、专属 migrations)
|
||||
- Create: `plugins/domain/user/` (用户资料、角色权限、专属 migrations)
|
||||
- Create: `plugins/domain/message_gateway/` (Bot网关、推送通道、Worker消费)
|
||||
- Create: `plugins/domain/risk_control/` (IP限流、风控中间件)
|
||||
- Create: `plugins/domain/admin/` (控制台、系统设置)
|
||||
- Test: `plugins/domain/domain_test.go`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `contracts.DBService`, `contracts.CacheService`, `contracts.LoggerService`
|
||||
- Produces: `contracts.AuthService`, `contracts.UserService`
|
||||
|
||||
- [ ] **Step 1: 编写 Auth 与 User 插件业务装配与独立迁移测试**
|
||||
- [ ] **Step 2: 将各业务模块迁移为扁平自包含插件,嵌入专属 Goose SQL 迁移**
|
||||
- [ ] **Step 3: 运行业务插件集成测试**
|
||||
|
||||
Run: `go test -v ./plugins/domain/...`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 4: 提交 Task 5 代码**
|
||||
|
||||
```bash
|
||||
git add plugins/domain/
|
||||
git commit -m "feat(plugins): migrate auth, user, message_gateway, risk_control, admin to domain plugins"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 统一装配入口与运行时切面分发器 (`core/app.go`, `cmd/`)
|
||||
|
||||
**Files:**
|
||||
- Create: `core/app.go`
|
||||
- Modify: `internal/cmd/root.go`
|
||||
- Modify: `internal/cmd/api.go`
|
||||
- Modify: `internal/cmd/worker.go`
|
||||
- Modify: `internal/cmd/scheduler.go`
|
||||
- Modify: `internal/cmd/all.go`
|
||||
- Test: `core/app_test.go`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `core.App`, `core.Plugin`, `core.Driver`
|
||||
- Produces: 统一 CLI 启动与优雅停机流程
|
||||
|
||||
- [ ] **Step 1: 编写 App 生命周期与 Profile 调度测试**
|
||||
- [ ] **Step 2: 实现 `core.App` 编排引擎,无缝接入 `wavelet api / worker / schedule / all`**
|
||||
- [ ] **Step 3: 运行启动与角色切面集成验证**
|
||||
|
||||
Run: `go test -v ./core -run TestAppProfileDispatch`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 4: 提交 Task 6 代码**
|
||||
|
||||
```bash
|
||||
git add core/ internal/cmd/
|
||||
git commit -m "feat(core): implement app profile lifecycle dispatcher and wire cli commands"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: 下游脚手架、自定义示例插件与端到端验证
|
||||
|
||||
**Files:**
|
||||
- Create: `downstream/custom_plugins/order/plugin.go`
|
||||
- Create: `downstream/main.go`
|
||||
- Create: `downstream/config.yaml`
|
||||
- Test: `downstream/e2e_test.go`
|
||||
|
||||
- [ ] **Step 1: 编写下游自定义业务插件并在下游 `main.go` 组装启动**
|
||||
- [ ] **Step 2: 执行全量 E2E 测试,验证数据迁移、HTTP 路由访问、Worker 任务消费与平滑停机**
|
||||
- [ ] **Step 3: 运行全局质量门禁检查**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
make test
|
||||
make code-check
|
||||
make format
|
||||
```
|
||||
Expected: 全部 PASS,0 lint 报错。
|
||||
|
||||
- [ ] **Step 4: 提交 Task 7 代码**
|
||||
|
||||
```bash
|
||||
git add downstream/
|
||||
git commit -m "feat(downstream): add starter scaffold, example custom plugin, and e2e tests"
|
||||
```
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
# Cordis Architecture Alignment & Refactoring Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Implement Cordis spatiotemporal composability (scoped revertible effects and reactive fiber lifecycle state machine) in `backend/core`, and eliminate cross-plugin direct imports in domain repositories.
|
||||
|
||||
**Architecture:**
|
||||
1. Build scoped extension proxies on `core.Context` that automatically attach unregister callbacks to `ctx.OnDispose` in LIFO order upon registration.
|
||||
2. Introduce `core/fiber.go` implementing the Fiber state machine (`PENDING -> LOADING -> ACTIVE -> UNLOADING -> DISPOSED`) with a reactive reconciler in `App`/`Container` ensuring dependency confluence.
|
||||
3. Clean up defensive boundaries in `backend/plugins/domain/user` by removing direct `database.DB(ctx)` imports in favor of `contracts.DBService`.
|
||||
|
||||
**Tech Stack:** Go 1.24+, GORM, Gin, Asynq, Cordis micro-kernel paradigm.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Strictly preserve `backend/pkg/util/` purity (no Gin/GORM imports).
|
||||
- Zero physically hardcoded temp directories in tests (use `t.TempDir()`).
|
||||
- All Go error returns and logging must adhere to project standards.
|
||||
- Follow Conventional Commits (`feat(core): ...`, `refactor(user): ...`).
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Scoped Revertible Effects for Core Extpoints
|
||||
|
||||
**Files:**
|
||||
- Create: `backend/core/scoped_extpoints.go`
|
||||
- Modify: `backend/core/context.go`
|
||||
- Modify: `backend/core/extpoints/task.go`
|
||||
- Modify: `backend/core/extpoints/schedule.go`
|
||||
- Modify: `backend/core/extpoints/setting.go`
|
||||
- Test: `backend/core/context_test.go`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `core.Context`, `extpoints.RouterExtension`, `extpoints.TaskExtension`, `extpoints.ScheduleExtension`, `extpoints.SettingExtension`, `core.EventBus`
|
||||
- Produces: Scoped extension methods on `Context` that automatically register LIFO disposers when routes, tasks, schedules, settings, and events are registered.
|
||||
|
||||
- [ ] **Step 1: Write the failing test for scoped extpoints automatic teardown**
|
||||
|
||||
In `backend/core/context_test.go`, add test cases verifying that registering routes, tasks, schedules, settings, and event listeners on a child context automatically registers unregister callbacks, and calling `childCtx.Dispose()` completely rolls them back:
|
||||
|
||||
```go
|
||||
func TestContext_ScopedExtpoints_RevertibleEffects(t *testing.T) {
|
||||
root := NewContext(context.Background())
|
||||
child := root.Fork()
|
||||
|
||||
// Register route, task, schedule, setting, event on child
|
||||
rd := child.Router().GET("/test-route", func() {})
|
||||
assert.Equal(t, 1, len(root.Router().Routes()))
|
||||
|
||||
child.Events().On("test:event", func() {})
|
||||
assert.Equal(t, 1, root.Events().Listeners("test:event"))
|
||||
|
||||
// Dispose child
|
||||
err := child.Dispose()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// All child effects should be revoked
|
||||
assert.Equal(t, 0, len(root.Router().Routes()))
|
||||
assert.Equal(t, 0, root.Events().Listeners("test:event"))
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `go test -v ./backend/core -run TestContext_ScopedExtpoints_RevertibleEffects`
|
||||
Expected: FAIL (because current `Router().GET()` does not bind unregistration to `child.OnDispose`).
|
||||
|
||||
- [ ] **Step 3: Implement Scoped Extpoints and Context bindings**
|
||||
|
||||
1. In `backend/core/extpoints/task.go`, ensure `Unregister(taskType string) bool` exists.
|
||||
2. In `backend/core/extpoints/schedule.go`, ensure `Unregister(name string) bool` exists.
|
||||
3. In `backend/core/extpoints/setting.go`, ensure `Unregister(key string) bool` exists.
|
||||
4. In `backend/core/scoped_extpoints.go` (or `context.go`), create scoped wrappers for `RouterExtension`, `TaskExtension`, `ScheduleExtension`, `SettingExtension` and `EventBus` that tie registrations to `ctx.OnDispose`.
|
||||
|
||||
- [ ] **Step 4: Run tests to verify they pass**
|
||||
|
||||
Run: `go test -v ./backend/core -run TestContext_ScopedExtpoints_RevertibleEffects`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/core/
|
||||
git commit -m "feat(core): implement scoped revertible effects for context extpoints"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Plugin Fiber State Machine and Reactive Coeffects (Confluence)
|
||||
|
||||
**Files:**
|
||||
- Create: `backend/core/fiber.go`
|
||||
- Create: `backend/core/fiber_test.go`
|
||||
- Modify: `backend/core/app.go`
|
||||
- Modify: `backend/core/types.go`
|
||||
- Modify: `backend/core/container.go`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `core.Plugin`, `core.Context`, `core.Container`
|
||||
- Produces: `core.DependentPlugin`, `core.Fiber`, `core.FiberState`, `App.Reconcile()`
|
||||
|
||||
- [ ] **Step 1: Write the failing test for Fiber state machine and out-of-order registration confluence**
|
||||
|
||||
In `backend/core/fiber_test.go`:
|
||||
|
||||
```go
|
||||
func TestFiber_ConfluenceAndReactiveActivation(t *testing.T) {
|
||||
app := NewApp()
|
||||
|
||||
// Plugin B depends on contracts.DBService, but is registered BEFORE DatabasePlugin (Plugin A)
|
||||
pluginB := &mockDependentPlugin{
|
||||
name: "plugin-b",
|
||||
deps: []reflect.Type{reflect.TypeFor[contracts.DBService]()},
|
||||
}
|
||||
pluginA := &mockDBPlugin{name: "database"}
|
||||
|
||||
app.Use(pluginB, pluginA)
|
||||
|
||||
err := app.Start(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify both plugins reached FiberActive state and B executed Apply successfully after A provided DBService
|
||||
assert.True(t, pluginB.applied)
|
||||
assert.True(t, pluginA.applied)
|
||||
|
||||
_ = app.Stop()
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `go test -v ./backend/core -run TestFiber_ConfluenceAndReactiveActivation`
|
||||
Expected: FAIL (because current `app.ApplyPlugins()` applies in static slice order without dependency reconciliation).
|
||||
|
||||
- [ ] **Step 3: Implement Fiber State Machine and Reconciler**
|
||||
|
||||
1. In `backend/core/types.go`, declare:
|
||||
```go
|
||||
type DependentPlugin interface {
|
||||
Plugin
|
||||
Inject() []reflect.Type
|
||||
}
|
||||
```
|
||||
2. In `backend/core/fiber.go`, implement `Fiber` with states (`FiberPending`, `FiberLoading`, `FiberActive`, `FiberUnloading`, `FiberDisposed`), child scoped context, and state transition methods.
|
||||
3. In `backend/core/app.go`, integrate Fibers into `App` and implement iterative dependency reconciliation during `ApplyPlugins` and on dynamic `Provide`.
|
||||
|
||||
- [ ] **Step 4: Run tests to verify they pass**
|
||||
|
||||
Run: `go test -v ./backend/core`
|
||||
Expected: ALL PASS
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/core/
|
||||
git commit -m "feat(core): implement plugin fiber state machine and reactive dependency reconciler"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Domain Plugin Isolation & Boundary Enforcement
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/plugins/domain/user/repository.go`
|
||||
- Modify: `backend/plugins/domain/user/service.go`
|
||||
- Modify: `backend/plugins/domain/user/handlers.go`
|
||||
- Modify: `backend/plugins/domain/user/plugin.go`
|
||||
- Test: `backend/plugins/domain/user/plugin_test.go`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `contracts.DBService` via `core.Inject` / `ctx.DB()`
|
||||
- Produces: Decoupled User repository without direct `Wavelet/plugins/infra/database` imports.
|
||||
|
||||
- [ ] **Step 1: Write/update test verifying User repository works with injected DBService**
|
||||
|
||||
In `backend/plugins/domain/user/plugin_test.go`, test user CRUD operations resolving `contracts.DBService` through Context.
|
||||
|
||||
- [ ] **Step 2: Run test to verify current state**
|
||||
|
||||
Run: `go test -v ./backend/plugins/domain/user/...`
|
||||
|
||||
- [ ] **Step 3: Refactor user repository to eliminate direct `plugins/infra/database` imports**
|
||||
|
||||
In `backend/plugins/domain/user/repository.go`:
|
||||
- Remove `import "Wavelet/plugins/infra/database"`.
|
||||
- Obtain `*gorm.DB` via `ctx` (e.g. from context using `contracts.DBService` or context value).
|
||||
|
||||
- [ ] **Step 4: Run tests to verify they pass**
|
||||
|
||||
Run: `go test -v ./backend/plugins/domain/user/...`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/plugins/domain/user/
|
||||
git commit -m "refactor(user): decouple repository from direct database infra import"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Full Suite Verification & Quality Gate
|
||||
|
||||
**Files:**
|
||||
- Entire repository
|
||||
|
||||
- [ ] **Step 1: Run all backend tests**
|
||||
|
||||
Run: `cd backend && go test -v ./...`
|
||||
Expected: ALL PASS
|
||||
|
||||
- [ ] **Step 2: Run code-check and format**
|
||||
|
||||
Run: `make code-check && make format`
|
||||
Expected: 0 lint errors, clean formatting.
|
||||
|
||||
- [ ] **Step 3: Commit any formatting or lint fixes**
|
||||
|
||||
```bash
|
||||
git commit -am "chore: format and verify code quality"
|
||||
```
|
||||
@@ -1,120 +0,0 @@
|
||||
# Cordis 架构重构实施计划 (Cordis Architecture Refactor Implementation Plan)
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 依据 Cordis 时空可组合性元框架,彻底消除 Wavelet 后端的包级静态单例、`init()` 隐式副作用建连以及跨插件私有实现依赖,实现微内核纯洁化与契约驱动解耦。
|
||||
|
||||
**Architecture:**
|
||||
1. 移除 `backend/core/context.go` 中的特权服务快捷方法(`DB()` / `Cache()`)。
|
||||
2. 将 `infra/database` 与 `infra/cache` 的连接初始化移至 `Plugin.Apply(ctx)`,并在 `ctx.OnDispose` 中注册 LIFO 逆操作(Close)。
|
||||
3. 重构全部 8 个 Domain 业务插件(`auth`、`user`、`admin`、`cap`、`message_gateway`、`risk_control`、`system`、`upload`),彻底斩断对 `infra/database`、`infra/cache` 及其他插件内部包的直接 import,统一面向 `contracts.DBService` / `contracts.CacheService`。
|
||||
4. 清除 `admin` 等插件的包级全局变量。
|
||||
|
||||
**Tech Stack:** Go 1.24+, GORM, Redis (go-redis/v9), Cordis micro-kernel, Goose migration.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- 严禁任何业务插件跨包 import `Wavelet/plugins/infra/database` 或 `Wavelet/plugins/infra/cache`。
|
||||
- 严禁跨插件 import 私有实现包(如 `admin` import `risk_control/logstore`)。
|
||||
- 保持 `backend/pkg/util/` 绝对纯净,禁止导入 Web/数据库框架。
|
||||
- 重构后必须确保 `go test ./...`、`make code-check` 与 `make format` 全部 0 错误通过。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 微内核纯洁化 (`backend/core/`)
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/core/context.go:240-260`
|
||||
- Test: `backend/core/context_test.go`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `core.Context`, `core.Inject`
|
||||
- Produces: 纯净无特权方法的 `core.Context`
|
||||
|
||||
- [ ] **Step 1: 编写/更新 Context 纯洁性测试**
|
||||
- [ ] **Step 2: 移除 `Context.DB()` 与 `Context.Cache()` 方法**
|
||||
- [ ] **Step 3: 运行 `go test ./backend/core/...` 验证通过**
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 基础设施插件生命周期可逆化 (`backend/plugins/infra/`)
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/plugins/infra/database/postgres.go`
|
||||
- Modify: `backend/plugins/infra/database/plugin.go`
|
||||
- Modify: `backend/plugins/infra/cache/redis.go`
|
||||
- Modify: `backend/plugins/infra/cache/plugin.go`
|
||||
- Test: `backend/plugins/infra/infra_test.go`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `core.Plugin`, `contracts.DBService`, `contracts.CacheService`
|
||||
- Produces: `contracts.DBService` 与 `contracts.CacheService`(带 `ctx.OnDispose` 逆操作)
|
||||
|
||||
- [ ] **Step 1: 移除 `infra/database` 中的 `func init()` 及全局 `var db`,在 `Plugin.Apply` 中建连并注册 `ctx.OnDispose(sqlDB.Close)`**
|
||||
- [ ] **Step 2: 移除 `infra/cache` 中的 `func init()` 及全局 `var Redis`,在 `Plugin.Apply` 中建连并注册 `ctx.OnDispose(client.Close)`**
|
||||
- [ ] **Step 3: 运行 `go test ./backend/plugins/infra/...` 验证通过**
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 核心 Domain 插件防线重塑(Auth & User 插件)
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/plugins/domain/auth/*`
|
||||
- Modify: `backend/plugins/domain/user/*`
|
||||
- Test: `backend/plugins/domain/auth/plugin_test.go`
|
||||
- Test: `backend/plugins/domain/user/plugin_test.go`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `contracts.DBService`, `contracts.CacheService`
|
||||
- Produces: `contracts.AuthService`, `contracts.UserService`
|
||||
|
||||
- [ ] **Step 1: 移除 `auth` 插件中对 `Wavelet/plugins/infra/database` 和 `cache` 的 import,改用插件持有的 `contracts.DBService` 与 `contracts.CacheService`**
|
||||
- [ ] **Step 2: 移除 `user` 插件中对 `Wavelet/plugins/infra/database` 和 `cache` 的 import,改用 `contracts.DBService` 与 `contracts.CacheService`**
|
||||
- [ ] **Step 3: 运行 `go test ./backend/plugins/domain/auth/... ./backend/plugins/domain/user/...` 验证通过**
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 业务 Domain 插件防线重塑(Cap, MessageGateway, RiskControl, System, Upload)
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/plugins/domain/cap/*`
|
||||
- Modify: `backend/plugins/domain/message_gateway/*`
|
||||
- Modify: `backend/plugins/domain/risk_control/*`
|
||||
- Modify: `backend/plugins/domain/system/*`
|
||||
- Modify: `backend/plugins/domain/upload/*`
|
||||
- Test: `backend/plugins/domain/domain_test.go`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `contracts.DBService`, `contracts.CacheService`
|
||||
|
||||
- [ ] **Step 1: 改造 `cap`、`message_gateway`、`risk_control`、`system`、`upload` 插件,移除所有 `infra/database` 和 `infra/cache` 的直接 import**
|
||||
- [ ] **Step 2: 统一各插件内部 Repository / Service 的 DB / Cache 获取途径**
|
||||
- [ ] **Step 3: 运行各插件单测验证通过**
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Admin 插件解耦与包级全局状态清除
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/plugins/domain/admin/*`
|
||||
- Test: `backend/plugins/domain/admin/plugin_test.go`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `contracts.DBService`, `contracts.CacheService`, `contracts.UserService`, `contracts.AuthService`, `ctx.Tasks()`
|
||||
|
||||
- [ ] **Step 1: 移除 `admin` 插件中对 `risk_control/logstore`、`driver_asynq_worker`、`infra/storage/diskcache` 等私有包的直接 import**
|
||||
- [ ] **Step 2: 清除 `admin/plugin.go` 中的 `globalUserSvc`、`globalAuthSvc`、`globalCoreCtx` 等包级变量**
|
||||
- [ ] **Step 3: 运行 `go test ./backend/plugins/domain/admin/...` 验证通过**
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 组装层对齐与全量质量门禁验证
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/cmd/app.go`
|
||||
- Modify: `backend/cmd/*`
|
||||
|
||||
- [ ] **Step 1: 检查并适配 `cmd/app.go` 及启动指令,确保 Goose 迁移与驱动正确接入新版 `DBService`**
|
||||
- [ ] **Step 2: 运行全局跨包 import 检查:`grep -r "Wavelet/plugins/infra/database" backend/plugins/domain/` 必须为空**
|
||||
- [ ] **Step 3: 运行全量单元测试与基准测试:`go test ./...`**
|
||||
- [ ] **Step 4: 运行质量门禁:`make code-check && make format`**
|
||||
@@ -1,326 +0,0 @@
|
||||
# 迁移脚本拆分执行计划
|
||||
|
||||
## 背景现状
|
||||
|
||||
| 维度 | 实际状态 |
|
||||
|---|---|
|
||||
| 总 SQL 文件 | 26 个全局 (`pkg/migrator/goose/`) + 5 个插件 (`plugins/domain/*/migrations/`) + 1 个 ClickHouse |
|
||||
| 实际运行的迁移 | **仅 26 个全局文件**(通过 `gooseEngine` → `pkg/migrator.Migrate()`) |
|
||||
| 插件注册的迁移 | 3 个 (`auth`, `user`, `message_gateway`) — 注册了但被 `gooseEngine` 丢弃 |
|
||||
| 有迁移文件但未注册的插件 | `admin`(2 个文件,0 个调用) |
|
||||
| 无迁移文件的插件 | `upload`, `risk_control`, `cap`, `driver_asynq_worker`, `driver_asynq_cron` |
|
||||
| ClickHouse 迁移 | 1 个文件 (`w_user_access_logs`),通过 `pkg/migrator.MigrateClickHouse()` 单独运行 |
|
||||
|
||||
## 表所有者映射
|
||||
|
||||
以下列表基于"单一所有者原则",每个表精确映射到一个插件:
|
||||
|
||||
| 表名 | 所有者插件 | 涉及全局迁移 |
|
||||
|---|---|---|
|
||||
| `w_users` | `domain/user` | 20260609 (create), 20260614 (seed system user) |
|
||||
| `w_access_tokens` | `domain/auth` | 20260609 (create), 20260610 (is_admin), 20260611 (rm last_used_at) |
|
||||
| `w_auth_sources` | `domain/auth` | 20260609 (create) |
|
||||
| `w_external_accounts` | `domain/auth` | 20260609 (create) |
|
||||
| `w_system_configs` | `domain/admin` | 20260609→20260611 (rename+seeds×7), 20260613 (TEXT), 20260816 (log_db) |
|
||||
| `w_templates` | `domain/admin` | 20260609→20260611 (rename) |
|
||||
| `w_schedules` | `driver_asynq_cron` | 20260610 (create), 20260611 (identity), 20260614 (update cleanup) |
|
||||
| `w_task_executions` | `driver_asynq_worker` | 20260609→20260611 (rename) |
|
||||
| `w_uploads` | `domain/upload` | 20260609→20260611 (rename), 20260613 (access_mode), 20260617 (indexes), 20260618 (drop storage_driver) |
|
||||
| `w_upload_stats` | `domain/upload` | 20260617 (create+backfill) |
|
||||
| `w_push_events` | `domain/message_gateway` | 20260614 (create), 20260615 (task_type), 20260616 (cleanup) |
|
||||
| `w_push_histories` | `domain/message_gateway` | 20260614 (create) |
|
||||
| `w_push_channels` | `domain/message_gateway` | 20260614 (create) |
|
||||
| `w_message_channels` | `domain/message_gateway` | 20260816 (create) |
|
||||
| `w_message_bindings` | `domain/message_gateway` | 20260816 (create) |
|
||||
| `w_message_pairing_codes` | `domain/message_gateway` | 20260816 (create) |
|
||||
| `w_user_access_logs` | `domain/risk_control` | 20260816 (create) + ClickHouse |
|
||||
|
||||
## 执行步骤(共 8 步)
|
||||
|
||||
---
|
||||
|
||||
### 步骤 1:创建 Bootstrap 迁移(保留在 `pkg/migrator`)
|
||||
|
||||
**文件**:`pkg/migrator/goose/postgres/00001_bootstrap.sql`
|
||||
|
||||
将以下全局迁移合并为一个 bootstrap 文件:
|
||||
- **`202606090001_initial_schema.sql`** → 创建 `users`, `auth_sources`, `external_accounts`, `access_tokens`, `system_configs`, `uploads`, `task_executions`, `templates`(全部无前缀旧名)
|
||||
- **`202606110003_rename_tables_to_w_prefix.sql`** → 全部重命名为 `w_` 前缀
|
||||
|
||||
**合并后,bootstrap 文件直接创建带 `w_` 前缀的表**,不再需要 rename 步骤:
|
||||
|
||||
```sql
|
||||
-- +goose Up
|
||||
CREATE TABLE IF NOT EXISTS w_users (
|
||||
id BIGINT PRIMARY KEY,
|
||||
username VARCHAR(64) UNIQUE,
|
||||
...
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS w_access_tokens (...);
|
||||
CREATE TABLE IF NOT EXISTS w_auth_sources (...);
|
||||
CREATE TABLE IF NOT EXISTS w_external_accounts (...);
|
||||
CREATE TABLE IF NOT EXISTS w_system_configs (
|
||||
key VARCHAR(64) PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
...
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS w_uploads (...);
|
||||
CREATE TABLE IF NOT EXISTS w_task_executions (...);
|
||||
CREATE TABLE IF NOT EXISTS w_templates (...);
|
||||
CREATE TABLE IF NOT EXISTS w_schedules (...);
|
||||
```
|
||||
|
||||
> **为什么保留在 `pkg/migrator`**:这些是平台的"初始化基座"——无论哪些插件启用,这些表都存在。将 bootstrap 放到 `pkg/migrator` 之下回避了循环依赖问题(例如 `w_system_configs` 属于 admin,但 bootstrap 时 admin 插件尚未 apply)。
|
||||
|
||||
---
|
||||
|
||||
### 步骤 2:修复 `gooseEngine` 支持插件迁移
|
||||
|
||||
**文件**:`cmd/app.go`
|
||||
|
||||
```go
|
||||
type gooseEngine struct{}
|
||||
|
||||
func (e *gooseEngine) Migrate(_ context.Context, entries []core.MigrationEntry) error {
|
||||
// 1. 先跑 bootstrap(初始化基座)
|
||||
_ = migrator.Migrate()
|
||||
|
||||
// 2. 再跑每个插件注册的迁移
|
||||
for _, entry := range entries {
|
||||
gormDB := database.DB(context.Background())
|
||||
if gormDB == nil {
|
||||
continue
|
||||
}
|
||||
sqlDB, err := gormDB.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
goose.SetBaseFS(entry.FS)
|
||||
if err := goose.SetDialect(gooseDialect()); err != nil {
|
||||
return err
|
||||
}
|
||||
dir := entry.Dir
|
||||
if dir == "" {
|
||||
dir = "migrations"
|
||||
}
|
||||
if err := goose.Up(sqlDB, dir); err != nil {
|
||||
return fmt.Errorf("migrate %s: %w", entry.PluginID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. ClickHouse 迁移
|
||||
_ = migrator.MigrateClickHouse()
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
依赖项:`gooseDialect()` 从 `pkg/migrator` 导出。
|
||||
|
||||
---
|
||||
|
||||
### 步骤 3:按表所有者拆分迁移到各插件
|
||||
|
||||
| 全局源文件 | 目标插件 | 迁移文件名 |
|
||||
|---|---|---|
|
||||
| `202606100002` (access_tokens is_admin) | `domain/auth` | `migrations/00002_add_access_token_is_admin.sql` |
|
||||
| `202606110001` (drop last_used_at) | `domain/auth` | `migrations/00003_drop_access_token_last_used_at.sql` |
|
||||
| `202606140003` (system user seed) | `domain/user` | `migrations/00002_seed_system_user.sql` |
|
||||
| `202606110004` (file_access_whitelist seed) | `domain/admin` | `migrations/00003_seed_file_access_whitelist.sql` |
|
||||
| `202606110005` (disk_cache configs seed) | `domain/admin` | `migrations/00004_seed_disk_cache_configs.sql` |
|
||||
| `202606120002` (update_upstream_repo seed) | `domain/admin` | `migrations/00005_seed_upstream_repo_config.sql` |
|
||||
| `202606130002` (system_configs value TEXT) | `domain/admin` | `migrations/00006_expand_config_value.sql` |
|
||||
| `202606130003` (storage_config seed) | `domain/admin` | `migrations/00007_seed_storage_config.sql` |
|
||||
| `202608160002` (log database configs) | `domain/admin` | `migrations/00008_seed_log_db_configs.sql` |
|
||||
| `202606120001` (login_session_ttl) | `domain/auth` | `migrations/00004_seed_login_session_ttl.sql` |
|
||||
| `202606130001` (w_uploads access_mode) | `domain/upload` | `migrations/00001_add_access_mode.sql` |
|
||||
| `202606170001` (upload indexes) | `domain/upload` | `migrations/00002_add_composite_indexes.sql` |
|
||||
| `202606170002` (upload stats table) | `domain/upload` | `migrations/00003_create_upload_stats.sql` |
|
||||
| `202606170003` (backfill stats) | `domain/upload` | `migrations/00004_backfill_upload_stats.sql` |
|
||||
| `202606180001` (drop storage_driver) | `domain/upload` | `migrations/00005_drop_storage_driver.sql` |
|
||||
| `202606140001` (push tables) | `domain/message_gateway` | `migrations/00002_create_push_tables.sql` |
|
||||
| `202606140004` (push channels) | `domain/message_gateway` | `migrations/00003_create_push_channels.sql` |
|
||||
| `202606150001` (push task_type) | `domain/message_gateway` | `migrations/00004_add_push_task_type.sql` |
|
||||
| `202606160001` (remove push config) | `domain/message_gateway` | `migrations/00005_remove_push_config.sql` |
|
||||
| `202608160003` (message gateway tables) | `domain/message_gateway` | `migrations/00006_create_message_tables.sql` |
|
||||
| `202606100001` (schedules) | `driver_asynq_cron` | `migrations/00001_create_schedules.sql` |
|
||||
| `202606110002` (schedules identity) | `driver_asynq_cron` | `migrations/00002_alter_schedules_identity.sql` |
|
||||
| `202606140005` (update cleanup schedule) | `driver_asynq_cron` | `migrations/00003_update_cleanup_schedule.sql` |
|
||||
| `202608160001` (user access logs) | `domain/risk_control/logstore` | `migrations/00001_create_access_logs.sql` |
|
||||
| `202608160002` (log_database configs) | `domain/admin` | (合并到 admin 步骤 7) |
|
||||
|
||||
---
|
||||
|
||||
### 步骤 4:补充缺失的 `go:embed` 和 `Register()` 调用
|
||||
|
||||
**`plugins/domain/admin/plugin.go`**:
|
||||
```go
|
||||
//go:embed migrations/*.sql
|
||||
var adminMigrations embed.FS
|
||||
|
||||
// 在 Apply() 中:
|
||||
ctx.Migrations().Register("admin", adminMigrations)
|
||||
```
|
||||
|
||||
**`plugins/domain/upload/plugin.go`**:
|
||||
```go
|
||||
//go:embed migrations/*.sql
|
||||
var uploadMigrations embed.FS
|
||||
|
||||
// 在 Apply() 中:
|
||||
ctx.Migrations().Register("upload", uploadMigrations)
|
||||
```
|
||||
|
||||
**`plugins/domain/risk_control/plugin.go`**:
|
||||
```go
|
||||
// go:embed 由 logstore 子包自行处理(它已有自己的 moved 文件)
|
||||
// 在 Apply() 中:
|
||||
ctx.Migrations().Register("risk_control/logstore", logstoreMigrationFS)
|
||||
```
|
||||
|
||||
**`plugins/drivers/driver_asynq_cron/plugin.go`**:
|
||||
```go
|
||||
//go:embed migrations/*.sql
|
||||
var cronMigrations embed.FS
|
||||
|
||||
// 在 Apply() 中:
|
||||
ctx.Migrations().Register("driver_asynq_cron", cronMigrations)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 步骤 5:解决 Admin 插件迁移与 Bootstrap 的冲突
|
||||
|
||||
当前 `admin/migrations/00001` 执行 `CREATE TABLE IF NOT EXISTS w_system_configs (...)`,但 bootstrap 已在步骤 1 中创建过这张表。需要:
|
||||
1. **保持 `IF NOT EXISTS`** 保证幂等性
|
||||
2. **从 admin migration 中移除 `w_schedules` 和 `w_task_executions` 的 CREATE**(它们在 bootstrap 中创建,属于 driver 插件)
|
||||
3. **仅保留 admin 自己的表**:`w_system_configs`, `w_templates`
|
||||
4. Seed 数据使用 `ON CONFLICT DO NOTHING` 避免重复:
|
||||
|
||||
当前 admin 的 seed 包含 29 个系统配置,其中约 14 个与全局迁移重复。整理后的 admin seed 应:
|
||||
|
||||
```sql
|
||||
INSERT INTO w_system_configs (...) VALUES
|
||||
('cap_login_enabled', 'false', ...),
|
||||
('cap_auto_solve', 'true', ...),
|
||||
-- ... (所有 29 个配置)
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
```
|
||||
|
||||
> 全局迁移中 `202606110004` 到 `202608160002` 的 7 个种子 INSERT 将被迁移到 admin,全部使用 `ON CONFLICT DO NOTHING`。
|
||||
|
||||
---
|
||||
|
||||
### 步骤 6:清理已迁移的全局文件
|
||||
|
||||
拆分完成后,从 `pkg/migrator/goose/postgres/` 中删除以下文件:
|
||||
|
||||
```
|
||||
202606100002_access_token_is_admin.sql
|
||||
202606100001_create_schedules.sql
|
||||
202606110001_remove_access_token_last_used_at.sql
|
||||
202606110002_alter_schedules_id_auto_increment.sql
|
||||
202606110004_add_file_access_whitelist_config.sql
|
||||
202606110005_add_disk_cache_configs.sql
|
||||
202606120001_add_login_session_ttl_config.sql
|
||||
202606120002_add_update_upstream_repository_config.sql
|
||||
202606130001_add_upload_access_mode.sql
|
||||
202606130002_expand_system_config_value.sql
|
||||
202606130003_add_storage_config.sql
|
||||
202606140001_create_push_tables.sql
|
||||
202606140003_add_system_user.sql
|
||||
202606140004_create_push_channels.sql
|
||||
202606140005_update_system_cleanup_schedule.sql
|
||||
202606150001_add_task_type_to_push_events.sql
|
||||
202606160001_remove_push_config.sql
|
||||
202606170001_add_upload_composite_indexes.sql
|
||||
202606170002_create_upload_stats_table.sql
|
||||
202606170003_backfill_upload_stats.sql
|
||||
202606180001_drop_upload_storage_driver.sql
|
||||
202608160001_create_user_access_logs.sql
|
||||
202608160002_log_database_configs.sql
|
||||
202608160003_create_message_gateway.sql
|
||||
```
|
||||
|
||||
**保留在 `pkg/migrator/goose/postgres/` 的仅限**:
|
||||
```
|
||||
00001_bootstrap.sql (合并后的初始化基座)
|
||||
```
|
||||
|
||||
**注意**:`202606110003_rename_tables_to_w_prefix.sql` 也被合并进 bootstrap。`202606090001_initial_schema.sql` 也被合并掉。
|
||||
|
||||
---
|
||||
|
||||
### 步骤 7:更新 `pkg/migrator` 导出 `gooseDialect()`
|
||||
|
||||
在 `pkg/migrator/migrator.go` 中将 `gooseDialect()` 和 `migrationDir()` 改为导出,供 `cmd/app.go` 的 `gooseEngine.Migrate()` 引用。
|
||||
|
||||
---
|
||||
|
||||
### 步骤 8:验证 + 提交
|
||||
|
||||
```bash
|
||||
cd /Users/ryan/Code/Go/Wavelet
|
||||
|
||||
# 1. 编译验证
|
||||
go build -mod=mod ./...
|
||||
go vet ./...
|
||||
|
||||
# 2. 架构门禁验证
|
||||
make code-check
|
||||
|
||||
# 3. 验证插件迁移注册完整性
|
||||
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
|
||||
# → 每个有 migrations/ 目录的插件既要有 go:embed 又要有 Register()
|
||||
|
||||
# 4. 验证 admin 插件迁移完整性
|
||||
grep -rn 'w_schedules\|w_task_executions' plugins/domain/admin/migrations/
|
||||
# → 不应有(这些属于 driver 插件)
|
||||
|
||||
# 5. 提交
|
||||
git add -A && git commit -m "refactor(migration): split global SQL into per-plugin migrations
|
||||
|
||||
- Merge 26 global SQLs into bootstrap + per-plugin migrations
|
||||
- Fix gooseEngine to iterate plugin-registered MigrationEntry
|
||||
- Add go:embed + Register() to admin, upload, risk_control, driver_asynq_cron
|
||||
- Remove 23 migrated SQL files from pkg/migrator/goose/
|
||||
- Keep only bootstrap in pkg/migrator/goose/
|
||||
- All CREATE TABLE use IF NOT EXISTS, all INSERT use ON CONFLICT DO NOTHING"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 依赖关系图
|
||||
|
||||
```
|
||||
Bootstrap (pkg/migrator)
|
||||
├── 创建 w_users, w_access_tokens, w_auth_sources, w_external_accounts
|
||||
├── 创建 w_system_configs, w_templates, w_schedules, w_task_executions
|
||||
├── 创建 w_uploads, w_upload_stats
|
||||
└── 创建所有 w_ 前缀表
|
||||
│
|
||||
├─ auth/00002 (access_tokens is_admin)
|
||||
├─ auth/00003 (drop last_used_at)
|
||||
├─ auth/00004 (login_session_ttl seed)
|
||||
│
|
||||
├─ user/00002 (system user seed)
|
||||
│
|
||||
├─ admin/00001 (w_system_configs, w_templates) [IF NOT EXISTS]
|
||||
├─ admin/00002 (29 config seeds + 2 template seeds)
|
||||
├─ admin/00003–00008 (拆分后的种子迁移)
|
||||
│
|
||||
├─ upload/00001–00005 (access_mode → indexes → stats → backfill → drop)
|
||||
│
|
||||
├─ message_gateway/00001 (w_message_* tables)
|
||||
├─ message_gateway/00002–00006 (push tables → channels → task_type → cleanup)
|
||||
│
|
||||
├─ driver_asynq_cron/00001–00003 (schedules → identity → cleanup)
|
||||
│
|
||||
├─ driver_asynq_worker/00001 (task_executions — 如果有追加操作)
|
||||
│
|
||||
└─ risk_control/logstore/00001 (w_user_access_logs)
|
||||
```
|
||||
|
||||
所有步骤执行的迁移顺序由 Goose 的文件名前缀控制。Bootstrap 使用 `00001_`,每个插件的迁移从 `00002_` 开始编号(`00001` 留给插件自身表 CREATE,若插件 bootstrap 已创建则从 `00002` 开始)。
|
||||
@@ -1,84 +0,0 @@
|
||||
# Zero-Redis Pluggable Architecture Implementation Plan
|
||||
|
||||
> **Goal**: Extract Redis into optional plugins and introduce lightweight in-process equivalents (`cache_memory`, `driver_inproc_worker`, `driver_inproc_cron`), enabling zero-Redis monolithic and embedded deployment modes.
|
||||
|
||||
- **Architecture Spec**: [`docs/superpowers/specs/2026-08-28-zero-redis-pluggable-architecture-design.md`](file:///Users/ryan/Code/Go/Wavelet/docs/superpowers/specs/2026-08-28-zero-redis-pluggable-architecture-design.md)
|
||||
- **Branch**: `main`
|
||||
|
||||
---
|
||||
|
||||
## Proposed Changes
|
||||
|
||||
### 1. In-Memory Cache Infrastructure Plugin (`backend/plugins/infra/cache_memory`)
|
||||
|
||||
#### [NEW] `backend/plugins/infra/cache_memory/plugin.go`
|
||||
- Implements `core.Plugin` (`Name() == "cache_memory"`).
|
||||
- Applies `contracts.CacheService` to the Context via `core.Provide[contracts.CacheService](ctx, memCacheSvc)`.
|
||||
|
||||
#### [NEW] `backend/plugins/infra/cache_memory/cache.go`
|
||||
- Implements `contracts.CacheService` using `pkg/cache/ram`.
|
||||
- Dispatches in-process invalidation notifications via `ctx.Events().Emit("cache:invalidate", key)`.
|
||||
|
||||
#### [NEW] `backend/plugins/infra/cache_memory/plugin_test.go`
|
||||
- Unit tests for Get, Set, Delete, GetOrSet, TTL expiration, and event bus emission.
|
||||
|
||||
---
|
||||
|
||||
### 2. In-Process Async Worker Driver (`backend/plugins/drivers/driver_inproc_worker`)
|
||||
|
||||
#### [NEW] `backend/plugins/drivers/driver_inproc_worker/plugin.go`
|
||||
- Implements `core.Plugin` & `core.Driver` (`Type() == core.DriverTypeWorker`).
|
||||
- Scans and executes registered tasks from `ctx.Tasks().Tasks()`.
|
||||
|
||||
#### [NEW] `backend/plugins/drivers/driver_inproc_worker/executor.go`
|
||||
- In-memory buffered channel queue and worker goroutine pool managed via `util.Go`.
|
||||
- Supports execution timeout, retry with backoff, and graceful shutdown.
|
||||
|
||||
#### [NEW] `backend/plugins/drivers/driver_inproc_worker/plugin_test.go`
|
||||
- Unit tests for in-process task execution, concurrency limit, retry on error, and graceful shutdown.
|
||||
|
||||
---
|
||||
|
||||
### 3. In-Process Cron Scheduler Driver (`backend/plugins/drivers/driver_inproc_cron`)
|
||||
|
||||
#### [NEW] `backend/plugins/drivers/driver_inproc_cron/plugin.go`
|
||||
- Implements `core.Plugin` & `core.Driver` (`Type() == core.DriverTypeScheduler`).
|
||||
- Reads `ctx.Schedules().Schedules()` and schedules jobs using `robfig/cron/v3`.
|
||||
|
||||
#### [NEW] `backend/plugins/drivers/driver_inproc_cron/scheduler.go`
|
||||
- Handles Cron expression registration, job triggering, and graceful stopping.
|
||||
|
||||
#### [NEW] `backend/plugins/drivers/driver_inproc_cron/plugin_test.go`
|
||||
- Unit tests verifying cron job scheduling, execution tracking, and stop behavior.
|
||||
|
||||
---
|
||||
|
||||
### 4. Admin Domain Decoupling from Redis
|
||||
|
||||
#### [MODIFY] `backend/plugins/domain/admin/repository.go`
|
||||
- Introduce in-memory `RingBuffer` for task output streams when Redis is nil.
|
||||
- Fallback task log lookups to `RingBuffer` and `w_task_executions` table.
|
||||
|
||||
#### [MODIFY] `backend/plugins/domain/admin/system_config_cache.go`
|
||||
- Guard Redis PubSub listener so that when Redis is nil, it gracefully falls back to local event bus updates without spawning disconnected subscriber loops.
|
||||
|
||||
---
|
||||
|
||||
### 5. Application Assembly & Profile Switching
|
||||
|
||||
#### [MODIFY] `backend/cmd/app.go`
|
||||
- Switch dynamically between Redis plugins (`cache`, `driver_asynq_worker`, `driver_asynq_cron`) and In-Process plugins (`cache_memory`, `driver_inproc_worker`, `driver_inproc_cron`) based on `config.Config.Redis.Enabled`.
|
||||
|
||||
#### [MODIFY] `backend/cmd/app_test.go`
|
||||
- Add test verifying application bootstrap in both `Redis.Enabled = true` and `Redis.Enabled = false` states.
|
||||
|
||||
---
|
||||
|
||||
## Verification Plan
|
||||
|
||||
### Automated Tests
|
||||
1. **In-Memory Cache Tests**: `go test -v ./backend/plugins/infra/cache_memory/...`
|
||||
2. **In-Process Worker Tests**: `go test -v ./backend/plugins/drivers/driver_inproc_worker/...`
|
||||
3. **In-Process Cron Tests**: `go test -v ./backend/plugins/drivers/driver_inproc_cron/...`
|
||||
4. **Full Test Suite**: `cd backend && go test ./...`
|
||||
5. **Quality Gate**: `make code-check && make format`
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user