楚河漢界

非功能性需求代碼,和功能性需求的實現代碼中間要畫一根線。這樣當我們去理解業務邏輯的時候不會被非功能性需求的代碼所干擾。而非功能性需求的實現要升級也會比較容易,不用去到處改代碼。

這裡的邊界包括:

  • 對外暴露成可調用的伺服器
  • 通過客戶端調用外部的服務
  • 用expvar或者日誌進行埋點(提供 observability)
  • 集群管理通道(註冊和管理後門)
  • 支持推送更新的配置緩存

v2pro/plz 按照這個邊界劃分,給 Go 的代碼提供了一個比標準庫更高一級的基礎 api/spi。起到的作用就是給分散式的服務開發提供一個標準庫。在這些邊界中,以伺服器和客戶端的邊界最難以畫清楚。

比較理想的介面是這樣的

type MyRequest struct { // ...}type MyResponse struct { // ...}func sayHello(ctx *countlog.Context, req *MyReqeust) (*MyResponse, error) { // ...}

功能性需求代碼只需要關注我對外提供的服務是這樣的一個介面。至於是暴露在了什麼tcp埠上,用的是 http/JSON 還是 thrift,是否有限流,這些都是非功能性需求的代碼和配置需要關注的事情。

利用 v2pro/plz.service 我們可以把介面定義成這樣的了。從而把楚河漢界劃分得一清二楚。

如果是啟動成 http 服務,則是這樣的

func sayHello(ctx *countlog.Context, req *MyReqeust) (*MyResponse, error) { // ...}server := http.NewServer()server.Handle("/sayHello", sayHello)server.Start("127.0.0.1:9998")

如果是啟動成 thrift 服務,則是這樣的

func sayHello(ctx *countlog.Context, req *MyReqeust) (*MyResponse, error) { // ...}server := thrift.NewServer(thrifter.Config{Protocol: thrifter.ProtocolBinary, IsFramed: true}.Froze())server.Handle("sayHello", sayHello)server.Start("127.0.0.1:9998")

我們可以看到,參數綁定這樣的事情從業務代碼里划走了,而且一份代碼可以同時暴露成 http 服務和 thrift 服務了。

對於客戶端介面,也是一樣的。我們的代碼里依賴的客戶端定義成這樣:

var sayHello = func (ctx *countlog.Context, req *MyReqeust) (*MyResponse, error)

使用的時候,就把 sayHello 當成一個函數來使用就行了。至於這個函數調用的背後是 http 服務,還是 thrift 服務,是走了服務發現還是固定ip埠,是有負載均衡還是沒有,這些都是非功能性需求。

比如,利用 v2pro/plz.service 調用 http 服務

var sayHello = func (ctx *countlog.Context, req *MyReqeust) (*MyResponse, error)client := http.NewClient()client.Handle("POST", "http://127.0.0.1:9998/sayHello", &sayHello)// use sayHello(...) to call server

或者調用 thrift 服務

var sayHello = func (ctx *countlog.Context, req *MyReqeust) (*MyResponse, error)client := thrift.NewClient(thrifter.Config{Protocol: thrifter.ProtocolBinary, IsFramed: true}.Froze())client.Handle("127.0.0.1:9998", "sayHello", &sayHello)// use sayHello(...) to call server

通過不同的 client,給 sayHello 這個函數指針綁定了不同的實現。對於功能性需求的代碼來說,無論你外面怎麼升級,都不會影響業務邏輯的寫法。

從內部實現的角度來說。這裡的 client/server 都沒有使用 reflect 來進行函數調用,省去了反射的開銷。無論是 client 還是 server 的 handler,在內部實現里都是轉換成同一個函數簽名來調用的:

// Handler is the function prototype for both client and server.// User should substitute request and response with their own concrete types.// For example func(ctx *countlog.Context, request NewOrderRequest) (NewOrderResponse, error)type Handler func(ctx *countlog.Context, request unsafe.Pointer) (response unsafe.Pointer, err error)

推薦閱讀:

TAG:Go語言 | Go編程 | 編程 |