业务逻辑

创建接口

  1. type StringService interface {
  2. Uppercase(string) (string, error)
  3. Count(string) int
  4. }

根据j接口实现业务逻辑

  1. type stringService struct{}
  2. func (stringService) Uppercase(s string) (string, error) {
  3. if s == "" {
  4. return "", ErrEmpty
  5. }
  6. return strings.ToUpper(s), nil
  7. }
  8. func (stringService) Count(s string) int {
  9. return len(s)
  10. }
  11. // ErrEmpty is returned when input string is empty
  12. var ErrEmpty = errors.New("Empty string")

请求和返回

在Go kit中,主要的消息传递模式是RPC。因此,接口中的每个方法都将建模为远程方法调用。对于每个方法,我们都需要定义请求和响应体结构,以此表示请求和返回参数。

Endpoints

Go kit通过一个称为抽象的endpoint来提供其大部分功能。
端点的定义如下(你不必把它放在代码中的任何地方,它由go-kit提供):

  1. type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)

它代表一个RPC。也就是说,我们的服务接口中只有一个方法。我们将编写简单的适配器,将每个服务方法转换为一个端点。每个适配器接受一个StringService,并返回与其中一个方法对应的端点。

  1. // Endpoints are a primary abstraction in go-kit. An endpoint represents a single RPC (method in our service interface)
  2. func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint {
  3. return func(_ context.Context, request interface{}) (interface{}, error) {
  4. req := request.(uppercaseRequest)
  5. v, err := svc.Uppercase(req.S)
  6. if err != nil {
  7. return uppercaseResponse{v, err.Error()}, nil
  8. }
  9. return uppercaseResponse{v, ""}, nil
  10. }
  11. }
  12. func makeCountEndpoint(svc StringService) endpoint.Endpoint {
  13. return func(_ context.Context, request interface{}) (interface{}, error) {
  14. req := request.(countRequest)
  15. v := svc.Count(req.S)
  16. return countResponse{v}, nil
  17. }
  18. }

Transports

现在我们需要向外部公开您的服务,以便可以调用它。您的组织可能已经对服务应该如何相互通信有了自己的看法。也许您使用Thrift,或者通过HTTP定制JSON。Go kit支持许多现成的Transports。
对于这个最小的示例服务,让我们使用HTTP上的JSON。Go kit提供了一个helper结构,在package transport/http中。

  1. func main() {
  2. svc := stringService{}
  3. uppercaseHandler := httptransport.NewServer(
  4. makeUppercaseEndpoint(svc),
  5. decodeUppercaseRequest,
  6. encodeResponse,
  7. )
  8. countHandler := httptransport.NewServer(
  9. makeCountEndpoint(svc),
  10. decodeCountRequest,
  11. encodeResponse,
  12. )
  13. http.Handle("/uppercase", uppercaseHandler)
  14. http.Handle("/count", countHandler)
  15. log.Fatal(http.ListenAndServe(":8080", nil))
  16. }
  17. func decodeUppercaseRequest(_ context.Context, r *http.Request) (interface{}, error) {
  18. var request uppercaseRequest
  19. if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
  20. return nil, err
  21. }
  22. return request, nil
  23. }
  24. func decodeCountRequest(_ context.Context, r *http.Request) (interface{}, error) {
  25. var request countRequest
  26. if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
  27. return nil, err
  28. }
  29. return request, nil
  30. }
  31. func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
  32. return json.NewEncoder(w).Encode(response)
  33. }

运行程序

  1. go run main.go

测试

  1. stringsvc1 git:(master) curl -XPOST -d'{"s":"hello, world"}' localhost:8080/uppercase
  2. {"v":"HELLO, WORLD"}
  3. stringsvc1 git:(master) curl -XPOST -d'{"s":"hello, world"}' localhost:8080/count
  4. {"v":12}

Middlewares

如果没有完整的日志记录和检测工具,就不能认为服务是可用于生产的。

Transport logging

Any component that needs to log should treat the logger like a dependency, same as a database connection. So, we construct our logger in our func main, and pass it to components that need it. We never use a globally-scoped logger.
We could pass a logger directly into our stringService implementation, but there’s a better way. Let’s use a middleware, also known as a decorator. A middleware is a function that takes an endpoint and returns an endpoint.

任何需要记录日志的组件都应该将记录器视为依赖项,就像数据库连接一样。因此,我们在main函数中构造logger,并将它传递给需要它的组件。我们从不使用全局范围的记录器。
我们可以直接将记录器传递到stringService实现中,但是有一种更好的方法。让我们使用中间件,也称为装饰器。中间件是一个接受端点并返回端点的函数。

参考

https://github.com/ifconfigure/go-kit-grpc-demo