go-kit|go-kit 上手之example stringsvc1 函数即服务


    • 使用包
    • 服务模型定义
    • 接口实现
    • 定义RPC输入输出参数
    • endpoint封装
    • 从Request解码输入参数编码输出到ResponseWriter
    • 服务启动
    • 测试结果
    • 增加log

go-kit stringsvc1动手笔记
go-kit官网
go-kit官方示例代码
使用包
package mainimport "context" import "errors" import "strings" import "github.com/go-kit/kit/endpoint" import httptransport "github.com/go-kit/kit/transport/http" import "encoding/json" import "net/http" import "github.com/go-kit/kit/log" import "os"

服务模型定义 //业务逻辑 在go-kit中,我们把服务模型定义为一个接口,接口中的方法代表了服务提供的功能:如下
//该服务提供两个功能:字符串大写化,计算字符串中字符数
type StringService interface { Uppercase(context.Context, string) (string, error) Count(context.Context, string) int }

【go-kit|go-kit 上手之example stringsvc1 函数即服务】接口定义好后就需要定义结构体实现这个接口,也就是真是的业务处理。
接口实现
var ErrEmpty = errors.New("empty string")type stringService struct { }//真正处理Uppercase业务 func (stringService) Uppercase(_ context.Context, s string) (string, error) { if s == "" { return "", ErrEmpty }return strings.ToUpper(s), nil }//真正处理Count业务 func (stringService) Count(_ context.Context, s string) int { return len(s) }

定义RPC输入输出参数 //go kit中主要的消息传递方式为RPC,所以接口中的每一个方法都要用 remote procedure call 实现。
//对每一个方法,我们要定义对应的request和response方法,request用来获取入参,response用来传递输出参数。
定义Uppercase的输入参数的结构
type uppercaseRequest struct { S string `json:"s"` }

定义Uppercase的输出接口
type uppercaseResponse struct { Vstring `json:"v"` Err string `json:"err,omitempty"` }

定义Count的输入参数结构
type countRequest struct { S string `json:"s"` }

定义Count的输入结构
type countResponse struct { V int `json:"v"` }

endpoint封装 go-kit中,如果使用go-kit/kit/transport/http,那么还需要把StringService封装为endpoint来供调用。
抽象 uppercase的RPC调用
func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(uppercaseRequest) v, err := svc.Uppercase(ctx, req.S) if err != nil { return uppercaseResponse{v, err.Error()}, nil } return uppercaseResponse{v, ""}, nil } }

抽象 len的RPC调用
func makeCountEndpoint(svc stringService) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(countRequest) v := svc.Count(ctx, req.S) return countResponse{v}, nil } }

可以看到,上述封装其实就是把RPC调用封装成了更加通用的接口,输入参数和输出参数都为interface
从Request解码输入参数,编码输出到ResponseWriter
根据go-kit/kit/transport/http ServerHttp中的代码request, err := s.dec(ctx, r)//将请求转为对应的输入参数 if err != nil { s.logger.Log("err", err) s.errorEncoder(ctx, err, w) return }//执行对应功能 response, err := s.e(ctx, request) if err != nil { s.logger.Log("err", err) s.errorEncoder(ctx, err, w) return }for _, f := range s.after { ctx = f(ctx, w) }//将输入参数放入到response if err := s.enc(ctx, w, response); err != nil { s.logger.Log("err", err) s.errorEncoder(ctx, err, w) return }

可以看出网络请求到达后服务器主要进行三步处理 :
- s.dec(ctx, r) 将request中提取输入参数
- s.e(ctx, request) 传入输入参数执行对应业务功能
- s.enc(ctx, w, response) 将业务处理结构返回到ResponseWriter,从而传递给客户端
第二步就是调用的是上面生成的endpoint,第一步需要我们传入解码器,用于将Request解码为输入参数,第三部需要我们传入编码器,输出到ResponseWriter。
Uppercase输入解码器
func decodeUpperCaseRequest(_ context.Context, r *http.Request) (interface{}, error) { var request uppercaseRequest if err := json.NewDecoder(r.Body).Decode(&request); err != nil { return nil, err } return request, nil }

Count输入解码器
func decodeCountRequest(_ context.Context, r *http.Request) (interface{}, error) { var request countRequest if err := json.NewDecoder(r.Body).Decode(&request); err != nil { return nil, err } return request, nil }

由于Uppercase和Count对输出的处理一样,所以可以用一个通用的编码器,将结果写入到ResponseWriter
func encodeResponse(_ context.Context, w http.ResponseWriter, respon interface{}) error { return json.NewEncoder(w).Encode(respon) }

服务启动
func main() {svc := stringService{} uppercaseHandler := httptransport.NewServer( makeUppercaseEndpoint(svc), decodeUpperCaseRequest, encodeResponse, )countHandler := httptransport.NewServer( makeCountEndpoint(svc), decodeCountRequest, encodeResponse, ) http.Handle("/uppercase", uppercaseHandler) http.Handle("/count", countHandler) http.ListenAndServe(":8082", nil) }

测试结果
Sean-MacBook-Air:goproject kes$ curl -XPOST -d'{"s":"Hello"}' localhost:8081/count {"v":5} Sean-MacBook-Air:goproject kes$ curl -XPOST -d'{"s":"Hello"}' localhost:8081/uppercase {"v":"HELLO"}

增加log 如果一个系统中业务很多,怎么才能快速的添加日志到每个业务里呢?
go-kit中的Middlewre可以让开发者快速的增加新的需求到原业务中,并且支持功能的串联,这都是通过 endpoint.Middleware实现的。
go-kit中 endpoint.Middleware定义如下:
type Middleware func(Endpoint) Endpoint

我们可以定义一个日志Middleware,将一个Endpoint封装为加入了日志功能的另外一种Endpoint。
func loggingMiddleware(logger log.Logger) endpoint.Middleware {return func(next endpoint.Endpoint) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { logger.Log("msg", "calling endpoint") defer logger.Log("msg", "callend endpoing") return next(ctx, request) } } }

然后利用loggingMiddleware对UppercaseEndpoin和CountEndpoint进行封装
func main() { logger := log.NewLogfmtLogger(os.Stderr) mid := loggingMiddleware(log.With(logger, "LOGMETHOD", "uppercase")) svc := stringService{} uppercaseHandler := httptransport.NewServer( mid(makeUppercaseEndpoint(svc)), decodeUpperCaseRequest, encodeResponse, )countHandler := httptransport.NewServer( mid(makeCountEndpoint(svc)), decodeCountRequest, encodeResponse, ) http.Handle("/uppercase", uppercaseHandler) http.Handle("/count", countHandler) http.ListenAndServe(":8082", nil) }

请求
Sean-MacBook-Air:sringsrv1 kes$ curl -XPOST -d'{"s":"Hello"}' localhost:8082/count {"v":5} Sean-MacBook-Air:sringsrv1 kes$ curl -XPOST -d'{"s":"Hello"}' localhost:8082/uppercase {"v":"HELLO"}

服务器打印
go-kit|go-kit 上手之example stringsvc1 函数即服务
文章图片

    推荐阅读