golang 泛型 middleware 设计模式: 一次只做一件事
1. 前言
本文主要介绍 在使用 gRPC 和 Gin 框架中常用的 middleware 设计模式
还有几种叫法
- 装饰器模式
- Pipeline 模式
设计思想:
- 10 个 10 行函数, 而不是 1 个 100 行函数
- 一次只做一件事, 而不一次做多件事
- 单一职责
2. 代码
已生产环境中大量使用, 每日执行千万次
package chain
type 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 chain
import (
"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 += 1
return
}
func ctx3_product(c *Ctx[*Input], in *Input) (err error) {
in.a *= 2
return
}