Plumego 入门:一个克制而清晰的 Go 服务框架

Plumego 是一个强调显式、可读性和长期演进的 Go 服务框架。本文从工程视角出发,带你快速理解 Plumego 的设计理念、核心结构与最小可运行示例。

引言:为什么需要 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)

每一层都有明确职责:

层级关注点
HandlerHTTP / JSON / 参数校验
Usecase业务流程编排
Domain核心业务规则
Repo数据访问 / 外部依赖

没有“为了分层而分层”,也没有“所有逻辑都塞进 Controller”。

3. 基于标准库,克制依赖

Plumego 优先使用 Go 标准库:

  • net/http
  • context
  • encoding/json
  • time

只有在 显著降低复杂度 的情况下,才引入第三方依赖。

这意味着:

  • 升级 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 框架的对比

维度PlumegoGinEchoFiber
设计哲学显式、克制、长期维护高性能、功能丰富简洁、可扩展极速、类 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 才真正开始发挥价值。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「Engineering」更多文章

  1. Plumego Best Practices · 一个真实业务(用户中心)的完整示例
  2. 一个真实业务(用户中心)的完整 Plumego 示例
  3. Plumego Roadmap