本文將使用gokit構建一個簡單的算術運算(兩個整數的加減乘除運算)微服務例項,該服務將以REST方式對外暴露介面,具體要求如下:
- 使用gokit構建REST介面;
- URL格式為:
/calculate/{type}/{a}/{b}
,請求方法為POST
;
Step-0:準備工作
- golang開發環境(我的環境go1.11.4+Goland+Windows 10);
- gokit工具集:
go get github.com/go-kit/kit
; - http請求路由元件:
go get github.com/gorilla/mux
;
golang開發環境搭建方式可以自行搜尋,IDE可以根據個人喜好選擇。
Step-1:建立Service
按照gokit的設計理念,Service將作為核心業務邏輯實現部分。所以,該Service將用於實現本文開頭所說的兩個整數之間的加減乘除運算,其中包含4個方法分別用於加減乘除計算。
在GOPATH
下建立目錄gokit-article-demo/arithmetic_rest_demo
,然後新建go檔案service.go
,定義介面Service
,程式碼如下所示:
// Service Define a service interface
type Service interface {
// Add calculate a+b
Add(a, b int) int
// Subtract calculate a-b
Subtract(a, b int) int
// Multiply calculate a*b
Multiply(a, b int) int
// Divide calculate a/b
Divide(a, b int) (int, error)
}
複製程式碼
接下來建立結構ArithmeticService
實現Service
介面。加減乘除的實現非常簡單,只有除法運算需要做下異常判斷,程式碼如下所示:
//ArithmeticService implement Service interface
type ArithmeticService struct {
}
// Add implement Add method
func (s ArithmeticService) Add(a, b int) int {
return a + b
}
// Subtract implement Subtract method
func (s ArithmeticService) Subtract(a, b int) int {
return a - b
}
// Multiply implement Multiply method
func (s ArithmeticService) Multiply(a, b int) int {
return a * b
}
// Divide implement Divide method
func (s ArithmeticService) Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("the dividend can not be zero!")
}
return a / b, nil
}
複製程式碼
Step-2:建立請求、響應模型
請求模型:接收http客戶端的請求後,把請求引數轉為請求模型物件,用於後續業務邏輯處理。觀察Service介面可以發現四個介面方法的輸入引數均為兩個整數,區別在於運算型別不同,所以請求模型只需包含三個欄位,即:請求型別、第一個整數、第二個整數。
響應模型:用於向客戶端響應結果。對於響應模型可以設定兩個欄位:一是結果,用於表示正常情況下的運算結果;二是錯誤描述,用於表示異常時的錯誤描述。
建立go檔案endpoints.go
,編寫如下程式碼:
// ArithmeticRequest define request struct
type ArithmeticRequest struct {
RequestType string `json:"request_type"`
A int `json:"a"`
B int `json:"b"`
}
// ArithmeticResponse define response struct
type ArithmeticResponse struct {
Result int `json:"result"`
Error error `json:"error"`
}
複製程式碼
Step-3:建立Endpoint
在gokit中Endpoint是可以包裝到http.Handler
中的特殊方法,gokit採用裝飾著模式,把Service應該執行的邏輯封裝到Endpoint方法中執行。Endpoint的作用是:呼叫Service中相應的方法處理請求物件(ArithmeticRequest),返回響應物件(ArithmeticResponse)。接下來在endpoints.go
檔案中增加以下程式碼:
// MakeArithmeticEndpoint make endpoint
func MakeArithmeticEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(ArithmeticRequest)
var (
res, a, b int
calError error
)
a = req.A
b = req.B
if strings.EqualFold(req.RequestType, "Add") {
res = svc.Add(a, b)
} else if strings.EqualFold(req.RequestType, "Substract") {
res = svc.Subtract(a, b)
} else if strings.EqualFold(req.RequestType, "Multiply") {
res = svc.Multiply(a, b)
} else if strings.EqualFold(req.RequestType, "Divide") {
res, calError = svc.Divide(a, b)
} else {
return nil, ErrInvalidRequestType
}
return ArithmeticResponse{Result: res, Error: calError}, nil
}
}
複製程式碼
Step-4:建立Transport
Transport層用於接收使用者網路請求並將其轉為Endpoint可以處理的物件,然後交由Endpoint執行,最後將處理結果轉為響應物件向使用者響應。為了完成這項工作,Transport需要具備兩個工具方法:
- 解碼器:把使用者的請求內容轉換為請求物件(ArithmeticRequest);
- 編碼器:把處理結果轉換為響應物件(ArithmeticResponse);
下面建立新的go檔案,命名為transports.go
,並新增如下程式碼:
// decodeArithmeticRequest decode request params to struct
func decodeArithmeticRequest(_ context.Context, r *http.Request) (interface{}, error) {
vars := mux.Vars(r)
requestType, ok := vars["type"]
if !ok {
return nil, ErrorBadRequest
}
pa, ok := vars["a"]
if !ok {
return nil, ErrorBadRequest
}
pb, ok := vars["b"]
if !ok {
return nil, ErrorBadRequest
}
a, _ := strconv.Atoi(pa)
b, _ := strconv.Atoi(pb)
return ArithmeticRequest{
RequestType: requestType,
A: a,
B: b,
}, nil
}
// encodeArithmeticResponse encode response to return
func encodeArithmeticResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
w.Header().Set("Content-Type", "application/json;charset=utf-8")
return json.NewEncoder(w).Encode(response)
}
複製程式碼
decodeArithmeticRequest
從使用者請求中解析請求引數type、a、b,並將三個引數轉換為請求物件ArithmeticRequest
;encodeArithmeticResponse
把響應內容轉為json結構,向使用者回寫響應內容。完成以上工作後,就可以使用解碼器、編碼器建立HTTP處理方法了,程式碼如下所示:
var (
ErrorBadRequest = errors.New("invalid request parameter")
)
// MakeHttpHandler make http handler use mux
func MakeHttpHandler(ctx context.Context, endpoint endpoint.Endpoint, logger log.Logger) http.Handler {
r := mux.NewRouter()
options := []kithttp.ServerOption{
kithttp.ServerErrorLogger(logger),
kithttp.ServerErrorEncoder(kithttp.DefaultErrorEncoder),
}
r.Methods("POST").Path("/calculate/{type}/{a}/{b}").Handler(kithttp.NewServer(
endpoint,
decodeArithmeticRequest,
encodeArithmeticResponse,
options...,
))
return r
}
複製程式碼
Step-5:編寫Main方法
到目前為止,我們已經為該服務完成了Service、Endpoint、Transport三個層次的構建工作,只需要通過main方法將它們按照gokit的要求組織起來,然後使用http庫將服務釋出即可。組織步驟如下:
- 建立Service物件:宣告Service介面,例項化為ArithmeticService;
- 建立Endpoint物件;
- 建立http處理物件handlder;
- 啟動http服務;
建立go檔案main.go
新增以下程式碼:
func main() {
ctx := context.Background()
errChan := make(chan error)
var svc Service
svc = ArithmeticService{}
endpoint := MakeArithmeticEndpoint(svc)
var logger log.Logger
{
logger = log.NewLogfmtLogger(os.Stderr)
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
logger = log.With(logger, "caller", log.DefaultCaller)
}
r := MakeHttpHandler(ctx, endpoint, logger)
go func() {
fmt.Println("Http Server start at port:9000")
handler := r
errChan <- http.ListenAndServe(":9000", handler)
}()
go func() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
errChan <- fmt.Errorf("%s", <-c)
}()
fmt.Println(<-errChan)
}
複製程式碼
Step-6:編譯&執行
在控制檯中開啟專案所在的目錄,執行指令go build
,然後執行可執行檔案arithmetic_rest_demo.exe
,看到以下內容就表明服務啟動成功了:
Http Server start at port:9000
複製程式碼
這個時候就可以通過PostMain進行測試了,效果如下所示:
本文首發於本人微信公眾號【兮一昂吧】,歡迎掃碼關注!