Plumego Best Practices

一份面向长期维护的 Plumego 工程实践指南,基于 Birdor 的真实项目经验,总结结构设计、分层边界、Context 使用、中间件规范与演进策略。

写在前面:这不是“技巧合集”

这份文档不是

  • 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

避免:

  • UserService
  • UserManager
  • HandleUserLogic

名词型命名往往意味着职责膨胀。

四、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. 前 1 个月:严格遵守分层
  2. 3 个月:开始沉淀通用 pkg
  3. 6 个月:重构 Usecase 边界
  4. 1 年:评估是否拆服务

Plumego 的优势在于:

它不会阻止你做任何“正确但困难”的事情。

Birdor 总结

Plumego 的 Best Practices,本质是一种工程态度:

  • 不追求短期爽感
  • 不用隐式魔法换取便利
  • 不为未来制造技术债

如果你正在构建的是一个:

  • 会被多人维护
  • 会持续演进
  • 会承载真实业务的 Go 服务

那么这套实践,足够你长期依赖。

下一步阅读

  • 「Why Explicit?」
  • 「Plumego vs Gin:工程视角下的取舍」
  • 「一个真实 User Service 的完整 Plumego 实现」

当你开始刻意减少“框架感”
Plumego 才真正成为你系统的一部分。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页