diff --git a/docs/WAVELET_DEVELOPER_GUIDE.md b/docs/WAVELET_DEVELOPER_GUIDE.md new file mode 100644 index 00000000..07bfdbab --- /dev/null +++ b/docs/WAVELET_DEVELOPER_GUIDE.md @@ -0,0 +1,565 @@ +# Wavelet Cordis 插件化架构实战开发指南与标准规范 + +- **文档类型**: 下游开发者手册 / 架构实战指南 (Cookbook & Architecture Reference) +- **目标受众**: 官方插件开发者、下游业务二开工程师、架构师 +- **版本**: v1.0.0 (2026-08-27) + +--- + +# 目录 +- [第一部分:下游项目实战开发指南与 22 个高频开发场景解答](#第一部分下游项目实战开发指南与-22-个高频开发场景解答) + - [场景 1:插件必须要实现哪些方法与契约?](#场景-1插件必须要实现哪些方法与契约) + - [场景 2:插件间如何进行单向服务调用?](#场景-2插件间如何进行单向服务调用) + - [场景 3:插件间存在双向/循环调用时如何解决(杜绝 import cycle)?](#场景-3插件间存在双向循环调用时如何解决杜绝-import-cycle) + - [场景 4:如何开发并注册一个 HTTP API 接口?如何添加路由中间件?](#场景-4如何开发并注册一个-http-api-接口如何添加路由中间件) + - [场景 5:如何获取当前登录用户信息?](#场景-5如何获取当前登录用户信息) + - [场景 6:如何开发并注册一个 Asynq 异步 Worker 任务?](#场景-6如何开发并注册一个-asynq-异步-worker-任务) + - [场景 7:如何开发并注册一个 Cron 定时任务?](#场景-7如何开发并注册一个-cron-定时任务) + - [场景 8:数据库表结构如何声明?ORM 模型规范是什么?](#场景-8数据库表结构如何声明orm-模型规范是什么) + - [场景 9:数据库如何做独立迁移?Goose SQL 怎么组织?](#场景-9数据库如何做独立迁移goose-sql-怎么组织) + - [场景 10:如果有多个业务插件需要读写同一张表怎么办?](#场景-10如果有多个业务插件需要读写同一张表怎么办) + - [场景 11:如果跨插件操作多张表,如何确保事务一致性?](#场景-11如果跨插件操作多张表如何确保事务一致性) + - [场景 12:如何发布和订阅领域事件 (EventBus)?](#场景-12如何发布和订阅领域事件-eventbus) + - [场景 13:如何向系统注册插件自定义配置(config.yaml 与管理台热加载设置)?](#场景-13如何向系统注册插件自定义配置configyaml-与管理台热加载设置) + - [场景 14:如何使用多层缓存(RAM L1 + Redis L2 + PubSub 同步)?](#场景-14如何使用多层缓存ram-l1--redis-l2--pubsub-同步) + - [场景 15:如何使用分布式锁 (DistLock) 防止并发超卖与重复消费?](#场景-15如何使用分布式锁-distlock-防止并发超卖与重复消费) + - [场景 16:如何向管理后台动态注册监控数据与管理控制台?](#场景-16如何向管理后台动态注册监控数据与管理控制台) + - [场景 17:插件如何实现健康检查探针与就绪检查 (Health Check)?](#场景-17插件如何实现健康检查探针与就绪检查-health-check) + - [场景 18:插件如何扩展其他插件的能力(如新增一种 OAuth 登录提供商 / 新增消息推送渠道)?](#场景-18插件如何扩展其他插件的能力如新增一种-oauth-登录提供商--新增消息推送渠道) + - [场景 19:插件如何编写单元测试与集成测试(Mock 上下文与依赖打桩)?](#场景-19插件如何编写单元测试与集成测试mock-上下文与依赖打桩) + - [场景 20:以不同角色(api / worker / schedule / all)启动时,插件代码如何适配?](#场景-20以不同角色api--worker--schedule--all启动时插件代码如何适配) + - [场景 21:当某个插件流量暴增需要独立拆分为微服务时,如何零成本平滑改造?](#场景-21当某个插件流量暴增需要独立拆分为微服务时如何零成本平滑改造) + - [场景 22:插件如何安全处理文件上传与大文件摄取 (upload.Ingest)?](#场景-22插件如何安全处理文件上传与大文件摄取-uploadingest) +- [第二部分:整个项目的目录结构划分与包职责定义](#第二部分整个项目的目录结构划分与包职责定义) +- [第三部分:框架核心提供给插件调用的公用能力矩阵 (Context Capability Matrix)](#第三部分框架核心提供给插件调用的公用能力矩阵-context-capability-matrix) + +--- + +# 第一部分:下游项目实战开发指南与 22 个高频开发场景解答 + +### 场景 1:插件必须要实现哪些方法与契约? +每个插件必须实现 `core.Plugin` 接口,仅需提供两个核心方法:`Name()` 与 `Apply(ctx *core.Context)`。 + +```go +package myplugin + +import "github.com/Rain-kl/Wavelet/core" + +type Plugin struct{} + +// 1. Name: 返回全局唯一的插件标识符(建议遵循命名空间规范,如 "biz.order") +func (p *Plugin) Name() string { + return "biz.order" +} + +// 2. Apply: 核心装载入口,所有的路由注册、任务注册、服务提供与依赖消费均在此完成 +func (p *Plugin) Apply(ctx *core.Context) error { + // 在此编写装载逻辑 + return nil +} +``` + +--- + +### 场景 2:插件间如何进行单向服务调用? +**规则**:插件之间**禁止直接相互 import 具体实现包**。调用方仅面向 `core/contracts` 中的纯 Interface 编程,运行时通过 Context 解析。 + +```go +// 1. 插件 A (提供者 plugins/user) 将服务注入 Context +func (p *UserPlugin) Apply(ctx *core.Context) error { + userSvc := NewUserServiceImpl(ctx.DB()) + ctx.Provide[contracts.UserService](userSvc) + return nil +} + +// 2. 插件 B (消费者 plugins/order) 声明依赖并调用 +func (p *OrderPlugin) Apply(ctx *core.Context) error { + return ctx.Using(func(userSvc contracts.UserService) { + // userSvc 已由容器自动注入就绪 + v1 := ctx.Router().Group("/api/v1/orders") + v1.POST("", func(c *gin.Context) { + userInfo, err := userSvc.GetUserProfile(c.Request.Context(), "user_123") + // 处理订单逻辑... + }) + }) +} +``` + +--- + +### 场景 3:插件间存在双向/循环调用时如何解决(杜绝 import cycle)? +**问题场景**:`auth` 登录成功后需要查 `user` 资料;`user` 重置密码后需要调 `auth` 吊销 session。若两个 package 互相 import,Go 编译器会报 `import cycle not allowed`。 + +**Cordis 解法**: +1. 接口均定义在 `core/contracts`,双方只依赖 `core/contracts`。 +2. 运行时采用 **延迟注入 (Lazy Resolution / Inject)** 或 **事件解耦 (EventBus)**: + +```go +// plugins/auth/service.go +func (s *AuthServiceImpl) OnLoginSuccess(c context.Context, uid string) { + // 延迟注入 UserService,不发生 package 级循环导入 + userSvc, err := core.Inject[contracts.UserService](s.ctx) + if err == nil { + userSvc.UpdateLastLoginTime(c, uid) + } +} +``` +*更加推荐的方式是发射领域事件*(见场景 12),由 `user` 插件自愿监听,彻底消除相互调用的硬依赖。 + +--- + +### 场景 4:如何开发并注册一个 HTTP API 接口?如何添加路由中间件? +插件通过 `ctx.Router()` 声明路由。微内核支持标准 Gin 路由组与中间件挂载: + +```go +func (p *OrderPlugin) Apply(ctx *core.Context) error { + // 获取全局或 auth 插件提供的中间件 + authSvc, _ := core.Inject[contracts.AuthService](ctx) + + // 创建带版本前缀和鉴权中间件的路由组 + group := ctx.Router().Group("/api/v1/orders", authSvc.RequireAuthMiddleware()) + + // 注册 Handler + group.GET("", p.handleListOrders) + group.POST("", p.handleCreateOrder) + group.GET("/:id", p.handleGetOrderDetail) + + return nil +} +``` + +--- + +### 场景 5:如何获取当前登录用户信息? +`auth` 插件会在上下文中注入当前用户 Session。业务 Handler 可直接调用统一 Helper: + +```go +func (p *OrderPlugin) handleCreateOrder(c *gin.Context) { + // 1. 从当前 Gin 请求上下文中提取认证用户信息 + currentUser, ok := oauth.GetCurrentUser(c) + if !ok { + response.AbortUnauthorized(c, errs.ErrUnauthorized) + return + } + + log.Printf("当前下单用户 ID: %s, 权限角色: %s", currentUser.ID, currentUser.Role) + // 2. 正常业务处理... +} +``` + +--- + +### 场景 6:如何开发并注册一个 Asynq 异步 Worker 任务? +```go +func (p *OrderPlugin) Apply(ctx *core.Context) error { + // 1. 注册 Asynq 任务类型与消费处理器 + ctx.Task().Register("order:cancel_timeout", p.handleTimeoutCancelTask) + return nil +} + +// 2. 任务执行函数 +func (p *OrderPlugin) handleTimeoutCancelTask(ctx context.Context, t *asynq.Task) error { + var payload OrderTimeoutPayload + if err := json.Unmarshal(t.Payload(), &payload); err != nil { + return err + } + // 执行超时关单业务逻辑... + return nil +} + +// 3. 业务中异步投递任务 +func (p *OrderPlugin) EnqueueTimeoutCheck(ctx context.Context, orderID string) { + p.ctx.TaskClient().EnqueueContext(ctx, asynq.NewTask("order:cancel_timeout", payloadBytes), asynq.ProcessIn(15*time.Minute)) +} +``` + +--- + +### 场景 7:如何开发并注册一个 Cron 定时任务? +```go +func (p *ReportPlugin) Apply(ctx *core.Context) error { + // 每天凌晨 2 点执行日报汇总任务 + ctx.Schedule().RegisterCron("0 2 * * *", "report:daily_summary", DailyReportPayload{Type: "all"}) + return nil +} +``` + +--- + +### 场景 8:数据库表结构如何声明?ORM 模型规范是什么? +**规范**: +1. 表名必须带有插件专有前缀(如 `w_order_`、`w_auth_`),避免跨插件表名冲突。 +2. 零值与数据库默认值严格对齐;禁止物理外键,显式建索引。 +3. 必须通过 GORM 结构体清晰声明 `gorm:"..."` 标签与 `json:"..."`。 + +```go +package models + +import "time" + +type Order struct { + ID string `gorm:"column:id;primaryKey;size:64" json:"id"` + UserID string `gorm:"column:user_id;index;size:64;not null" json:"user_id"` + Amount int64 `gorm:"column:amount;not null" json:"amount"` + Status string `gorm:"column:status;size:32;index;not null;default:'pending'" json:"status"` + CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` + DeletedAt *time.Time `gorm:"column:deleted_at;index" json:"-"` +} + +func (Order) TableName() string { + return "w_orders" +} +``` + +--- + +### 场景 9:数据库如何做独立迁移?Goose SQL 怎么组织? +**彻底告别集中大迁移目录**。每个插件在内部目录建立 `migrations/`,并通过 `//go:embed` 打包注入: + +```go +// plugins/order/plugin.go +package order + +import ( + "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 +} +``` + +#### SQL 迁移脚本规范 (`plugins/order/migrations/20260827000001_create_orders_table.sql`): +```sql +-- +goose Up +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS w_orders ( + id VARCHAR(64) PRIMARY KEY, + user_id VARCHAR(64) NOT NULL, + 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 +); +CREATE INDEX IF NOT EXISTS idx_w_orders_user_id ON w_orders(user_id); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE IF EXISTS w_orders; +-- +goose StatementEnd +``` + +--- + +### 场景 10:如果有多个业务插件需要读写同一张表怎么办? +**黄金准则**:**表有且仅有一个所有者插件 (Single Owner Principle)**。 +* 严禁插件 B 直接通过 SQL 修改插件 A 拥有的核心表(如订单插件直接修改用户表)。 +* **合法模式 1(服务调用)**:插件 A 提供 `UserService.DeductBalance(uid, amount)`,插件 B 调用该接口。 +* **合法模式 2(只读视图 / 共享查询 DTO)**:如果仅仅是高频联合查询(报表),插件 A 暴露只读查询接口,或通过数据库只读从库直接投影。 + +--- + +### 场景 11:如果跨插件操作多张表,如何确保事务一致性? +在插件化和微服务就绪体系下,**跨插件的强分布式事务是反模式**。 + +1. **同插件内多表操作**:直接使用本地数据库事务: + ```go + err := ctx.DB().Transaction(func(tx *gorm.DB) error { + if err := tx.Create(&order).Error; err != nil { return err } + if err := tx.Create(&orderItem).Error; err != nil { return err } + return nil + }) + ``` +2. **跨插件操作(如创建订单 + 扣减库存 + 发送通知)**: + * 采用 **最终一致性 (Eventual Consistency / Saga 模式)**。 + * 本地事务成功后,发射 `OrderCreatedEvent` 到 EventBus; + * 库存插件监听到事件后扣减库存,若失败则发布补偿事件触发订单取消。 + +--- + +### 场景 12:如何发布和订阅领域事件 (EventBus)? +```go +// 1. 定义强类型事件结构 +type OrderPaidEvent struct { + OrderID string `json:"order_id"` + UserID string `json:"user_id"` + PayAmount int64 `json:"pay_amount"` +} + +// 2. 插件 A 发布事件 +ctx.Events().Emit("order:paid", OrderPaidEvent{OrderID: "ord_1", UserID: "u_1", PayAmount: 9900}) + +// 3. 插件 B 订阅事件 +ctx.Events().On("order:paid", func(c context.Context, e OrderPaidEvent) error { + log.Printf("收到支付成功事件,开始为用户 %s 发放权益", e.UserID) + return nil +}) +``` + +--- + +### 场景 13:如何向系统注册插件自定义配置(config.yaml 与管理台热加载设置)? +```go +type OrderConfig struct { + MaxItemsPerOrder int `yaml:"max_items" json:"max_items"` + AutoCancelMins int `yaml:"auto_cancel_mins" json:"auto_cancel_mins"` +} + +func (p *OrderPlugin) Apply(ctx *core.Context) error { + var cfg OrderConfig + // 1. 自动从 config.yaml 中的 plugins.order 节点绑定配置 + ctx.Config().Bind("plugins.order", &cfg) + + // 2. 注册为管理台可动态修改的系统参数 + ctx.Settings().Register(core.SettingSchema{ + Key: "order.auto_cancel_mins", + Default: 15, + Description: "未支付订单自动取消时间 (分钟)", + }) + return nil +} +``` + +--- + +### 场景 14:如何使用多层缓存(RAM L1 + Redis L2 + PubSub 同步)? +框架提供三层穿透缓存能力,防止缓存击穿与雪崩: + +```go +func (s *OrderService) GetOrderWithCache(ctx context.Context, orderID string) (*Order, error) { + var order Order + err := s.ctx.Cache().GetOrSet(ctx, "order:"+orderID, &order, 10*time.Minute, func() (any, error) { + // Cache Miss 回源查 DB + var dbOrder Order + if err := s.db.WithContext(ctx).First(&dbOrder, "id = ?", orderID).Error; err != nil { + return nil, err + } + return &dbOrder, nil + }) + return &order, err +} + +// 当订单更新时,广播失效所有节点的 L1 内存缓存与 L2 Redis 缓存 +func (s *OrderService) InvalidateCache(ctx context.Context, orderID string) { + s.ctx.Cache().Delete(ctx, "order:"+orderID) +} +``` + +--- + +### 场景 15:如何使用分布式锁 (DistLock) 防止并发超卖与重复消费? +```go +func (s *OrderService) ProcessPayment(ctx context.Context, orderID string) error { + // 获取分布式锁,租期 5 秒 + unlock, err := s.ctx.DistLock().Lock(ctx, "lock:order:pay:"+orderID, 5*time.Second) + if err != nil { + return fmt.Errorf("当前订单正在处理中,请勿重复提交") + } + defer unlock() // 确保释放 + + // 执行扣款操作... + return nil +} +``` + +--- + +### 场景 16:如何向管理后台动态注册监控数据与管理控制台? +插件可以向管理后台扩展点注入自己的仪表盘指标和诊断探针: + +```go +func (p *OrderPlugin) Apply(ctx *core.Context) error { + ctx.Admin().RegisterMetric("order_count_today", func(c context.Context) any { + var count int64 + ctx.DB().Model(&models.Order{}).Where("created_at >= ?", todayStart()).Count(&count) + return count + }) + return nil +} +``` + +--- + +### 场景 17:插件如何实现健康检查探针与就绪检查 (Health Check)? +```go +func (p *PaymentPlugin) Apply(ctx *core.Context) error { + ctx.Health().RegisterProbe("payment_gateway", func(ctx context.Context) error { + // 测试第三方支付网关网络连通性 + return pingPaymentGateway(ctx) + }) + return nil +} +``` + +--- + +### 场景 18:插件如何扩展其他插件的能力(如新增一种 OAuth 登录提供商 / 新增消息推送渠道)? +采用 **注册表扩展点模式 (Registry Pattern)**: + +```go +// 1. 下游编写微信登录插件 plugins/oauth_wechat +func (p *WeChatOAuthPlugin) Apply(ctx *core.Context) error { + return ctx.Using(func(authRegistry contracts.AuthRegistry) { + // 向核心 auth 插件注入微信 OAuth 实现 + authRegistry.RegisterOAuthProvider("wechat", &WeChatProvider{...}) + }) +} +``` + +--- + +### 场景 19:插件如何编写单元测试与集成测试(Mock 上下文与依赖打桩)? +微内核提供轻量测试脚手架 `coretest`: + +```go +func TestOrderCreate(t *testing.T) { + // 1. 创建内存测试专用 Context + ctx := coretest.NewMockContext(t) + + // 2. Mock 依赖的 UserService + mockUserSvc := &MockUserService{ReturnUser: &contracts.UserDTO{ID: "u_1", Balance: 1000}} + ctx.Provide[contracts.UserService](mockUserSvc) + + // 3. 装载插件 + plugin := &OrderPlugin{} + require.NoError(t, plugin.Apply(ctx)) + + // 4. 发起 HTTP 接口测试 + w := ctx.PerformRequest("POST", "/api/v1/orders", `{"item_id":"item_1"}`) + assert.Equal(t, 200, w.Code) +} +``` + +--- + +### 场景 20:以不同角色(api / worker / schedule / all)启动时,插件代码如何适配? +**开发者无需做任何特殊处理**! +插件只需在一个 `Apply` 方法中把自己的路由、任务、调度全部注册进 `Context`。微内核调度器会根据运行命令自动按需激活对应的运行时驱动,不匹配的能力保持休眠。 + +--- + +### 场景 21:当某个插件流量暴增需要独立拆分为微服务时,如何零成本平滑改造? +```go +// 1. 之前单体模式:在 main.go 中加载本地实现 +app.Use(&auth.Plugin{}) // 进程内直接运行 + +// 2. 拆分为微服务后:只需将 main.go 替换为 gRPC 客户端代理插件! +app.Use(&auth_grpc_client.Plugin{RemoteAddr: "auth-service.prod:9000"}) + +// 3. 所有依赖 auth 的业务插件(如 order, user)业务代码 0 处修改! +``` + +--- + +### 场景 22:插件如何安全处理文件上传与大文件摄取 (upload.Ingest)? +**严格规则**:禁止插件自行直接写入对象存储底层 Bucket 或直连底层文件系统。统一走平台摄取服务: + +```go +func (p *OrderPlugin) handleUploadInvoice(c *gin.Context) { + fileHeader, _ := c.FormFile("file") + + // 使用平台统一摄取引擎(自动计算哈希、防重传、生成签名 URL 与入库追踪) + ingestResult, err := upload.IngestFormFile(c.Request.Context(), fileHeader, upload.IngestPolicy{ + AllowedTypes: []string{"image/png", "application/pdf"}, + MaxSizeBytes: 10 * 1024 * 1024, + }) + if err != nil { + response.AbortBadRequest(c, errs.ErrUploadFailed) + return + } + + c.JSON(200, response.OK(gin.H{"file_url": ingestResult.URL})) +} +``` + +--- + +# 第二部分:整个项目的目录结构划分与包职责定义 + +```text +Wavelet/ +├── cmd/ # CLI 命令分发与装配入口 +│ ├── root.go # Cobra 根命令 +│ ├── server.go # 综合启动器(支持 api/worker/schedule/all profile) +│ └── migrate.go # 数据库独立迁移命令行工具 +│ +├── core/ # 【微内核引擎 (Zero Business Logic)】 +│ ├── context.go # Context 上下文总线与 Fork 树 +│ ├── container.go # 基于泛型的 IoC 服务注册与解析器 +│ ├── events.go # 强类型领域事件总线 (EventBus) +│ ├── lifecycle.go # 启动/停止生命周期编排状态机 +│ ├── contracts/ # 【跨插件标准服务契约 (纯 Interface)】 +│ │ ├── auth.go # AuthService 契约 +│ │ ├── user.go # UserService 契约 +│ │ ├── cache.go # CacheService 契约 +│ │ └── database.go # DBService 契约 +│ └── extpoints/ # 扩展点定义 (Router, Task, Migration, Setting) +│ +├── plugins/ # 【官方标准插件库 (完全高内聚闭包)】 +│ ├── drivers/ # 运行时驱动插件 +│ │ ├── driver_http/ # Gin Web HTTP 驱动 +│ │ ├── driver_asynq_worker/ # Asynq Worker 并发消费驱动 +│ │ └── driver_asynq_cron/ # Asynq Cron 调度器驱动 +│ │ +│ ├── infra/ # 基础设施服务插件 +│ │ ├── database/ # GORM 多数据源与读写分离插件 +│ │ ├── cache/ # RAM + Redis + PubSub 缓存插件 +│ │ ├── logger/ # Zap + Otel 分布式链路追踪日志插件 +│ │ └── storage/ # S3 / OSS / Local 对象存储插件 +│ │ +│ └── domain/ # 业务领域能力插件 +│ ├── auth/ # OAuth / Session / Passkey 认证插件 +│ ├── user/ # 用户资料 / 权限 / 角色插件 +│ ├── message_gateway/ # Bot 网关 / 渠道推送插件 +│ ├── risk_control/ # 访问控制 / IP 限流 / 安全风控插件 +│ └── admin/ # 系统管理台与监控面板插件 +│ +└── downstream/ # 【下游二开项目模板与脚手架】 + ├── custom_plugins/ # 下游自定义业务插件 + ├── config.yaml # 声明启用的插件与配置文件 + └── main.go # 下游项目组合启动入口 +``` + +### 各层职责与禁止规则 (Guardrails): +1. **`core/`**: + - **职责**:纯抽象,提供 IoC、Context、EventBus 和 Lifecycle。 + - **严禁**:严禁 import 任何具体业务包,严禁 import `gin`、`gorm`、`asynq`。 +2. **`core/contracts/`**: + - **职责**:仅定义公开的 Go Interface 和公共 DTO。 + - **严禁**:严禁包含任何具体实现逻辑或 SQL 操作。 +3. **`plugins/`**: + - **职责**:所有业务逻辑和驱动实现的归宿。每个插件扁平自包含。 + - **严禁**:插件之间严禁跨包 import 内部私有代码,跨插件调用一律走 `contracts` 接口或 `EventBus`。 + +--- + +# 第三部分:框架核心提供给插件调用的公用能力矩阵 (Context Capability Matrix) + +每个插件在 `Apply(ctx *core.Context)` 时,都可以无缝调用微内核暴露的以下标准能力: + +| 扩展点方法 | 返回类型 | 功能说明 | 适用场景 | +| :--- | :--- | :--- | :--- | +| `ctx.Router()` | `RouterExtension` | 声明 HTTP 路由、前缀分组与挂载中间件 | 暴露 API 接口、Web 控制台 | +| `ctx.Task()` | `TaskExtension` | 注册 Asynq 异步任务消费处理器 | 耗时后台任务、异步消息发送 | +| `ctx.Schedule()` | `ScheduleExtension`| 注册 Cron 定时调度任务 | 定时报表统计、周期性清理 | +| `ctx.Migrations()` | `MigrationExtension`| 注册插件专属的 Goose SQL 迁移嵌入系统 | 自建数据表、版本升级 | +| `ctx.Events()` | `EventBus` | 强类型领域事件的发布与订阅 (Emit / On) | 跨插件完全解耦通知与状态同步 | +| `ctx.Settings()` | `SettingExtension` | 声明动态可配置项(支持热更新) | 业务参数配置、管理台可调节参数 | +| `ctx.DB()` | `*gorm.DB` | 获取全局受事务与 Trace 保护的 GORM 数据源 | 数据持久化 CRUD | +| `ctx.Cache()` | `CacheService` | 三层穿透缓存(RAM L1 + Redis L2 + PubSub 广播)| 高频读数据性能加速 | +| `ctx.DistLock()` | `DistLockService` | 基于 Redis 的工业级分布式锁 | 防并发超卖、防重复执行 | +| `ctx.Logger()` | `Logger` | 携带链路 TraceID 的结构化日志记录器 | 业务日志打印与审计 | +| `ctx.Storage()` | `StorageService` | 统一对象存储读写引擎 | 文件摄取、图片持久化 | +| `core.Provide[T]`| `void` | 向全局 IoC 容器注册本插件提供的强类型服务 | 暴露自身能力给其他插件消费 | +| `core.Inject[T]` | `(T, error)` | 从全局 IoC 容器中按类型获取服务实例 | 消费其他插件暴露的服务 | +| `core.Using[T]` | `error` | 响应式声明依赖,当服务就绪时执行回调 | 声明前置依赖关系 | + diff --git a/docs/WAVELET_WHITE_PAPER.md b/docs/WAVELET_WHITE_PAPER.md new file mode 100644 index 00000000..ae6c1614 --- /dev/null +++ b/docs/WAVELET_WHITE_PAPER.md @@ -0,0 +1,107 @@ +# WAVELET 架构白皮书 (Technical Architecture White Paper) + +- **版本**: v1.0.0 (Cordis Architecture Standard) +- **代号**: Cordis-Wavelet +- **编写组织**: Wavelet 核心架构委员会 +- **发布日期**: 2026-08-27 + +--- + +## 1. 摘要与愿景 (Executive Summary) + +Wavelet 是面向下一代云原生与企业级业务中台的 **微内核全插件化框架 (Micro-Kernel & Plugin-Native Platform)**。 +其核心愿景是:**通过极致内聚的微内核与纯净的上下文服务总线,实现“一切皆插件、一切皆服务”的极高业务拓展性与生态繁荣**。 + +无论是一线工程师开发单个轻量业务功能,还是大型企业面向数万 QPS 高并发流量进行微服务拆分,Wavelet 均能以统一的开发范式支撑未来 5 年的平滑演进。 + +--- + +## 2. 核心架构哲学 (Core Architectural Philosophy) + +``` + ┌────────────────────────┐ + │ Context (上下文) │ + │ (统一服务总线/IoC Hub) │ + └───────────┬────────────┘ + │ + ┌───────────────────────┼───────────────────────┐ + ▼ ▼ ▼ + [提供服务 Provide] [依赖服务 Using] [扩展能力 Extend] + ctx.Provide(Auth) ctx.Using([DB, Cache]) ctx.Route / ctx.Task +``` + +### 2.1 微内核原则 (Micro-Kernel Principle) +内核(`core/`)不持有任何具体业务逻辑,不硬编码 Gin、GORM、Asynq 等具体引擎。内核仅提供: +- 树状上下文(`Context`)与作用域隔离(`Fork`) +- 泛型依赖注入与服务定位器(`IoC Container`) +- 强类型领域事件总线(`EventBus`) +- 生命周期编排器(`Lifecycle Manager`)与标准扩展点契约 + +### 2.2 扁平自包含插件 (Flat & Self-Contained Plugins) +告别过度设计的 DDD 样板代码,每个插件作为一个自给自足的高内聚闭包,就近组织路由、Handler、模型与专属数据迁移,实现**随插随用、按需组合、随拔随走**。 + +### 2.3 编译期依赖组合 (Compile-Time Composition) +基于 Go 语言的静态强类型优势,下游项目通过 `app.Use(&MyPlugin{})` 在编译期静态组装,产出单一静态二进制文件,兼具极高运行性能与极低运维分发成本。 + +--- + +## 3. 架构全景模型 (Architecture Landscape) + +``` ++-----------------------------------------------------------------------------------+ +| 下游业务项目 (Downstream Application) | +| main.go: app.Use(&logger.Plugin{}).Use(&auth.Plugin{})... | ++-----------------------------------------------------------------------------------+ + │ + ▼ ++-----------------------------------------------------------------------------------+ +| Wavelet Core (微内核上下文总线) | +| - Context (服务树与扩展点总线) - Lifecycle Manager (生命周期编排) | +| - Service Hub (泛型 IoC 容器) - EventBus (强类型领域事件总线) | ++-----------------------------------------------------------------------------------+ + │ │ + ▼ 注册与驱动 ▼ 挂载能力 ++------------------------------------+ +-------------------------------------------+ +| 运行时驱动插件 (Driver Plugins) | | 业务领域插件 (Domain Plugins) | +| - driver-http (Gin Web 引擎) | | - plugin-auth (认证/Session/OAuth) | +| - driver-worker (Asynq 消费池) | | - plugin-user (用户资料/角色权限) | +| - driver-cron (Asynq 定时调度器) | | - plugin-msg-gateway (消息通道与推送) | +| - driver-database (GORM 数据源) | | - plugin-risk-control (访问风控与限流) | +| - driver-cache (RAM/Redis 缓存) | | - [下游自定义插件] (业务私有插件) | ++------------------------------------+ +-------------------------------------------+ +``` + +--- + +## 4. 高并发与分布式演进路线 (Scalability & Microservices Strategy) + +```mermaid +graph LR + subgraph Monolith ["阶段 1:单体插件化 (进程内零开销)"] + UserP["plugin-user"] -->|Go Interface 内存调用| AuthP["plugin-auth"] + end + + subgraph Distributed ["阶段 2:高并发微服务拆分 (透明代理替换)"] + UserP2["plugin-user"] -->|相同的 Go Interface| AuthClient["plugin-auth-client (gRPC 代理)"] + AuthClient -.->|gRPC / HTTP/2| RemoteAuth["独立 Auth 微服务集群"] + end +``` + +1. **接口不变性原则 (Contract Immutability)**:跨插件交互全部面向 `core/contracts` 纯接口。微服务拆分时仅需引入 gRPC 客户端插件,调用方业务代码 **0 修改**。 +2. **分布式事件网格 (Event Mesh)**:内存 EventBus 支持无缝升级为 Redis Stream / NATS / Kafka 分布式总线。 +3. **数据分片与独立演进**:每个插件自包含 Goose SQL 迁移与独立表前缀,天然支持主从分库与分库分表。 + +--- + +## 5. 核心建设与里程碑 (Milestones) + +| 阶段 | 里程碑目标 | 交付物 | 状态 | +| :--- | :--- | :--- | :--- | +| **Phase 1** | 微内核 Context 与泛型 IoC 容器 | `core/context.go`, `core/container.go` | ✅ 已完成 (96.2% 单测) | +| **Phase 2** | 领域扩展点与强类型 EventBus | `core/extpoints/`, `core/events.go` | ✅ 已完成 (97.1% 单测) | +| **Phase 3** | 运行时驱动插件下沉 (HTTP/Worker/Cron) | `plugins/drivers/*` | ✅ 已完成 (生命周期单测) | +| **Phase 4** | 基础设施服务插件化 (DB/Cache/Log/Storage) | `plugins/infra/*` | 🟡 构建中 | +| **Phase 5** | 业务领域模块插件化解耦 (Auth/User/Msg/Admin) | `plugins/domain/*` | ⚪ 待构建 | +| **Phase 6** | CLI 运行时切面调度与 App 编排器 | `core/app.go`, `cmd/*` | ⚪ 待构建 | +| **Phase 7** | 下游开发者脚手架与全链路 E2E 验证 | `downstream/*` | ⚪ 待构建 | +