golang 泛型 middleware 设计模式: 一次只做一件事
1. 前言
本文主要介绍 在使用 gRPC 和 Gin 框架中常用的 middleware 设计模式
还有几种叫法
- 装饰器模式
- Pipeline 模式
设计思想:
- 10 个 10 行函数, 而不是 1 个 100 行函数
- 一次只做一件事, 而不一次做多件事
- 单一职责
2. 代码
已生产环境中大量使用, 每日执行千万次
package chaintype Ctx[T any] struct {in T // 数据入参fns []func(c *Ctx[T], in T) (err error)idx int
}func NewCtx[T any](in T) *Ctx[T] {return &Ctx[T]{in: in,idx: -1,}
}func (c *Ctx[T]) Next() (err error) {c.idx++for ; c.idx < len(c.fns); c.idx++ {err = c.fns[c.idx](c, c.in)if err != nil {return}}return
}func (c *Ctx[T]) Add(fns ...func(c *Ctx[T], in T) (err error)) {c.fns = append(c.fns, fns...)
}
3. test case
package chainimport ("fmt""testing""time"
)type Input struct {a int
}func TestNewCtx(t *testing.T) {// 初始化in := Input{a: 1}c := NewCtx(&in)// 添加中间件c.Add(ctx1_cost) // 记录耗时c.Add(ctx2_add) // 数据加工c.Add(ctx3_product) // 数据加工2// 执行err := c.Next()if err != nil {panic(err)}// 检查结果fmt.Println(in.a)if in.a != 4 {panic(fmt.Sprintf("expect 4, but got %d", in.a))}
}func ctx1_cost(c *Ctx[*Input], in *Input) (err error) {start := time.Now()defer func() {cost := time.Since(start)fmt.Println("cost:", cost)}()err = c.Next()return
}func ctx2_add(c *Ctx[*Input], in *Input) (err error) {in.a += 1return
}func ctx3_product(c *Ctx[*Input], in *Input) (err error) {in.a *= 2return
}