引言:为什么需要 Plumego?
在 Birdor 的工程实践中,我们反复遇到同一个问题:
当项目从“能跑”进入“要长期维护”阶段,Go Web 框架往往开始成为负担,而不是助力。
Plumego 的目标并不是“更快写完第一个接口”,而是帮助你在 6 个月、12 个月甚至 3 年后,依然能够清晰地理解、演进和重构你的服务。
它强调三件事:
- 显式优于隐式
- 结构优于技巧
- 可读性优于魔法
如果你认同这些价值观,那么 Plumego 值得你花 10 分钟了解。
Plumego 的核心理念
1. 显式(Explicit by Design)
在 Plumego 中:
- 路由如何注册,一目了然
- 中间件如何生效,顺序明确
- Context 中有什么数据,来源清楚
Plumego 刻意避免:
- 隐式注入
- 全局魔法状态
- 难以追踪的 hook / lifecycle
你看到的代码,就是系统真实的运行方式。
2. 分层而不过度抽象
Plumego 推荐但不强制的基本分层模型:
HTTP
└── Handler
└── Usecase
└── Domain / Service
└── Repository (optional)
每一层都有明确职责:
| 层级 | 关注点 |
|---|---|
| Handler | HTTP / JSON / 参数校验 |
| Usecase | 业务流程编排 |
| Domain | 核心业务规则 |
| Repo | 数据访问 / 外部依赖 |
没有“为了分层而分层”,也没有“所有逻辑都塞进 Controller”。
3. 基于标准库,克制依赖
Plumego 优先使用 Go 标准库:
net/httpcontextencoding/jsontime
只有在 显著降低复杂度 的情况下,才引入第三方依赖。
这意味着:
- 升级 Go 版本风险极低
- 调试体验接近原生
- 不被生态波动绑架
快速开始:最小可运行示例
1. 安装
go get github.com/spcent/plumego
2. 项目结构示例
.
├── main.go
├── app/
│ ├── http/
│ │ ├── router.go
│ │ └── handlers/
│ │ └── health_handler.go
│ └── usecase/
│ └── health_usecase.go
这是一个可长期扩展的最小结构,而不是 demo-only 的目录。
3. main.go
package main
import (
"log"
"net/http"
"github.com/spcent/plumego"
"yourapp/app/http"
)
func main() {
app := plumego.New()
http.RegisterRoutes(app)
log.Println("server started at :8080")
log.Fatal(http.ListenAndServe(":8080", app))
}
注意几点:
- Plumego 本身实现了
http.Handler - 没有隐藏的 server lifecycle
- 启动逻辑完全可控
4. 路由注册
package http
import (
"github.com/spcent/plumego"
"yourapp/app/http/handlers"
)
func RegisterRoutes(app *plumego.App) {
app.GET("/health", handlers.Health)
}
路由集中、显式、可搜索。
5. Handler
package handlers
import (
"net/http"
"github.com/spcent/plumego"
)
func Health(ctx *plumego.Context) {
ctx.JSON(http.StatusOK, map[string]string{
"status": "ok",
})
}
Handler 只关心:
- 请求
- 响应
- HTTP 语义
Context:克制但足够
Plumego 的 Context 不是“万能对象”。
它只提供:
- Request / ResponseWriter
- JSON / Text / Status helpers
- Context-aware 生命周期控制
你不会在这里看到:
- ORM Session
- 全局配置
- 业务状态缓存
这是一种刻意的约束。
中间件:顺序即语义
app.Use(Logger())
app.Use(Recover())
app.Use(Auth())
在 Plumego 中:
- 注册顺序 = 执行顺序
- 没有隐式优先级
- 没有注解驱动
这让系统行为可预测、可推理、可调试。
与其他 Go 框架的对比
| 维度 | Plumego | Gin | Echo | Fiber |
|---|---|---|---|---|
| 设计哲学 | 显式、克制、长期维护 | 高性能、功能丰富 | 简洁、可扩展 | 极速、类 Express API |
| 路由性能 | 接近原生 | 极快(radix tree) | 极快 | 最快(zero-allocation) |
| 中间件机制 | 显式顺序注册 | 洋葱模型 | 洋葱模型 | 洋葱模型 |
| Context 设计 | 最小化,无隐式注入 | 功能丰富,内置 binding | 功能丰富 | 兼容 fasthttp Context |
| 依赖数量 | 极少(标准库为主) | 较多 | 中等 | 较少 |
| 学习曲线 | 低(标准库知识即可) | 中 | 低 | 低 |
| 典型用户 | 追求代码可读性的团队 | 快速交付的创业项目 | 企业级应用 | 高并发 API 服务 |
Plumego 的取舍很明确:用 slightly 的性能牺牲换取长期的可维护性。如果你的服务需要持续演进而不仅是快速上线,Plumego 的显式设计会在 12 个月后显现出巨大优势。
自定义中间件开发
Plumego 的中间件是一个满足 HandlerFunc 签名的函数,通过闭包包装实现:
func Logger() plumego.MiddlewareFunc {
return func(next plumego.HandlerFunc) plumego.HandlerFunc {
return func(ctx *plumego.Context) {
start := time.Now()
// 请求前逻辑
log.Printf(“[%s] %s”, ctx.Request.Method, ctx.Request.URL.Path)
next(ctx) // 执行后续 handler
// 请求后逻辑
duration := time.Since(start)
log.Printf(“completed in %v, status: %d”, duration, ctx.StatusCode)
}
}
}
// 注册:顺序即执行顺序
app.Use(Logger())
app.Use(Recover())
关键原则:
- 中间件应保持幂等和无副作用
- 错误处理通过
ctx.Error()或 panic + recover 模式 - 不要在中间件中执行业务逻辑,只做横切关注点(日志、认证、限流、tracing)
错误处理与统一响应
Plumego 推荐基于 HTTP 状态码的结构化错误设计:
type AppError struct {
Code string `json:”code”`
Message string `json:”message”`
Details any `json:”details,omitempty”`
}
func (e *AppError) Error() string { return e.Message }
// 统一的错误响应中间件
func ErrorHandler() plumego.MiddlewareFunc {
return func(next plumego.HandlerFunc) plumego.HandlerFunc {
return func(ctx *plumego.Context) {
defer func() {
if r := recover(); r != nil {
log.Printf(“panic recovered: %v\n%s”, r, debug.Stack())
ctx.JSON(http.StatusInternalServerError, AppError{
Code: “INTERNAL_ERROR”,
Message: “服务器内部错误”,
})
}
}()
next(ctx)
// 检查 handler 是否设置了自定义错误
if err, ok := ctx.Get(“error”).(*AppError); ok {
ctx.JSON(http.StatusBadRequest, err)
}
}
}
}
错误码命名规范:DOMAIN_ACTION_FAILURE(如 AUTH_TOKEN_EXPIRED、USER_NOT_FOUND),便于客户端做精细化处理与监控告警。
测试策略
Plumego 的显式设计天然适合测试:
// Handler 单元测试:直接构造 plumego.Context
func TestHealthHandler(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, “/health”, nil)
rec := httptest.NewRecorder()
ctx := plumego.NewContext(req, rec)
Health(ctx)
assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), “ok”)
}
// 集成测试:完整启动应用
func TestIntegration(t *testing.T) {
app := plumego.New()
RegisterRoutes(app)
server := httptest.NewServer(app)
defer server.Close()
resp, err := http.Get(server.URL + “/health”)
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
}
推荐测试分层:
- Handler 层:Mock Usecase,测试 HTTP 协议边界
- Usecase 层:Mock Repository,测试业务规则
- 集成测试:验证路由、中间件链、错误处理全链路
生产环境配置清单
| 类别 | 推荐实践 |
|---|---|
| 部署 | 容器化(Docker)+ Kubernetes / Docker Compose |
| 配置 | 环境变量注入,避免硬编码 |
| 日志 | Structured JSON log(zap / zerolog),避免 fmt.Println |
| 监控 | OpenTelemetry + Prometheus metrics endpoint |
| 追踪 | OTel tracing,关联请求全链路 |
| 健康检查 | /healthz(存活)+ /readyz(就绪)单独端点 |
什么时候适合使用 Plumego?
Plumego 非常适合:
- 内部服务 / 中台服务
- 长期维护的业务系统
- 强调代码审查与工程规范的团队
- 希望”代码即文档”的项目
它不追求:
- 最少代码行数
- 极致开发速度
- All-in-one 生态
Birdor 视角下的总结
在 Birdor 看来,Plumego 更像是:
一套工程纪律,而不是一个炫技框架。
如果你正在寻找:
- 一个不会替你“做决定”的 Go 框架
- 一个能陪你走完整个项目生命周期的基础层
- 一个让新人快速理解、让老代码不失控的结构
那么,Plumego 是一个值得认真考虑的选择。
下一步
- 阅读:
Why Explicit? - 理解:Usecase 与 Handler 的边界
- 实践:为你的真实业务写第一个 Usecase
当你不再需要“框架帮你想”,
而是希望“框架不妨碍你思考”,
Plumego 才真正开始发挥价值。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。