kit是一个能够自动生成基于go-kit组件的框架,可以帮助我们快速创建微服务,而让我们只关注自己的业务逻辑和中间件实现。
仓库地址 github.com/kujtimiihoxha/kit
// 1. 创建一个service模板
kit new service hello //kit n s hello
------
// 2. 编辑hello/pkg/service/service.go添加service
type HelloService interface {
// Add your methods here
// e.x: Foo(ctx context.Context,s string)(rs string, err error)
Foo(ctx context.Context, s string) (rs string, err error)
Test(ctx context.Context, s string, i int64) (string, error)
}
// 3. 创建transport、endpoint、service层
kit g s hello --dmw
//生成service目录结构
hello
├── cmd
│ ├── service
│ │ ├── service.go
│ │ └── service_gen.go
│ └── main.go
└── pkg
├── endpoint //endpoint layer
│ ├── endpoint.go
│ ├── endpoint_gen.go
│ └── middleware.go
├── http //transport layer
│ ├── handler.go
│ └── handler_gen.go
└── service //service layer
├── middleware.go
└── service.go
// 4. 生成client library
kit g c hello
// 生成client目录结构
hello
└── client
└── http
└── http.go //client library
主要讲讲kit生成的transport层、endpoint层、service层分别是怎么实现的,又是怎么协同工作的。最后解释下service/middleware
endpoint/middleware
的工作原理。
go-kit的调用关系:
client访问微服务http server;经过transport层是decode获得http包request数据;然后交个endpoint处理,endpoint在go-kit是一个重要的概念,代表了一个可执行的服务,表现形式是一个函数type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
;endpoint将request传给业务函数处理,返回response,最后在一层层返回给client。
这里顺便解释下middleware,endpoint的middleware会在endpoint调用业务函数之前处理,service的middleware由service逻辑自己决定。
下面的解释基于上面服务里面的两个业务:Foo/Test
http通过ListenAndServer监听连接,连接产生时转交给http.Handler
处理,transport的核心就是将endpoint、decodeReq、encodeResp告诉http.Handler
。
比如下面的代码,处理路由为/foo
的业务,"github.com/go-kit/kit/transport/http".NewServer
创建一个server,里面包含一个http.Handler
的实现。
// makeFooHandler creates the handler logic
func makeFooHandler(m *http.ServeMux, endpoints endpoint.Endpoints, options []http1.ServerOption) {
m.Handle("/foo", http1.NewServer(endpoints.FooEndpoint, decodeFooRequest, encodeFooResponse, options...))
}
http.Handler
的实现
在文件/go-kit/kit/transport/http/server.go
中的ServeHTTP(w http.ResponseWriter, r *http.Request)
,核心代码为:
// Server wraps an endpoint and implements http.Handler.
type Server struct {
e endpoint.Endpoint
dec DecodeRequestFunc
enc EncodeResponseFunc
before []RequestFunc
after []ServerResponseFunc
errorEncoder ErrorEncoder
finalizer []ServerFinalizerFunc
logger log.Logger
}
// ServeHTTP implements http.Handler.
func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() //request.Context
request, err := s.dec(ctx, r) //decode request
response, err := s.e(ctx, request) //endpoint function call
s.enc(ctx, w, response) //encode response
}
endpoint本质是一个处理函数类型,代表了一个RPC方法,每个服务需要针对服务进行具体实现
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
,下面是一个实现的例子:
func MakeFooEndpoint(s service.HelloService) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(FooRequest)
rs, err := s.Foo(ctx, req.S)
return FooResponse{
Err: err,
Rs: rs,
}, nil
}
}
然后通过传入service创建endpoint:
func New(s service.HelloService) Endpoints {
eps := Endpoints{
FooEndpoint: MakeFooEndpoint(s),
TestEndpoint: MakeTestEndpoint(s),
}
return eps
}
然后传入一个service创建一个endpoint实例:
svc := service.New() // service 部分会介绍
eps := endpoint.New(svc)
最后将endpoint实例传给上面的"github.com/go-kit/kit/transport/http".NewServer()
,创建一个http.Handler()
实现。
service.go
应该是go-kit里面最好理解的一层,也是定义业务最纯粹的地方,所有的业务定义在RPC接口HelloService
中。
type HelloService interface {
Foo(ctx context.Context, s string) (rs string, err error)
Test(ctx context.Context, s string, i int64) (string, error)
}
实现接口
type basicHelloService struct{}
func (b *basicHelloService) Foo(ctx context.Context, s string) (rs string, err error) {
return rs, err
}
func (b *basicHelloService) Test(ctx context.Context, s string, i int64) (s0 string, e1 error) {
return s0, e1
}
创建service
func New() HelloService {
return &basicHelloService{}
}
启动一个微服务的详细流程
import http1 "github.com/go-kit/kit/transport/http"
// 1. 创建一个service,此service为当前目录下service包
var svc HelloService = service.New()
// 2. 创建一个Endpoints,此endpoint为当前目录下的endpoint包
eps := endpoint.New(svc)
// 3. 创建一个microserver
// 3.1 创建http.Handler
//这里省略(decodeFooRequest和encodeFooResponse实现)
// type DecodeRequestFunc func(context.Context, *http.Request) (request interface{}, err error)
// type EncodeResponseFunc func(context.Context, http.ResponseWriter, interface{}) error
serv := http1.NewServer(eps.FooEndpoint, decodeFooRequest, encodeFooResponse, options...)
// 3.2 创建Mux server
mux := http.NewServeMux()
mux.Handle("/foo", serv)
// 3.3 启动微服务server
http.ListenAndServe("localhost:8080", mux)
middleware模块应该是go-kit最强大的地方,middleware通常是一个chain处理,比如go-kit中对于endpoint层的Middleware的定义endpoint.Middleware
=> type Middleware func(Endpoint) Endpoint
。endpoint的middleware通常是对request做一些预处理,比如对header中的Authorization的处理。
对于service层的Middleware的定义需要根据service本身的业务接口定义,比如HelloService的Middleware可以定义为type Middleware func(HelloService) HelloService
。go-kit提供了一些供service用的middleware组件。
通常service Middleware的实现是用对基类HelloService的扩展来实现的,比如
type loggingMiddleware struct {
logger log.Logger
next HelloService
}
这样就可以传入一个Base HelloService而返回一个loggingMiddleware,比如func(HelloService) loggingMiddleware
。
对于transport层没有明确是说明是middleware,但是可以添加option,比如记录log,trace等。
含有middleware的endpoint和service是go-kit的服务的标准创建方式,也就是说上面的介绍为了理解简化了创建流程,接下来将详细介绍含有middleware的服务创建。
由于middleware的创建有些绕,这里先给出一个具体的service的创建的主要流程,将忽略具体的业务的实现,下面的介绍将围绕该流程展开,微服务包含两个方法Foo
Test
:
启动一个带middleware微服务的详细流程
import http1 "github.com/go-kit/kit/transport/http"
// 1. 创建一个Service Middleware类型定义
type Middleware func(HelloService) HelloService
// 2. 扩展一个基于HelloService的middleware,并重写HelloService的所有RPC接口
type loggingMiddleware struct {
logger log.Logger
next HelloService
}
func (l loggingMiddleware) Foo(ctx context.Context, s string) (rs string, err error) {
// something
return l.next.Foo(ctx, s)
}
func (l loggingMiddleware) Test(ctx context.Context, s string, i int64) (s0 string, e1 error) {
// somethin
return l.next.Test(ctx, s, i)
}
// 3. 实例化一个Service Middleware,并append到一个[]Middleware
var logger log.Logger
logMW := func(next HelloService) HelloService {
return &loggingMiddleware{logger, next}
}
// 4. 创建一个service,这里需要重新定义service.New()为service.New(mws []Middleware),传入的为需要处理的middleware,后面会具体介绍这些middleware是怎么串起来的形成一个service chain的。
mws := []Middleware{logMW}
var svc HelloService = service.New(mws)
// 5. 实例化一个endpoint.Middleware => type Middleware func(Endpoint) Endpoint
logeMW := func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
// something TODO about log
// ctx = ...
// request = ...
logger.Log("...")
return next(ctx, request)
}
}
// 6. 创建一个Endpoint,这里需要重新定义`endpoint.New(service.HelloService)`为`endpoint.New(service.HelloService,map[string][]endpoint.Middleware)`,传入一个`map[string][]endpoint.Middleware{}`。
emws := map[string][]endpoint.Middleware{}
emws["Foo"] = []endpoint.Middleware{logeMW} // 这里的"Foo"是HelloService里面定义的RPC方法,也可以更多的类似logeMW的实现;
//如果"Test"也有endpoint.Middleware,则也应该添加
// emws["Test"] = []endpoint.Middleware{logeMW}
eps := endpoint.New(svc,emws)
// eps为Endpoints类型,即所有Service服务接口对应的endpoint.Endpoint集合:
// type Endpoints struct {
// FooEndpoint endpoint.Endpoint
// TestEndpoint endpoint.Endpoint
// }
// 7. 创建一个microserver
// 7.1 创建http.Handler
//这里省略(decodeFooRequest和encodeFooResponse实现)
// type DecodeRequestFunc func(context.Context, *http.Request) (request interface{}, err error)
// type EncodeResponseFunc func(context.Context, http.ResponseWriter, interface{}) error
servfoo := http1.NewServer(eps.FooEndpoint, decodeFooRequest, encodeFooResponse, options...)
servtest := http1.NewServer(eps.TestEndpoint, decodeFooRequest, encodeFooResponse, options...)
// 7.2 创建Mux server
mux := http.NewServeMux()
mux.Handle("/foo", servfoo)
mux.Handle("/test", servtest)
// 7.3 启动微服务server
http.ListenAndServe("localhost:8080", mux)
endpoint.Endpoint定义:
// Endpoint is the fundamental building block of servers and clients.
// It represents a single RPC method.
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
endpoint.Middleware定义:
// Middleware is a chainable behavior modifier for endpoints.
type Middleware func(Endpoint) Endpoint
所以Middleware的实现方式可以理解为是一个输入输出链模型:
其实kit对endpoint的实现是比较巧妙的,具体体现在endpoint.New
和endpoint.Middleware
,看下他们的实现:
// New returns a Endpoints struct that wraps the provided service, and wires in all of the
// expected endpoint middlewares
func New(s service.HelloService, mdw map[string][]endpoint.Middleware) Endpoints {
eps := Endpoints{
FooEndpoint: MakeFooEndpoint(s),
TestEndpoint: MakeTestEndpoint(s),
}
for _, m := range mdw["Foo"] {
eps.FooEndpoint = m(eps.FooEndpoint)
}
for _, m := range mdw["Test"] {
eps.TestEndpoint = m(eps.TestEndpoint)
}
return eps
}
func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) response interface{}, err error) {
// something need to do
return next(ctx, request)
}
}
来分析下核心代码:
第一段代码中的:
for _, m := range mdw["Foo"] {
eps.FooEndpoint = m(eps.FooEndpoint)
}
//--------
第二段代码中的:
return next(ctx, request)
也就是说在New创建的时候,会创建一个endpoint.Endpoint的链式调用,最后创建的将会先执行。当收到http的请求时,http.Handler会调用endpoint.Endpoint(ctx,req),从而依次往后调,最后将调用业务接口的Endpoint。
service的middleware和endpoint的middleware是类似的,也是一个链式调用,先来看几个概念:
Middleware的定义:
// Middleware describes a service middleware.
type Middleware func(HelloService) HelloService
具体某个Middleware的定义:
type loggingMiddleware struct {
logger log.Logger
next HelloService
}
我们来看看其中实现的奥秘,共三段代码:
loggingMiddleware包含的HelloService接口的实现:
func (l loggingMiddleware) Foo(ctx context.Context, s string) (rs string, err error) {
defer func() {
l.logger.Log("method", "Foo", "s", s, "rs", rs, "err", err)
}()
return l.next.Foo(ctx, s)
}
func (l loggingMiddleware) Test(ctx context.Context, s string, i int64) (s0 string, e1 error) {
defer func() {
l.logger.Log("method", "Test", "s", s, "i", i, "s0", s0, "e1", e1)
}()
return l.next.Test(ctx, s, i)
}
Middleware的实现:
var logger log.Logger
func(next HelloService) HelloService {
return &loggingMiddleware{logger, next}
}
创建一个service的时候
// New returns a HelloService with all of the expected middleware wired in.
func New(middleware []Middleware) HelloService {
var svc HelloService = NewBasicHelloService()
for _, m := range middleware {
svc = m(svc)
}
return svc
}
来分析下核心代码:
第一段代码中的:
return l.next.Foo(ctx, s)
//--------
第二段代码中的:
return &loggingMiddleware{logger, next}
//--------
第三段代码中的:
for _, m := range middleware {
svc = m(svc)
}
也就是说每个Middleware会接收一个老的HelloService返回一个包含老的HelloService的新的HelloService,同时新的HelloService的接口实现会调用老的HelloService的接口return l.next.Foo(ctx, s)
。
在创建的时候,会依次调用,从而将这些Middleware串起来。
kit g c hello
client的连接具体参考生成client库。
package main
import (
"context"
"fmt"
client "hello/client/http"
"github.com/go-kit/kit/transport/http"
)
func main() {
svc, err := client.New("http://localhost:8081", map[string][]http.ClientOption{})
if err != nil {
panic(err)
}
r, err := svc.Foo(context.Background(), "hello")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", r)
}
go-kit 除了支持http还支持grpc,amqp,nats,thrift。比如,grpc使用kit g s hello -t grpc
,nats使用kit g s hello -t nats
。
这些的实现框架与http是一致的。