业务逻辑
创建接口
type StringService interface {
Uppercase(string) (string, error)
Count(string) int
}
根据j接口实现业务逻辑
type stringService struct{}
func (stringService) Uppercase(s string) (string, error) {
if s == "" {
return "", ErrEmpty
}
return strings.ToUpper(s), nil
}
func (stringService) Count(s string) int {
return len(s)
}
// ErrEmpty is returned when input string is empty
var ErrEmpty = errors.New("Empty string")
请求和返回
在Go kit中,主要的消息传递模式是RPC。因此,接口中的每个方法都将建模为远程方法调用。对于每个方法,我们都需要定义请求和响应体结构,以此表示请求和返回参数。
Endpoints
Go kit通过一个称为抽象的endpoint来提供其大部分功能。
端点的定义如下(你不必把它放在代码中的任何地方,它由go-kit提供):
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
它代表一个RPC。也就是说,我们的服务接口中只有一个方法。我们将编写简单的适配器,将每个服务方法转换为一个端点。每个适配器接受一个StringService,并返回与其中一个方法对应的端点。
// Endpoints are a primary abstraction in go-kit. An endpoint represents a single RPC (method in our service interface)
func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(uppercaseRequest)
v, err := svc.Uppercase(req.S)
if err != nil {
return uppercaseResponse{v, err.Error()}, nil
}
return uppercaseResponse{v, ""}, nil
}
}
func makeCountEndpoint(svc StringService) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(countRequest)
v := svc.Count(req.S)
return countResponse{v}, nil
}
}
Transports
现在我们需要向外部公开您的服务,以便可以调用它。您的组织可能已经对服务应该如何相互通信有了自己的看法。也许您使用Thrift,或者通过HTTP定制JSON。Go kit支持许多现成的Transports。
对于这个最小的示例服务,让我们使用HTTP上的JSON。Go kit提供了一个helper结构,在package transport/http中。
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)
log.Fatal(http.ListenAndServe(":8080", nil))
}
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
}
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
}
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
return json.NewEncoder(w).Encode(response)
}
运行程序
go run main.go
测试
➜ stringsvc1 git:(master) ✗ curl -XPOST -d'{"s":"hello, world"}' localhost:8080/uppercase
{"v":"HELLO, WORLD"}
➜ stringsvc1 git:(master) ✗ curl -XPOST -d'{"s":"hello, world"}' localhost:8080/count
{"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实现中,但是有一种更好的方法。让我们使用中间件,也称为装饰器。中间件是一个接受端点并返回端点的函数。