Plumego Best Practices
By Birdor Engineering
写在前面:这不是“技巧合集”
这份文档不是:
- API 参数的穷举说明
- 所有功能的使用手册
- 为了“写得高级”而复杂化的套路
它是一份 工程级 Best Practices,核心目标只有一个:
让一个 Plumego 项目在 1~3 年后依然清晰、可控、可演进。
一、项目结构:稳定优先于灵活
推荐的基础结构
.
├── main.go
├── app/
│ ├── http/
│ │ ├── router.go
│ │ ├── middleware/
│ │ └── handlers/
│ ├── usecase/
│ ├── domain/
│ └── repo/
├── pkg/
│ └── shared/
└── internal/
设计原则
- 目录代表职责,不代表技术
- 允许“空目录”,为未来演进预留位置
- 宁愿结构稳定,也不要频繁重构目录
Birdor 的经验是:
目录一旦被团队记住,就不该轻易推翻。
二、Handler:只做 HTTP 语义
Handler 应该做的事
- 参数解析(path / query / body)
- 参数合法性校验
- HTTP 状态码选择
- 调用 usecase
Handler 不应该做的事
- 业务规则判断
- 数据库访问
- 事务控制
- 跨请求状态缓存
推荐模式
func CreateUser(ctx *plumego.Context) {
var req CreateUserRequest
if err := ctx.BindJSON(&req); err != nil {
ctx.BadRequest(err)
return
}
resp, err := usecase.CreateUser(ctx.Context(), req)
if err != nil {
ctx.Fail(err)
return
}
ctx.JSON(http.StatusCreated, resp)
}
Handler 越薄,系统越稳。
三、Usecase:业务流程的唯一入口
Usecase 的职责
- 编排业务流程
- 调用 domain / repo
- 管理事务边界(如需要)
- 将技术错误转化为业务语义
强约束原则
- 一个 Usecase = 一个业务动作
- 不要在 Usecase 之间相互调用
- 不依赖 HTTP / JSON / Context 实现
命名规范
CreateUser
UpdateUserProfile
DisableAccount
ResetPassword
避免:
UserServiceUserManagerHandleUserLogic
名词型命名往往意味着职责膨胀。
四、Domain:规则比流程更重要
Domain 层关注什么?
- 状态合法性
- 业务不变量
- 核心计算逻辑
示例
func (u *User) CanLogin() error {
if u.Disabled {
return ErrUserDisabled
}
if u.Expired() {
return ErrUserExpired
}
return nil
}
Birdor 的经验
- Domain 代码变化最慢
- 也是最值得写测试的地方
- 不依赖任何基础设施
五、Repository:延迟决定,保持可替换
原则
- Repo 是 接口,而不是实现
- 不在 Repo 中包含业务规则
- 不返回 HTTP 语义错误
type UserRepo interface {
FindByID(ctx context.Context, id string) (*User, error)
Save(ctx context.Context, u *User) error
}
何时可以不要 Repo?
- 纯计算服务
- 外部 API 聚合服务
- 一次性脚本型项目
Plumego 不强制 Repository。
六、Context 使用规范
推荐用法
- 传递
context.Context - 读取 request-scoped 信息(trace id / auth info)
- 控制超时与取消
禁止行为
- 把业务状态塞进 Context
- 当作全局 KV 使用
- 跨 goroutine 共享可变数据
Context 是“控制信号”,不是“数据仓库”。
七、中间件:少而确定
推荐的中间件类型
- Logging
- Recover
- Auth
- Trace / RequestID
顺序即语义
app.Use(RequestID())
app.Use(Logger())
app.Use(Recover())
app.Use(Auth())
规则:
- 越靠前,越基础
- Auth 永远在业务前
- 中间件之间不隐式通信
八、错误处理:统一,但不抽象过度
推荐错误分层
- Domain Error(业务错误)
- Infra Error(技术错误)
- HTTP 映射在 Handler 层完成
示例
if errors.Is(err, domain.ErrUserNotFound) {
ctx.NotFound("user not found")
return
}
避免:
- 全局 error code 枚举
- 过早引入复杂错误框架
九、测试策略(Birdor 实践)
| 层级 | 测试类型 |
|---|---|
| Domain | 单元测试(高覆盖) |
| Usecase | 场景测试 |
| Handler | 少量集成测试 |
重点不是覆盖率,而是 回归成本。
十、演进建议:Plumego 的长期使用方式
推荐节奏
- 前 1 个月:严格遵守分层
- 3 个月:开始沉淀通用 pkg
- 6 个月:重构 Usecase 边界
- 1 年:评估是否拆服务
Plumego 的优势在于:
它不会阻止你做任何“正确但困难”的事情。
Birdor 总结
Plumego 的 Best Practices,本质是一种工程态度:
- 不追求短期爽感
- 不用隐式魔法换取便利
- 不为未来制造技术债
如果你正在构建的是一个:
- 会被多人维护
- 会持续演进
- 会承载真实业务的 Go 服务
那么这套实践,足够你长期依赖。
下一步阅读
- 「Why Explicit?」
- 「Plumego vs Gin:工程视角下的取舍」
- 「一个真实 User Service 的完整 Plumego 实现」
当你开始刻意减少“框架感”,
Plumego 才真正成为你系统的一部分。