一文詳解Golang的中間件設計模式
背景
記錄一下自己在go開發和學習上的一些筆記
最近在看一些rpc框架的使用原理和源碼的時候,對中間件的實現非常感興趣,然後也看瞭一下grpc的中間件的用法,也看瞭別的框架的中間件的設計,感覺grpc的還算是比較容易弄懂,於是記錄一下這個常用中間件的實現的一個原理的demo(吐槽一下其他的rpc框架分為inbound和outbound的middleware感覺好像有點復雜化瞭,所以我也不知道哪種設計會比較好,樓主是java出身,所以對反射走aop的那種模式比較熟悉,對鏈式調用的middleware有點不太熟悉,當然現在已經熟悉瞭)
Demo
所以接下來我們就來看看demo吧 首先先定義好中間件的類型,這裡我就簡單定義為以下的格式
type middleware func(ctx context.Context, req interface{}, handler endpoint) (resp interface{}, err error)
- ctx: 協程間通信帶著
- req: 請求的格式,這裡圖簡便,直接interface{}類型
- resp: 同req
- err: error
handler: endpoint類型,真正用來發起請求的一個處理方法或者是經過N層中間件包裝的後的發起請求的處理方法
type endpoint func(ctx context.Context, req interface{}) (resp interface{}, err error) //ctx: 協程間通信帶著 //req: 請求的格式,這裡圖簡便,直接interface{}類型 //resp: 同req //err: error
然後既然我們要將上方的endpoint
進行包裝然後產生一個新的endpoint
那麼也就是需要一個函數去做一步的事情,input是endpoint
,ouput也是endpoint
type warp func(endpoint) endpoint //就是這個warp函數
然後我們通過每次調用這個warp的定義去生成一個新的endpoint
就可以產生一個類似於dfs鏈式調用的一個中間件的過程,因為將會一層套一層的endpoint
下去,然後當最後一層有返回瞭以後就可以接著返回瞭,然後不斷的彈棧回去最開始的地方,因為我們中間件的實現必然是要調用handler的
func(ctx context.Context, req interface{}, handler endpoint) (resp interface{}, err error) { fmt.Printf("before1\n") resp, err = handler(ctx, req) fmt.Printf("end1\n") return }
// handler0 var handler endpoint = func(ctx context.Context, req interface{}) (resp interface{}, err error) { fmt.Printf("make msg\n") return nil, nil } // middleware var md Middleware = func(ctx context.Context, req interface{}, handler endpoint) (resp interface{}, err error) { fmt.Printf("before1\n") resp, err = handler(ctx, req) fmt.Printf("end1\n") return } // warp ->> handler1 handler = warp(func(e endpoint) endpoint { return func(ctx context.Context, req interface{}) (resp interface{}, err error) { return md(ctx, req, e) } })(handler)
驗證結論
package main import ( "context" "fmt" ) type endpoint func(ctx context.Context, req interface{}) (resp interface{}, err error) type middleware func(ctx context.Context, req interface{}, handler endpoint) (resp interface{}, err error) type warp func(endpoint) endpoint func main() { mds := []middleware{} mds = append(mds, func(ctx context.Context, req interface{}, handler endpoint) (resp interface{}, err error) { fmt.Printf("before1\n") resp, err = handler(ctx, req) fmt.Printf("end1\n") return }) mds = append(mds, func(ctx context.Context, req interface{}, handler endpoint) (resp interface{}, err error) { fmt.Printf("before2\n") resp, err = handler(ctx, req) fmt.Printf("end2\n") return }) var handler endpoint = func(ctx context.Context, req interface{}) (resp interface{}, err error) { fmt.Printf("make msg\n") return nil, nil } for i := len(mds) - 1; i >= 0; i-- { handler = warp(func(e endpoint) endpoint { // 由於go的機制問題如果不用tmp去存下當前的i,那麼mds[i]就會取最終的那一個,就會溢出,所以在return前先保存一下i的量,然後每一個stack去存的變量就是對的 cur := i return func(ctx context.Context, req interface{}) (resp interface{}, err error) { return mds[cur](ctx, req, e) } })(handler) } resp, err := handler(context.Background(), "ster") if resp != nil && err != nil { return } }
結果是
before1
before2
make msg
end2
end1
結論:感覺有函數指針的語言都可以用這一套去實現一個中間件
以上就是一文詳解Golang的中間件設計模式的詳細內容,更多關於Golang中間件設計模式的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- golang 實現一個負載均衡案例(隨機,輪訓)
- Golang 中反射的應用實例詳解
- Golang接口型函數使用小結
- golang在GRPC中設置client的超時時間
- 在 Golang 中實現一個簡單的Http中間件過程詳解