name: goddd description: GoDDD 六边形架构开发指南。当使用 goddd 架构实现代码、创建新领域、新增 CRUD、数据库表定义、领域间依赖解耦、排序功能、Core 层需要 HTTP 请求信息时使用此技能。也应在以下隐含场景主动触发:新增业务模块、讨论 Core/Store/API 分层、使用 godddx 生成代码、实现适配器模式、添加 Wire provider、使用 web.WrapH/PagerFilter/DateFilter/WithContext 等框架工具。即使用户没有提到"goddd",只要涉及六边形架构、领域驱动、依赖倒置、CRUD 生成等概念,都应使用此技能。
GoDDD 六边形架构开发指南
本技能指导在 GoDDD 六边形架构下进行开发,覆盖代码生成、领域间解耦、排序方案、HTTP 上下文透传、Web 工具使用等核心场景。
遇到不确定的写法时,优先参考项目中已有的符合规范的领域代码。
目录
详细参考文档在 references/ 目录下,按需阅读:
references/sort.md— 排序功能完整实现方案references/with-context.md— WithContext 架构设计详解references/adapter-pattern.md— 适配器模式与 Option 注入完整示例references/web-toolkit.md— Web 工具函数完整参考(含签名和用法示例)
架构概览
┌──────────────────────────────────────────────────────────┐
│ API 层 (主动适配器) │
│ internal/web/api/ │
│ 职责: HTTP 协议转换 → 调用 Core → 返回响应 │
└──────────────────────┬───────────────────────────────────┘
│ 依赖
▼
┌──────────────────────────────────────────────────────────┐
│ Core 层 (领域层/业务核心) │
│ internal/core/<domain>/ │
│ │
│ ├─ core.go Core 结构体 + Storer 接口 │
│ ├─ port.go 被动适配器接口 │
│ ├─ doc.go 领域说明 │
│ ├─ model.go 非 GORM 类型定义 │
│ ├─ <entity>.go 业务方法 + EntityStorer 接口 │
│ ├─ <entity>.model.go 领域模型 (GORM 映射) │
│ ├─ <entity>.param.go Find/Add/Edit Input 参数 │
│ ├─ <provider>adapter/ 对外提供的适配器实现 │
│ └─ store/<domain>db/ 数据库实现 (被动适配器) │
└──────────────────────────────────────────────────────────┘
依赖方向:API → Core ← Store/Adapter(外层依赖内层,内层通过接口反转依赖)
godddx 代码生成
CRUD 场景必须使用 godddx 生成代码,确保结构一致。
步骤
- 在
tables/<domain>/下创建表定义文件 - 结构体必须包含
ID、CreatedAt、UpdatedAt字段 - 若使用随机字符 ID,使用
uniqueid.Core类型 - 同一领域多个结构体放在同一个 tables 文件中
- 执行生成:
godddx -f tables/<domain>/<entity>.go - 在
internal/web/api/provider.go注册 Wire provider - 调用生成的
Register<Domain>函数注册路由 - 在领域目录下创建
doc.go描述领域用途
表定义示例
// tables/task/task.go
package task
import (
"time"
"github.com/ixugo/goddd/domain/uniqueid"
)
type Task struct {
ID uniqueid.Core `gorm:"primaryKey"`
Name string
Status int
TenantID string
CreatedBy string
Sort int64 `gorm:"autoIncrement"`
CreatedAt time.Time
UpdatedAt time.Time
}
Wire 注册模式
在 provider.go 中添加 NewXxxCore 和 NewXxxAPI:
var ProviderSet = wire.NewSet(
wire.Struct(new(Usecase), "*"),
NewHTTPHandler,
// ... 已有 provider
NewTaskCore, NewTaskAPI, // 新增领域
)
func NewTaskCore(db *gorm.DB) task.Core {
store := taskdb.NewDB(db).AutoMigrate(orm.GetEnabledAutoMigrate())
return task.NewCore(store)
}
参数定义规范
核心原则:归属字段(TenantID、CreatedBy)由 API 层填充,编辑时不可修改。
FindInput — 查询参数
type FindEntityInput struct {
web.PagerFilter // 分页
web.DateFilter // 日期范围(start_ms, end_ms 毫秒时间戳)
Name string `form:"name"` // 模糊查询字段
TenantID string `form:"-"` // API 层填充
CreatedBy string `form:"-"` // API 层填充
}
AddInput — 新增参数
type AddEntityInput struct {
Name string `json:"name"`
TenantID string `json:"-"` // API 层填充
CreatedBy string `json:"-"` // API 层填充
}
EditInput — 编辑参数
type EditEntityInput struct {
Name string `json:"name"`
// 不包含 TenantID、CreatedBy 等归属字段
}
领域间解耦
领域间必须通过适配器解耦,不能直接依赖其他领域的 Core。
核心规则
| 规则 | 说明 |
|---|---|
| Port 定义在提供方 | 接口和模型定义在提供能力的领域子包中 |
| Adapter 实现在提供方 | 适配器放在 <provider>adapter/ 子包 |
| 消费方通过 Option 注入 | NewCore(store, opts...) 模式 |
| 返回类型定义在提供方子包 | 避免重复定义 |
Option 注入模式
// 消费方 core.go
type Core struct {
store Storer
userProvider useradapter.BriefProvider
}
type Option func(*Core)
func WithUserProvider(p useradapter.BriefProvider) Option {
return func(c *Core) { c.userProvider = p }
}
func NewCore(store Storer, opts ...Option) Core {
c := Core{store: store}
for _, opt := range opts { opt(&c) }
return c
}
API 层注入
func NewMessageCore(db *gorm.DB, briefProvider useradapter.BriefProvider) message.Core {
store := messagedb.NewDB(db).AutoMigrate(orm.GetEnabledAutoMigrate())
return message.NewCore(store,
message.WithUserProvider(briefProvider),
)
}
详细示例和完整代码请阅读
references/adapter-pattern.md
排序功能实现
实现拖拽排序:接收有序 ID 数组,重新分配 sort 值而不影响未传入的记录。
核心逻辑
- 查询传入 ID 的记录,获取现有
sort值 - 将
sort值升序排列 - 按传入 ID 顺序重新分配排序值
- 事务批量更新
完整实现代码请阅读
references/sort.md
要点
- 数据库字段
sort使用 gorm tagautoIncrement自增 - Store 层用事务批量更新,Core 层编排逻辑,API 层只做协议转换
- 校验所有 ID 存在,不存在则返回错误
WithContext
解决 Core 层不依赖 HTTP 框架、但 Adapter 需要 HTTP 请求信息的矛盾。
原理
web.Context 扩展了 context.Context,携带 *http.Request,通过类型断言按需获取:
// API 层 — 构造 web.Context
ctx := web.WithContext(c.Request)
core.DoSomething(ctx, ...)
// Adapter 层 — 类型断言获取 HTTP 信息
func (p *impl) resolveCover(ctx context.Context, cover string) string {
if wc, ok := ctx.(web.Context); ok {
return p.coverURLFunc(wc.Request(), cover)
}
return cover // 非 HTTP 场景降级
}
设计要点
| 特性 | 说明 |
|---|---|
| 零破坏性 | 实现 context.Context,现有签名无需修改 |
| 渐进式采用 | 只改调用处(API)和使用处(Adapter),Core 层透传 |
| 优雅降级 | 类型断言失败时返回原始值,非 HTTP 场景正常工作 |
| 可扩展 | 接口可定义子接口扩展 |
适用场景
- 动态 URL 拼接(需要 scheme + host)
- 请求级元信息透传(IP、User-Agent)
- 跨领域 Adapter 需要 HTTP 上下文辅助数据转换
不适用
- 已有明确参数的简单场景 → 直接传参
- 与 HTTP 无关的逻辑 → 使用标准
context.Context - 需要修改请求状态 → 在 API 层处理
完整设计文档请阅读
references/with-context.md
Web 工具函数速查
github.com/ixugo/goddd/pkg/web 提供 HTTP 开发基础设施。
完整函数签名和使用示例请阅读
references/web-toolkit.md
请求处理
| 函数/类型 | 用途 |
|---|---|
WrapH(fn) | 核心包装函数,将 func(*gin.Context, *Input) (Output, error) 包装为 gin.HandlerFunc |
WrapHs(fn, mid...) | 同 WrapH,附加前置中间件 |
PagerFilter | 分页参数(Page, Size, Sort, SortSafelist),含 Offset(), Limit(), SortColumn() |
NewPagerFilterMaxSize() | 不分页查询(Size=99999) |
DateFilter | 日期范围(StartMs, EndMs 毫秒时间戳),含 StartAt(), EndAt(), DefaultStartAt() |
Validator | 参数校验,Check(ok, key, msg), AddError(key, msg), Valid(), List() |
CustomMethods | 自定义方法路由 |
响应处理
| 函数/类型 | 用途 |
|---|---|
PageOutput[T] | 分页响应 {Items, Total} |
ScrollPageOutput[T] | 滚动分页 {Items, Next} |
Success(c, data) | 统一成功响应 |
Fail(c, err) | 统一错误响应,自动映射 HTTP 状态码 |
ResponseMsg | 通用消息响应 {Msg} |
Context 与 URL
| 函数/类型 | 用途 |
|---|---|
WithContext(r) | *http.Request → web.Context,携带 HTTP 元信息 |
GetBaseURL(r) | 提取 scheme://host |
BaseURLJoin(r,...string) | 拼接 scheme://host/fullpath |
GetHost(r) / GetScheme(r) | 提取 host / scheme |
XForwardedPrefix(r, path) | 处理反向代理前缀 |
TraceID(ctx) / MustTraceID(ctx) | 获取请求追踪 ID |
SetTraceID(ctx, id) | 设置追踪 ID |
JWT 鉴权
| 函数 | 用途 |
|---|---|
NewToken(data, secret, opts...) | 创建 JWT(默认 6h 过期) |
ParseToken(token, secret) | 解析 JWT |
AuthMiddleware(secret) | JWT 鉴权中间件 |
AuthLevel(level) | 等级鉴权中间件(等级越小权限越大) |
NewClaimsData() | 创建 Claims 数据,链式 SetUserID/SetUsername/SetRoleID/SetLevel/Set |
GetUID/GetUsername/GetRoleID/GetLevel/GetToken/GetInt | 从上下文获取用户信息 |
中间件
| 函数 | 用途 |
|---|---|
Logger(ignoreFn...) | 请求日志 |
LoggerWithBody(limit, ignoreFn...) | 记录请求体/响应体 |
LoggerWithUseTime(maxLimit, ignoreFn...) | 耗时记录,超时打 warn |
RateLimiter(r, b) | 全局限流 |
IPRateLimiterForGin(r, b) | 按 IP 限流 |
IDRateLimiter(r, b, ttl) | 按 ID 限流 |
LimitContentLength(limit) | 请求体大小限制 |
CacheControlMaxAge(second) | Cache-Control 头 |
EtagHandler() | ETag + 304 支持 |
Recover() | panic 恢复 |
SetDeadline(duration) | 读写超时 |
Metrics() | 请求计数统计 |
SSE(Server-Sent Events)
| 函数/类型 | 用途 |
|---|---|
NewSSE(length, timeout) | 创建 SSE 实例 |
SSE.Publish(event) | 发布事件 |
SSE.ServeHTTP(w, r) | 实现 http.Handler |
SendChunk(ch, c) / SendChunkPro(ch, c) | 分块进度发送 |
NewEventMessage(event, data) | 创建 SSE 事件消息 |
忽略选项(用于日志/限流中间件)
| 函数 | 用途 |
|---|---|
IgnorePrefix(prefix...) | 忽略路径前缀 |
IgnorePath(path...) | 忽略完整路径 |
IgnoreMethod(method) | 忽略 HTTP 方法 |
IgoreContains(substrs...) | 忽略路径含子串 |
IgnoreBool(v) | 固定布尔值忽略 |
性能分析
| 函数 | 用途 |
|---|---|
SetupPProf(r, &ips) | 注册 pprof 路由,IP 白名单 |
SetupMutexProfile(rate) | 启用互斥锁采样 |
CountGoroutines(d, num) | 记录 goroutine 数量 |
WrapH 入参规则
- POST/PUT/DELETE/PATCH → 绑定 Request Body(
jsontag) - GET → 绑定 URL Query(
formtag) - 入参第二个参数必须是指针,
*struct{}表示无参数 - 路由参数用
c.Param()获取,不走自动绑定
错误处理
Core 层返回 reason.Error 类型错误,web.WrapH 自动映射 HTTP 状态码:
return nil, reason.ErrBadRequest.SetMsg("参数不合法") // → 400
return nil, reason.ErrDB.Withf("查询失败: %s", err) // → 500
return nil, reason.ErrUnauthorized.SetMsg("未登录") // → 401
SetMsg()— 给用户的友好提示Withf()— 给开发者的 details(SetRelease()后不输出)
API 层规范
- 只做 HTTP 协议转换:参数绑定 → 填充归属字段 → 调用 Core → 返回响应
- 归属字段在 API 层填充:TenantID、CreatedBy 等通过
json:"-"/form:"-"标记 - 路由参数用
c.Param:不走自动绑定,仅路由参数时入参用_ *struct{} - 适配器不定义在 API 层:统一放在领域的
<provider>adapter/目录 - Handler 若需访问后续赋值字段:使用指针接收者
路由注册模式
func registerTask(r gin.IRouter, api TaskAPI, handler ...gin.HandlerFunc) {
g := r.Group("/tasks", handler...)
g.GET("", web.WrapH(api.findTasks))
g.POST("", web.WrapH(api.addTask))
g.GET("/:id", web.WrapH(api.getTask))
g.PUT("/:id", web.WrapH(api.editTask))
g.DELETE("/:id", web.WrapH(api.deleteTask))
g.PUT("/sort", web.WrapH(api.sortTasks))
}
常用导入
import (
"github.com/ixugo/goddd/pkg/orm"
"github.com/ixugo/goddd/pkg/reason"
"github.com/ixugo/goddd/pkg/web"
"github.com/ixugo/goddd/domain/uniqueid"
"github.com/jinzhu/copier"
)