Gin、GORM 中责任链模式的使用

Gin、GORM 中责任链模式的使用

责任链模式在 GoF 的《设计模式》一书中是这样定义的

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

解耦请求的发送和接收,让多个接收对象都有机会处理这个请求。请求在多个串联的对象中传递,直到有对象能处理它。看下面这个例子

package main

import "fmt"

type HandlerFunc func(*Request) bool

type Request struct {
    url      string
    handlers []HandlerFunc
}

func (r *Request) Use(handlerFunc HandlerFunc) {
    r.handlers = append(r.handlers, handlerFunc)
}

func (r *Request) Run() {
    for _, handler := range r.handlers {
        if handler(r) {
            break
        }
    }
}

// 测试
// 输出 2 
func main() {
    r := &Request{}
    r.Use(func(r *Request) bool {
        return false
    })
    r.Use(func(r *Request) bool {
        fmt.Print(2, " ")
        return true
    })
    r.Use(func(r *Request) bool {
        fmt.Print(3, " ")
        return true
    })
    r.Run()
}

例子中定义了一个请求 Request,请求可以被多个 HandlerFunc 处理,如果某个 HandlerFunc 处理成功则结束处理,其中 HandlerFunc 可以通过 Request.Use() 方法进行添加。

不过责任链模式在实际使用时,更多的是另一种变体,请求会被串联的多个对象依次处理,而不是被某个对象处理后就停止处理了。类似这样

package main

import "fmt"

type HandlerFunc func(*Request)

type Request struct {
    url      string
    handlers []HandlerFunc
}

func (r *Request) Use(handlerFunc HandlerFunc) {
    r.handlers = append(r.handlers, handlerFunc)
}

func (r *Request) Run() {
    for _, handler := range r.handlers {
        handler(r)
    }
}

// 测试
// 输出 1 2 3
func main() {
    r := &Request{}
    r.Use(func(r *Request) {
        fmt.Print(1, " ")
    })
    r.Use(func(r *Request) {
        fmt.Print(2, " ")
    })
    r.Use(func(r *Request) {
        fmt.Print(3, " ")
    })
    r.Run()
}

注意:本文内容基于以下环境编写

  • Go 1.15.15
  • Gin 1.7.2
  • GORM 1.21.12

Gin 中责任链模式的使用

Gin 框架中间件的实现用到了责任链模式,责任链什么时候开始处理的呢?

在解决这个问题之前,我们先简单了解下 Gin 在整个 HTTP 请求处理过程中所扮演的角色。在不使用三方框架或者类库时,我们一般会直接使用 net/http 编写 HTTP 应用

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello World"))
    })

    if err := http.ListenAndServe(":8000", nil); err != nil {
        fmt.Println("start http server fail:", err)
    }
}

核心在于

func ListenAndServe(addr string, handler Handler) error

该方法会执行标准的 socket 连接过程,bind -> listen -> accept,当 accept 时启动新的协程处理客户端 socket,调用 Handler.ServeHTTP() 方法,处理结束后返回响应。

上面例子中传入的 Handler 为 nil,会默认使用 DefaultServeMux 作为 Handler,而 http.HandleFunc() 方法则是给 DefaultServeMux 添加一条路由以及对应的处理函数,当 DefaultServeMux.ServeHTTP() 被调用时,则会查找路由并执行对应的处理函数。因此,可以将 DefaultServeMux 的作用简单理解为路由注册、路由匹配、请求处理。

如果使用 Gin 呢,可以这样编写应用

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/", func(c *gin.Context) {
        c.String(200, "Hello World")
    })
    r.Run() // listen and serve on 0.0.0.0:8080
}

查看 r.Run() 方法

func (engine *Engine) Run(addr ...string) (err error) {
    // ...
    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    err = http.ListenAndServe(address, engine)
    return
}

可以发现 http.ListenAndServe() 方法使用了 engine 作为 Handler,即 engine 取代 DefaultServeMux 了来处理路由注册、路由匹配、请求处理等任务。现在直接看 Engine.ServeHTTP()

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    // ...

    engine.handleHTTPRequest(c)

    // ...
}
func (engine *Engine) handleHTTPRequest(c *Context) {
    // ...

    // Find root of the tree for the given HTTP method
    t := engine.trees
    for i, tl := 0, len(t); i < tl; i++ {
        // ...
        // Find route in tree
        value := root.getValue(rPath, c.params, unescape)
        if value.params != nil {
            c.Params = *value.params
        }
        if value.handlers != nil {
            c.handlers = value.handlers
            c.fullPath = value.fullPath
            c.Next()
            c.writermem.WriteHeaderNow()
            return
        }
        // ...
        break
    }

    // ...
}

上面方法可以暂时理解为路由匹配后得到了对应的 HandlerFunc 列表(表示注册的中间件或者路由处理方法,从这里可以看出所谓的中间件和路由处理方法其实是相同类型的),然后调用 c.Next() 开始处理责任链

func (c *Context) Next() {
    c.index++
    for c.index < int8(len(c.handlers)) {
        c.handlers[c.index](c)
        c.index++
    }
}

咋一看还不大好理解,我们改造下开始的例子

package main

import "fmt"

type HandlerFunc func(*Request)

type Request struct {
    url      string
    handlers []HandlerFunc
    index    int // 新增
}

func (r *Request) Use(handlerFunc HandlerFunc) {
    r.handlers = append(r.handlers, handlerFunc)
}

// 新增
func (r *Request) Next() {
    r.index++
    for r.index < len(r.handlers) {
        r.handlers[r.index](r)
        r.index++
    }
}

// 修改
func (r *Request) Run() {
    r.index = -1
    r.Next()
}

// 测试
// 输出 1 2 3 11
func main() {
    r := &Request{}
    r.Use(func(r *Request) {
        fmt.Print(1, " ")
        r.Next()
        fmt.Print(11, " ")
    })
    r.Use(func(r *Request) {
        fmt.Print(2, " ")
    })
    r.Use(func(r *Request) {
        fmt.Print(3, " ")
    })
    r.Run()
}

注意这几个点

  • 首先在 Request 结构体中新增了 index 属性,用于记录当前执行到了第几个 HandlerFunc
  • 然后新增 Next() 方法支持手动调用责任链中之后的 HandlerFunc
  • 最后修改 Run() 方法,通过调用 Next() 触发责任链处理

这个例子理解了后,Gin 的责任链触发也就基本清楚了。

另外需要注意的是,Gin 框架中 handlers 和 index 信息放在了 Context 里面

type Context struct {
    // ...
    handlers HandlersChain
    index    int8

    engine       *Engine
    // ...
}

其中,HandlersChain 就是一个 HandlerFunc 切片

type HandlerFunc func(*Context)

type HandlersChain []HandlerFunc

GORM 中责任链模式的使用

GORM 中增删改查都会涉及到责任链模式的使用,比如 Create()、Delete()、Update()、First() 等等,这里以 First() 为例跟下

func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB) {
    // ...
    return tx.callbacks.Query().Execute(tx)
}

tx.callbacks.Query() 返回 processor 对象,然后执行其 Execute() 方法

func (cs *callbacks) Query() *processor {
    return cs.processors["query"]
}
func (p *processor) Execute(db *DB) *DB {
    // ...

    for _, f := range p.fns {
        f(db)
    }

    // ...
}

就是在这个位置调用了与操作类型绑定的处理函数。嗯?操作类型是啥意思?对应的处理函数又有哪些?想解决这几个问题,需要搞清楚 callbacks 的定义、初始化、注册。

callbacks 定义如下

type callbacks struct {
    processors map[string]*processor
}

type processor struct {
    db        *DB
   // ...
    fns       []func(*DB)
    callbacks []*callback
}

type callback struct {
    name      string
    // ...
    handler   func(*DB)
    processor *processor
}

在注册完毕后,值类似这样

// ---- callbacks 结构体属性 ----
// processors
{
    "create": processorCreate,
    "query": ...,
    "update": ...,
    "delete": ...,
    "row": ...,
    "raw": ...,
}

// ---- processor 结构体属性(processorCreate) ----
// callbacks 中有 3 个 callback
{name: gorm:query, handler: Query}
{name: gorm:preload, handler: Preload}
{name: gorm:after_query, handler: AfterQuery}
// fns 对应 3 个 callback 中的 handler,不过是排过序后的

callbacks 在 callbacks.go/initializeCallbacks() 中进行初始化

func initializeCallbacks(db *DB) *callbacks {
    return &callbacks{
        processors: map[string]*processor{
            "create": {db: db},
            "query":  {db: db},
            "update": {db: db},
            "delete": {db: db},
            "row":    {db: db},
            "raw":    {db: db},
        },
    }
}

在 callbacks/callbacks.go/RegisterDefaultCallbacks 中进行注册(为了简洁,所贴代码只保留了 query 类型的回调注册)

func RegisterDefaultCallbacks(db *gorm.DB, config *Config) {
    // ...

    queryCallback := db.Callback().Query()
    queryCallback.Register("gorm:query", Query)
    queryCallback.Register("gorm:preload", Preload)
    queryCallback.Register("gorm:after_query", AfterQuery)
    if len(config.QueryClauses) == 0 {
        config.QueryClauses = queryClauses
    }
    queryCallback.Clauses = config.QueryClauses

    // ...
}

到这里,前面提出的 2 个问题也就迎刃而解了。所谓操作类型是指增删改查等操作,比如 create、delete、update、query 等等;每种操作类型绑定多个处理函数,比如 query 绑定了 Query()、Preload()、AfterQuery() 方法,其中 Query() 是核心方法,Preload() 实现预加载,AfterQuery() 类似一种 Hook 机制。

小结

责任链模式就是给请求创建一个对象链,链上的对象依次处理同一个请求,每个对象承担各自的处理责任。

Gin 中间件和 GORM 增删改查都涉及到了这种模式的使用。

这种模式有利于解耦请求对象和多个处理对象,处理对象只负责单一职责的功能,如果需要增加功能添加处理对象就行,扩展性好,便于维护。

Comments are closed.