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

Github地址
如標題所描述的 , Droplet 是一個 輕量 的 中間層框架,何為中間層呢?通常來說,我們的程序(注意這里我們僅僅討論程序的范圍,而非作為一個系統,因此這里不設計如 LB、Gateway、Mesh等內容 , 因為它們都處于程序以外)按不同的職責可以分為不同的層次,而按照不同的設計風格,常見的如下:
  • 三層架構:UIL(UserInterfaceLayer), BLL(BusinessLogicLayer), DAL(DataAccessLayer)
  • DDD分層架構(參考ddd-oriented-microservice):ApplicationLayer , DomainLayer,InfrastructureLayer
  • 洋蔥架構(參考Onion Architecture ):Application, Infrastructure, ApplicationService, DomainService, DomainModel 。
Tips
洋蔥架構其實也是基于DDD的,它是DDD分層架構的升級版本 。
但是今天我想用于解釋中間層的架構并非以上的任何一種 , 它也源自于DDD的分層架構 , 不過我配合了六邊形架構來說明它 , 分層圖如下:
Droplet——一款輕量的Golang應用層框架

文章插圖
在六邊形架構中有個規則:依賴只能是由外部指向內部 。因此從外層到最內層分別是:
分層職責Access程序的接入層(在六邊形架構中這被稱為輸入適配器) , 通常位于整個請求 or 任務的起點,它可能是某種Web框架,也可能是一些隊列的消費框架等 。Application程序應用層 , 包含了一些非業務的邏輯 , 如:業務邏輯的編排、參數綁定、校驗、請求日志、鏈路上報、狀態讀取等等Domain & Utils在最中心的地方我放入了兩個層次描述:Domain 與 Utils , 這兩個分層都應該是位于依賴的最底層,意味著他們不應該引用本項目的其他層次 。Domain層主要包含核心的業務邏輯,而Utils則是一些程序任何地方都可能會引用的代碼段 , 比如常量定義、數據結構和語法糖等等Infrastructure基礎設施層(在六邊形架構中這被稱為輸出適配器) , 程序所有需要對外進行信息交換 or 功能依賴時都會放置在這一層實現,通常來說這些功能都是被依賴的那部分,因此我們如果要滿足依賴約束的話 , 這里必須要引入 DIP(Dependency inversion principle) , 即在Application、Domain中定義依賴,而 Infrastructure 來實現它們,這樣保證了它們是可被替換的六邊形架構優點在于解耦程序中業務無關的部分,以保證它們都是可被替換與擴展的 。而 Droplet 就工作在 Application 層,它的核心能力只有一個:提供基于pipeline的請求/響應處理能力 ??赡苡腥藭蓡枺瑤缀趺總€框架都會實現類似的能力,為什么我們需要 Droplet 呢?別急,我們來看看這些框架自帶的 pipeline/middleware 存在什么弊端 。根據上面的架構圖我們可以知道諸如 gin、go-restful、fasthttp 之類的http框架都是工作在 Access 層,因此框架自帶的 pipeline/middleware 存在以下兩個弊端:
  1. 框架綁定: 這個很容易理解,這些機制只能工作于特定的框架下,如果切換框架則需要需要調整代碼 , 除了中間件的代碼外 , 我時常也會見到程序在 API Handler 中耦合了大量框架相關的代碼,比如:讀取參數(header, query, body等)、根據業務結果回寫響應等 , 這些代碼滲透到了業務程序中(有時它們甚至會比業務代碼占用了更多的行數) , 這加大了業務開發同學的維護成本 , 同時也降低了程序的可擴展性 。
一些相關的BadCase
想象一下:
  • 你一直在使用 gin,但是有一天運營拿著數據找到你,說機器占用的成本太高了,而你發現只要切換到 fasthttp 就能為你帶來更高的性能,但是從 gin -> fasthttp 你需要調整大量的 API handler 代碼,這可太讓人頭疼了 。
  • API handler中充斥了諸如 param, ok := req.Query("param") / param, ok := req.Header.Get("param") / err := xxx.Bind(req, &param) 之類的代碼,這和業務毫無關系
  1. 沒有請求/響應的結構化實體: 如果有開發過這些框架中間件的同學一定知道,大部分框架中間件的協議定義都是以 http.Request/httpResponse 為主體的 , 這意味著如果不做任何前置處理 , 你只能通過字節數組來感知 請求與響應 這在部分場景都不太方便,比如:根據請求、響應的結構體是否具備某些特征(比如接口)來執行某些特定的業務通用邏輯;又或者想在中間件中融入一些自動化的參數校驗邏輯,因為你沒有一個具體的結構化對象;再或者你不想要在每一個 API handler 中去設置一個響應的 Wrapper(通常它類似于

    推薦閱讀