Droplet——一款輕量的Golang應用層框架( 二 )

{code: 0, msg: "", data:{}}) , 想要在中間件去自動包裝上它,也很難執行;最后就是——如果只依靠 http.Request/httpResponse,你也難在中間件感知到其他參與者的處理狀態,。相信我說的這些問題,使用過的同學應該都有所感觸 , 而這些問題并非難以解決 , 它們中的大部分基本都是可以通過自行建立一套約定來得以緩解(比如將這些信息都通過 context 去獲取),而 Droplet 也是誕生于我在過往團隊中去克服這些問題的實踐之中,是一個相對可靠的實現 。
工作原理帶著上面提到的這些問題,我們來看看 Droplet 的工作原理是怎樣的 , 如下圖所示:

Droplet——一款輕量的Golang應用層框架

文章插圖
如我所說的那樣,Droplet 的核心在于 提供基于pipeline的請求/響應處理能力,因此我們可以看見這個圖中涉及的所有模塊都是基于 pipleline,可以說 Droplet 的所有能力都是由其擴展而來 。這里我們先介紹下圖中出現的幾個中間件(Middleware,這是組成 pipepine 關鍵元素):
  1. HttpInfoInjector: 注入 http 相關的一些信息,如 requestid, http.Request 等
  2. RespReshape: 根據 handler 的響應結果來進行一些調整 , 包括:發生錯誤時設置上默認的錯誤碼、錯誤信息;如果缺少響應 wrapper 時包裝上配置好的 wrapper
  3. HttpInput: 如果你設置了 API 的輸入參數類型,那么該中間件會自動根據 Content-Type、 struct tag 來讀取對應的參數值,同時自動使用 validator 來檢測參數錯誤
  4. TrafficLog: 如名字所示,如果你配置了響應的 logger,那么該中間件會執行日志記錄 。請注意該中間件工作在其他默認中間件的后面、你的handler之前,因此它統計的耗時是你業務函數的真正耗時,而不包含其他中間件的耗時時間,你可以考慮通過 網關 或 Mesh 來記錄完整的接口耗時 。
Tips
  • Middleware 處理請求和響應順序是相反的——即第一個處理請求的中間件它會是最后一個處理響應的 。
  • 框架工作在應用層的優勢有兩點:
    • 與接入層框架解耦,保證絕項目代碼可平滑 擴展/切換 其他接入層框架
    • 能夠獲取到結構化的接口 輸入參數 與 輸出參數 你可以對其進行更具精細的切面操作
GetStart
這里以 Gin 為例,其他框架類似 。
首先獲取對應 wrapper 的 submodule:
go get github.com/shiningrush/droplet/wrapper/gin// if you want to ensure the droplet is latest, you can get dropletgo get github.com/shiningrush/droplet然后程序代碼如下:
package mainimport ( "reflect" "github.com/gin-gonic/gin" "github.com/shiningrush/droplet/core" "github.com/shiningrush/droplet/wrapper" ginwrap "github.com/shiningrush/droplet/wrapper/gin")func main() { r := gin.Default()// 使用 wrapper 包裝原始的 API r.POST("/json_input/:id", ginwrap.Wraps(JsonInputDo, wrapper.InputType(reflect.TypeOf(&JsonInput{})))) r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")}type JsonInput struct {// 從 path 讀取, 并且為必須參數 IDstring`auto_read:"id,path" json:"id" validate:"required"`// 從 header 讀取, 并且為必須參數 Userstring`auto_read:"user,header" json:"user" validate:"required"`// 從 json unmarshal 后的ips字段讀取 IPs[]string `json:"ips"`// 從 json unmarshal 后的 count 字段讀取 Count int`json:"count"`// 讀取原始的 http body , 接收參數類型必須為 []byte or io.ReadCloser Body[]byte`auto_read:"@body"`}func JsonInputDo(ctx core.Context) (interface{}, error) { input := ctx.Input().(*JsonInput) return input, nil}參數綁定如 Usage 一節中所展示的,我們可以通過 wrapper.InputType 選項來告訴 Droplet 是否期望自動化進行參數綁定,如果某些場景下你不需要從 Body 進行自動的參數綁定了,可以通過顯式的選項來禁止它 , 如:
r.POST("/json_input/:id", ginwrap.Wraps(JsonInputDo, wrapper.InputType(reflect.TypeOf(&JsonInput{}), wrapper.DisableUnmarshalBody())))參數綁定的Tag格式如下:
auto_read: ({key},{source}) or @body其中值如下:
  • key: 用于到各個來源中匹配對應值
  • source: 可選值有 query, header, path, body(缺省默認)
  • @body: 特殊的取值 , 意味著獲取原生的body作為字段值,此時你的字段類型應該為 []byte or io.ReadCloser
同時 Droplet 會自動使用 validator 對入參進行校驗,因此你可以使用其 tag 來輔助驗證參數合法性 。
響應整形通常來說API都會在響應的最外層進行一層包裝,比如 Droplet 自帶的 wrapper 如下所示:

推薦閱讀