Medium原文点这里。
对以下四个框架进行比较:
最受欢迎的框架之一,网上有很多的博客、文章,还有很多使用示例。可以在Medium上关注microhq,来获得Go Micro中的最新更新。
Go Micro是使用Go语言编写微服务的可插拔RPC框架,它开箱即用,有如下功能:
Content-Type
标头的编码/解码Go Micro架构可以描述为如下所示的三层堆栈:
顶层由客户端-服务器模型和服务抽象组成。服务器(Server)是用于编写服务的构建块,客户端(Client)提供了向服务请求的接口。
底层由以下类型的插件组成:
json
,bson
,protobuf
,msgpack
等Go Micro还提供了Sidecar等功能。这就可以使用以Go以外的语言编写的服务。Sidecar提供服务注册,gRPC编码/解码和HTTP处理程序。它支持多种语言。
Go Kit是用于在Go中构建微服务的编程工具包。与Go Micro不同,它是一个旨在导入二进制软件包的库。Go Kit遵循简单原则,例如:
在Go Kit中可以找到以下软件包:
basic
和JWT
Peter Bourgon的文章和演示幻灯片是对Go Kit最好的描述之一:
在“ Go + microservices”幻灯片中,可以找到使用Go Kit构建的服务体系结构的示例。为了快速参考,服务架构图如下所示:
Gizmo是《纽约时报》的微服务工具包。它提供了将服务和pubsub
守护进程放在一起的软件包。它公开了以下软件包:
Pubsub
软件包提供了与以下队列一起使用的接口:
因此,可以认为Gizmo位于Go Micro和Go Kit之间。它不是像Go Micro这样的完整“黑箱”。同时,它不像Go Kit那样原始。它提供了更高级别的构建组件,例如config和pubsub软件包。
Kite是用于在Go中开发微服务的框架。它公开了RPC客户端和服务器程序包。创建的服务会自动在服务发现系统Kontrol中注册。 Kontrol是用Kite编写的,它本身就是Kite服务。这意味着Kite微服务在其自己的环境中运行良好。如果需要将Kite微服务连接到另一个服务发现系统,则需要自定义。这是从列表中删除Kite并决定不审查此框架的主要原因。
使用四个类别来比较框架:
时间:2019/10/28
Go Micro | Go Kit | Gizmo | |
---|---|---|---|
License | Apache License 2.0 | MIT | Apache License 2.0 |
Created | Jan 2015 | Feb 2015 | Dec 2015 |
Release | 60 | 9 | 48 |
Author | Asim Aslam | Peter Bourgon | JP Robinson |
Stars | 9484 | 15190 | 2937 |
Open/Closed Issues | 35/400 | 56/370 | 12/52 |
Last Update | Oct 26, 2019 | Oct 18, 2019 | Oct 14, 2019 |
Go Micro和Go kit 提供了一个主页,其中包含正式的文档和示例。Gizmo框架没有提供可靠的文档,唯一的正式文档是仓库中的README文件。
有关Go Micro的大量信息和公告,请访问micro.mu,microhq和社交媒体(https://twitter.com/MicroHQ)。
如果使用Go Kit,最好的文档可在Peter Bourgon博客中找到。我发现的最好的例子之一是在ru-rocker博客中。
使用Gizmo,源代码提供了最佳的文档和示例。
综上所述,如果您来自NodeJS领域,并希望看到类似ExpressJS的教程,您将感到失望。另一方面,这是创建自己的教程的绝佳机会。
Go Kit是最流行的微服务框架,基于GitHub统计数据,它已超过1万五千颗星。它有大量的贡献者(161)和超过1600个分叉。最后,Go Kit受DigitalOcean支持。
拥有9484颗星,54个贡献者和993个分叉-Go Micro位居第二。 Sixt是Go Micro的最大赞助商之一。
Gizmo第三名。超过2937星,41个贡献者和188个分叉。由《纽约时报》支持和创建。
Go Kit在代码质量类别中排名第一。它具有近80%的代码覆盖率,并且具有出色的Go报告评级。Gizmo也具有出色的Go报告评级。但是它的代码覆盖率只有46%。Go Micro不提供覆盖范围信息,但确实具有很高的Go报告评级。
为了更好地了解框架,创建了三个简单的微服务,如下图所示:
这些服务都实现了一个业务逻辑:打招呼(greeting),当用户向服务中输入“名称”参数时,服务会发送问候语作为响应。所有服务都满足以下需求:
对于喜欢阅读源代码的人,可以在这里访问仓库源代码。
使用Go Micro创建服务所需要做的第一件事是定义protobuf描述。所有这三种服务都使用了相同的protobuf定义。创建了以下服务描述:
syntax = "proto3";
package pb;
service Greeter {
rpc Greeting(GreetingRequest) returns (GreetingResponse) {}
}
message GreetingRequest {
string name = 1;
}
message GreetingResponse {
string greeting = 2;
}
这个接口包含一个方法Greeting,它有一个请求参数(name)和一个响应参数(greeting)
然后使用修改后的protoc从protobuf中生成服务接口,该proto生成器是Go Micro的一个分叉,并经过修改以支持该框架的某些功能。在打招呼服务中将所有这些连接在一起,此时,服务正在启动并在服务发现系统中注册,它仅支持gRPC传输协议。
package main
import (
"log"
pb "github.com/antklim/go-microservices/go-micro-greeter/pb"
"github.com/micro/go-micro"
"golang.org/x/net/context"
)
// Greeter implements greeter service.
type Greeter struct{}
// Greeting method implementation.
func (g *Greeter) Greeting(ctx context.Context, in *pb.GreetingRequest, out *pb.GreetingResponse) error {
out.Greeting = "GO-MICRO Hello " + in.Name
return nil
}
func main() {
service := micro.NewService(
micro.Name("go-micro-srv-greeter"),
micro.Version("latest"),
)
service.Init()
pb.RegisterGreeterHandler(service.Server(), new(Greeter))
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
为了支持HTTP传输,必须添加其他模块。它将HTTP请求映射到protobuf定义的请求,并调用gRPC服务。然后,它将服务响应映射到HTTP响应,并将其响应返回给用户。
package main
import (
"context"
"encoding/json"
"log"
"net/http"
proto "github.com/antklim/go-microservices/go-micro-greeter/pb"
"github.com/micro/go-micro/client"
web "github.com/micro/go-web"
)
func main() {
service := web.NewService(
web.Name("go-micro-web-greeter"),
)
service.HandleFunc("/greeting", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
var name string
vars := r.URL.Query()
names, exists := vars["name"]
if !exists || len(names) != 1 {
name = ""
} else {
name = names[0]
}
cl := proto.NewGreeterClient("go-micro-srv-greeter", client.DefaultClient)
rsp, err := cl.Greeting(context.Background(), &proto.GreetingRequest{Name: name})
if err != nil {
http.Error(w, err.Error(), 500)
return
}
js, err := json.Marshal(rsp)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(js)
return
}
})
if err := service.Init(); err != nil {
log.Fatal(err)
}
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
一方面,Go Micro在后台处理了许多事情,例如在服务发现系统中进行注册。另一方面,很难创建纯HTTP服务。
在完成Go Micro之后,继续进行Go Kit服务的实现。我花了很多时间阅读Go Kit存储库中提供的代码示例。了解端点的概念花了很多时间。下一个耗时的难题是服务发现注册中心代码。在找到一个很好的例子之前,我无法实现它。
最后,创建了四个包用于:
服务逻辑实现如下所示,代码没有任何依赖关系,它只是实现逻辑。
package greeterservice
// Service describe greetings service.
type Service interface {
Health() bool
Greeting(name string) string
}
// GreeterService implementation of the Service interface.
type GreeterService struct{}
// Health implementation of the Service.
func (GreeterService) Health() bool {
return true
}
// Greeting implementation of the Service.
func (GreeterService) Greeting(name string) (greeting string) {
greeting = "GO-KIT Hello " + name
return
}
下一段代码显示了端点定义:
package greeterendpoint
import (
"context"
"github.com/go-kit/kit/log"
"github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterservice"
"github.com/go-kit/kit/endpoint"
)
// 端点集合收集打招呼服务的所有端点,它旨在用作辅助结构将所有端点收集到单个参数
type Endpoints struct {
HealthEndpoint endpoint.Endpoint // used by Consul for the healthcheck
GreetingEndpoint endpoint.Endpoint
}
// MakeServerEndpoints返回服务端点,并在所有提供的中间件中进行连线
func MakeServerEndpoints(s greeterservice.Service, logger log.Logger) Endpoints {
var healthEndpoint endpoint.Endpoint
{
healthEndpoint = MakeHealthEndpoint(s)
healthEndpoint = LoggingMiddleware(log.With(logger, "method", "Health"))(healthEndpoint)
}
var greetingEndpoint endpoint.Endpoint
{
greetingEndpoint = MakeGreetingEndpoint(s)
greetingEndpoint = LoggingMiddleware(log.With(logger, "method", "Greeting"))(greetingEndpoint)
}
return Endpoints{
HealthEndpoint: healthEndpoint,
GreetingEndpoint: greetingEndpoint,
}
}
// MakeHealthEndpoint构造Health端点来包装服务
func MakeHealthEndpoint(s greeterservice.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
healthy := s.Health()
return HealthResponse{Healthy: healthy}, nil
}
}
// MakeHealthEndpoint构造Greeter端点来包装服务
func MakeGreetingEndpoint(s greeterservice.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(GreetingRequest)
greeting := s.Greeting(req.Name)
return GreetingResponse{Greeting: greeting}, nil
}
}
// Failer是应该由响应类型实现的接口
// 响应编码器可以检查响应是否为Failer,如果是Failer,
// 判断是否失败,或者是否基于错误使用单独的写入路径对响应进行编码
type Failer interface {
Failed() error
}
// HealthRequest收集Health方法的请求参数
type HealthRequest struct{}
// HealthResponse收集Health方法的响应值
type HealthResponse struct {
Healthy bool `json:"healthy,omitempty"`
Err error `json:"err,omitempty"`
}
// Failed实现Failer接口
func (r HealthResponse) Failed() error { return r.Err }
// GreetingRequest收集Greeting方法的请求参数
type GreetingRequest struct {
Name string `json:"name,omitempty"`
}
// GreetingResponse收集Greeting方法的响应值
type GreetingResponse struct {
Greeting string `json:"greeting,omitempty"`
Err error `json:"err,omitempty"`
}
// Failed实现Failer接口
func (r GreetingResponse) Failed() error { return r.Err }
定义服务和端点后,通过不同的传输协议暴露端点。从HTTP传输开始:
package greetertransport
import (
"context"
"encoding/json"
"errors"
"net/http"
"github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterendpoint"
"github.com/go-kit/kit/log"
httptransport "github.com/go-kit/kit/transport/http"
"github.com/gorilla/mux"
)
var (
// 如果缺少预期的路径变量,则返回ErrBadRouting
ErrBadRouting = errors.New("inconsistent mapping between route and handler")
)
// NewHTTPHandler返回一个HTTP处理程序,该处理程序使一组端点在预定义路径上可用
func NewHTTPHandler(endpoints greeterendpoint.Endpoints, logger log.Logger) http.Handler {
m := mux.NewRouter()
options := []httptransport.ServerOption{
httptransport.ServerErrorEncoder(encodeError),
httptransport.ServerErrorLogger(logger),
}
// GET /health retrieves service heath information
// GET /greeting?name retrieves greeting
m.Methods("GET").Path("/health").Handler(httptransport.NewServer(
endpoints.HealthEndpoint,
DecodeHTTPHealthRequest,
EncodeHTTPGenericResponse,
options...,
))
m.Methods("GET").Path("/greeting").Handler(httptransport.NewServer(
endpoints.GreetingEndpoint,
DecodeHTTPGreetingRequest,
EncodeHTTPGenericResponse,
options...,
))
return m
}
// DecodeHTTPHealthRequest method.
func DecodeHTTPHealthRequest(_ context.Context, _ *http.Request) (interface{}, error) {
return greeterendpoint.HealthRequest{}, nil
}
// DecodeHTTPGreetingRequest method.
func DecodeHTTPGreetingRequest(_ context.Context, r *http.Request) (interface{}, error) {
vars := r.URL.Query()
names, exists := vars["name"]
if !exists || len(names) != 1 {
return nil, ErrBadRouting
}
req := greeterendpoint.GreetingRequest{Name: names[0]}
return req, nil
}
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
w.WriteHeader(err2code(err))
json.NewEncoder(w).Encode(errorWrapper{Error: err.Error()})
}
func err2code(err error) int {
switch err {
default:
return http.StatusInternalServerError
}
}
type errorWrapper struct {
Error string `json:"error"`
}
// EncodeHTTPGenericResponse是`transport/http.EncodeResponseFunc`,它将响应编码为JSON编码到响应编写器
func EncodeHTTPGenericResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
if f, ok := response.(greeterendpoint.Failer); ok && f.Failed() != nil {
encodeError(ctx, f.Failed(), w)
return nil
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
return json.NewEncoder(w).Encode(response)
}
在开始gRPC端点实现之前,不需要protobuf定义。直接复制上面Go Micro服务的protobuf定义。但是对于Go Kit,使用默认的服务生成器来创建服务接口。
protoc greeter.proto --go_out=plugins=grpc:.
package greetertransport
import (
"context"
"github.com/antklim/go-microservices/go-kit-greeter/pb"
"github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterendpoint"
"github.com/go-kit/kit/log"
grpctransport "github.com/go-kit/kit/transport/grpc"
oldcontext "golang.org/x/net/context"
)
type grpcServer struct {
greeter grpctransport.Handler
}
// NewGRPCServer使一组端点可用作gRPC GreeterServer
func NewGRPCServer(endpoints greeterendpoint.Endpoints, logger log.Logger) pb.GreeterServer {
options := []grpctransport.ServerOption{
grpctransport.ServerErrorLogger(logger),
}
return &grpcServer{
greeter: grpctransport.NewServer(
endpoints.GreetingEndpoint,
decodeGRPCGreetingRequest,
encodeGRPCGreetingResponse,
options...,
),
}
}
// Greeting实现GreeterService接口的方法
func (s *grpcServer) Greeting(ctx oldcontext.Context, req *pb.GreetingRequest) (*pb.GreetingResponse, error) {
_, res, err := s.greeter.ServeGRPC(ctx, req)
if err != nil {
return nil, err
}
return res.(*pb.GreetingResponse), nil
}
// encodeGRPCGreetingRequest是一个`transport/grpc.DecodeRequestFunc`,它将gRPC问候请求转换为用户域问候请求
func decodeGRPCGreetingRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*pb.GreetingRequest)
return greeterendpoint.GreetingRequest{Name: req.Name}, nil
}
// encodeGRPCGreetingResponse是`transport/grpc.EncodeResponseFunc`,它将用户域问候响应转换为gRPC问候响应
func encodeGRPCGreetingResponse(_ context.Context, response interface{}) (interface{}, error) {
res := response.(greeterendpoint.GreetingResponse)
return &pb.GreetingResponse{Greeting: res.Greeting}, nil
}
最后,实现服务发现注册器:
package greetersd
import (
"math/rand"
"os"
"strconv"
"time"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/sd"
consulsd "github.com/go-kit/kit/sd/consul"
"github.com/hashicorp/consul/api"
)
// ConsulRegister method.
func ConsulRegister(consulAddress string,
consulPort string,
advertiseAddress string,
advertisePort string) (registar sd.Registrar) {
// Logging domain.
var logger log.Logger
{
logger = log.NewLogfmtLogger(os.Stderr)
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
logger = log.With(logger, "caller", log.DefaultCaller)
}
rand.Seed(time.Now().UTC().UnixNano())
// Service discovery domain. In this example we use Consul.
var client consulsd.Client
{
consulConfig := api.DefaultConfig()
consulConfig.Address = consulAddress + ":" + consulPort
consulClient, err := api.NewClient(consulConfig)
if err != nil {
logger.Log("err", err)
os.Exit(1)
}
client = consulsd.NewClient(consulClient)
}
check := api.AgentServiceCheck{
HTTP: "http://" + advertiseAddress + ":" + advertisePort + "/health",
Interval: "10s",
Timeout: "1s",
Notes: "Basic health checks",
}
port, _ := strconv.Atoi(advertisePort)
num := rand.Intn(100) // to make service ID unique
asr := api.AgentServiceRegistration{
ID: "go-kit-srv-greeter-" + strconv.Itoa(num), //unique service ID
Name: "go-kit-srv-greeter",
Address: advertiseAddress,
Port: port,
Tags: []string{"go-kit", "greeter"},
Check: &check,
}
registar = consulsd.NewRegistrar(client, &asr, logger)
return
}
准备好所有构建块之后,我将它们连接到服务启动器中:
package main
import (
"flag"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"text/tabwriter"
"github.com/antklim/go-microservices/go-kit-greeter/pb"
"google.golang.org/grpc"
"github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterendpoint"
"github.com/antklim/go-microservices/go-kit-greeter/pkg/greetersd"
"github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterservice"
"github.com/antklim/go-microservices/go-kit-greeter/pkg/greetertransport"
"github.com/go-kit/kit/log"
"github.com/oklog/oklog/pkg/group"
)
func main() {
fs := flag.NewFlagSet("greetersvc", flag.ExitOnError)
var (
debugAddr = fs.String("debug.addr", ":9100", "Debug and metrics listen address")
consulAddr = fs.String("consul.addr", "", "Consul Address")
consulPort = fs.String("consul.port", "8500", "Consul Port")
httpAddr = fs.String("http.addr", "", "HTTP Listen Address")
httpPort = fs.String("http.port", "9110", "HTTP Listen Port")
grpcAddr = fs.String("grpc-addr", ":9120", "gRPC listen address")
)
fs.Usage = usageFor(fs, os.Args[0]+" [flags]")
fs.Parse(os.Args[1:])
var logger log.Logger
{
logger = log.NewLogfmtLogger(os.Stderr)
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
logger = log.With(logger, "caller", log.DefaultCaller)
}
var service greeterservice.Service
{
service = greeterservice.GreeterService{}
service = greeterservice.LoggingMiddleware(logger)(service)
}
var (
endpoints = greeterendpoint.MakeServerEndpoints(service, logger)
httpHandler = greetertransport.NewHTTPHandler(endpoints, logger)
registar = greetersd.ConsulRegister(*consulAddr, *consulPort, *httpAddr, *httpPort)
grpcServer = greetertransport.NewGRPCServer(endpoints, logger)
)
var g group.Group
{
// The debug listener mounts the http.DefaultServeMux, and serves up
// stuff like the Go debug and profiling routes, and so on.
debugListener, err := net.Listen("tcp", *debugAddr)
if err != nil {
logger.Log("transport", "debug/HTTP", "during", "Listen", "err", err)
os.Exit(1)
}
g.Add(func() error {
logger.Log("transport", "debug/HTTP", "addr", *debugAddr)
return http.Serve(debugListener, http.DefaultServeMux)
}, func(error) {
debugListener.Close()
})
}
{
// The service discovery registration.
g.Add(func() error {
logger.Log("transport", "HTTP", "addr", *httpAddr, "port", *httpPort)
registar.Register()
return http.ListenAndServe(":"+*httpPort, httpHandler)
}, func(error) {
registar.Deregister()
})
}
{
// The gRPC listener mounts the Go kit gRPC server we created.
grpcListener, err := net.Listen("tcp", *grpcAddr)
if err != nil {
logger.Log("transport", "gRPC", "during", "Listen", "err", err)
os.Exit(1)
}
g.Add(func() error {
logger.Log("transport", "gRPC", "addr", *grpcAddr)
baseServer := grpc.NewServer()
pb.RegisterGreeterServer(baseServer, grpcServer)
return baseServer.Serve(grpcListener)
}, func(error) {
grpcListener.Close()
})
}
{
// This function just sits and waits for ctrl-C.
cancelInterrupt := make(chan struct{})
g.Add(func() error {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
select {
case sig := <-c:
return fmt.Errorf("received signal %s", sig)
case <-cancelInterrupt:
return nil
}
}, func(error) {
close(cancelInterrupt)
})
}
logger.Log("exit", g.Run())
}
func usageFor(fs *flag.FlagSet, short string) func() {
return func() {
fmt.Fprintf(os.Stderr, "USAGE\n")
fmt.Fprintf(os.Stderr, " %s\n", short)
fmt.Fprintf(os.Stderr, "\n")
fmt.Fprintf(os.Stderr, "FLAGS\n")
w := tabwriter.NewWriter(os.Stderr, 0, 2, 2, ' ', 0)
fs.VisitAll(func(f *flag.Flag) {
fmt.Fprintf(w, "\t-%s %s\t%s\n", f.Name, f.DefValue, f.Usage)
})
w.Flush()
fmt.Fprintf(os.Stderr, "\n")
}
}
在几个地方使用了日志记录中间件。它使我们能够将日志记录逻辑与主要服务/端点工作流分离。
package greeterservice
import (
"time"
"github.com/go-kit/kit/log"
)
// ServiceMiddleware描述了服务中间件
type ServiceMiddleware func(Service) Service
// LoggingMiddleware将记录器作为依赖项,并返回ServiceMiddleware。
func LoggingMiddleware(logger log.Logger) ServiceMiddleware {
return func(next Service) Service {
return loggingMiddleware{next, logger}
}
}
type loggingMiddleware struct {
Service
logger log.Logger
}
func (m loggingMiddleware) Health() (healthy bool) {
defer func(begin time.Time) {
m.logger.Log(
"method", "Health",
"healthy", healthy,
"took", time.Since(begin),
)
}(time.Now())
healthy = m.Service.Health()
return
}
func (m loggingMiddleware) Greeting(name string) (greeting string) {
defer func(begin time.Time) {
m.logger.Log(
"method", "Greeting",
"name", name,
"greeting", greeting,
"took", time.Since(begin),
)
}(time.Now())
greeting = m.Service.Greeting(name)
return
}
package greeterendpoint
import (
"context"
"time"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/log"
)
// LoggingMiddleware返回一个端点中间件,该中间件记录每次调用的持续时间以及所产生的错误(如果有)
func LoggingMiddleware(logger log.Logger) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
defer func(begin time.Time) {
logger.Log("transport_error", err, "took", time.Since(begin))
}(time.Now())
return next(ctx, request)
}
}
}
以与Go Kit类似的方式创建了Gizmo服务。为服务,端点,传输和服务发现注册器定义了四个包。服务实现和服务发现系统注册中心与Go Kit服务共享相同的代码。但是端点定义和传输实现必须根据Gizmo的功能来完成。
package greeterendpoint
import (
"net/http"
ocontext "golang.org/x/net/context"
"github.com/NYTimes/gizmo/server"
"github.com/antklim/go-microservices/gizmo-greeter/pkg/greeterservice"
)
// Endpoints collects all of the endpoints that compose a greeter service.
type Endpoints struct {
HealthEndpoint server.JSONContextEndpoint
GreetingEndpoint server.JSONContextEndpoint
}
// MakeServerEndpoints returns service Endoints
func MakeServerEndpoints(s greeterservice.Service) Endpoints {
healthEndpoint := MakeHealthEndpoint(s)
greetingEndpoint := MakeGreetingEndpoint(s)
return Endpoints{
HealthEndpoint: healthEndpoint,
GreetingEndpoint: greetingEndpoint,
}
}
// MakeHealthEndpoint constructs a Health endpoint.
func MakeHealthEndpoint(s greeterservice.Service) server.JSONContextEndpoint { return func(ctx ocontext.Context, r *http.Request) (int, interface{}, error) {
healthy := s.Health()
return http.StatusOK, HealthResponse{Healthy: healthy}, nil }
}
// MakeGreetingEndpoint constructs a Greeting endpoint.
func MakeGreetingEndpoint(s greeterservice.Service) server.JSONContextEndpoint { return func(ctx ocontext.Context, r *http.Request) (int, interface{}, error) {
vars := r.URL.Query()
names, exists := vars["name"]
if !exists || len(names) != 1 {
return http.StatusBadRequest, errorResponse{Error: "query parameter 'name' required"}, nil
}
greeting := s.Greeting(names[0])
return http.StatusOK, GreetingResponse{Greeting: greeting}, nil }
}
// HealthRequest collects the request parameters for the Health method.
type HealthRequest struct{}
// HealthResponse collects the response values for the Health method.
type HealthResponse struct {
Healthy bool `json:"healthy,omitempty"`
}
// GreetingRequest collects the request parameters for the Greeting method.
type GreetingRequest struct {
Name string `json:"name,omitempty"`
}
// GreetingResponse collects the response values for the Greeting method.
type GreetingResponse struct {
Greeting string `json:"greeting,omitempty"`
}
type errorResponse struct {
Error string `json:"error"`
}
如上所示,该代码段类似于Go Kit。主要区别在于应返回的接口类型:
package greetertransport
import (
"context"
"github.com/NYTimes/gizmo/server"
"google.golang.org/grpc"
"errors"
"net/http"
"github.com/NYTimes/gziphandler"
pb "github.com/antklim/go-microservices/gizmo-greeter/pb"
"github.com/antklim/go-microservices/gizmo-greeter/pkg/greeterendpoint"
"github.com/sirupsen/logrus"
)
type (
// TService will implement server.RPCService and handle all requests to the server.
TService struct {
Endpoints greeterendpoint.Endpoints
}
// Config is a struct to contain all the needed
// configuration for our JSONService.
Config struct {
Server *server.Config
}
)
// NewTService will instantiate a RPCService with the given configuration.
func NewTService(cfg *Config, endpoints greeterendpoint.Endpoints) *TService {
return &TService{Endpoints: endpoints}
}
// Prefix returns the string prefix used for all endpoints within this service.
func (s *TService) Prefix() string {
return ""
}
// Service provides the TService with a description of the service to serve and
// the implementation.
func (s *TService) Service() (*grpc.ServiceDesc, interface{}) {
return &pb.Greeter_serviceDesc, s
}
// Middleware provides an http.Handler hook wrapped around all requests.
// In this implementation, we're using a GzipHandler middleware to
// compress our responses.
func (s *TService) Middleware(h http.Handler) http.Handler {
return gziphandler.GzipHandler(h)
}
// ContextMiddleware provides a server.ContextHAndler hook wrapped around all
// requests. This could be handy if you need to decorate the request context.
func (s *TService) ContextMiddleware(h server.ContextHandler) server.ContextHandler {
return h
}
// JSONMiddleware provides a JSONEndpoint hook wrapped around all requests.
// In this implementation, we're using it to provide application logging and to check errors
// and provide generic responses.
func (s *TService) JSONMiddleware(j server.JSONContextEndpoint) server.JSONContextEndpoint {
return func(ctx context.Context, r *http.Request) (int, interface{}, error) {
status, res, err := j(ctx, r)
if err != nil {
server.LogWithFields(r).WithFields(logrus.Fields{
"error": err,
}).Error("problems with serving request")
return http.StatusServiceUnavailable, nil, errors.New("sorry, this service is unavailable")
}
server.LogWithFields(r).Info("success!")
return status, res, nil
}
}
// ContextEndpoints may be needed if your server has any non-RPC-able
// endpoints. In this case, we have none but still need this method to
// satisfy the server.RPCService interface.
func (s *TService) ContextEndpoints() map[string]map[string]server.ContextHandlerFunc {
return map[string]map[string]server.ContextHandlerFunc{}
}
// JSONEndpoints is a listing of all endpoints available in the TService.
func (s *TService) JSONEndpoints() map[string]map[string]server.JSONContextEndpoint {
return map[string]map[string]server.JSONContextEndpoint{
"/health": map[string]server.JSONContextEndpoint{
"GET": s.Endpoints.HealthEndpoint,
},
"/greeting": map[string]server.JSONContextEndpoint{
"GET": s.Endpoints.GreetingEndpoint,
},
}
}
package greetertransport
import (
pb "github.com/antklim/go-microservices/gizmo-greeter/pb"
ocontext "golang.org/x/net/context"
)
// Greeting implementation of the gRPC service.
func (s *TService) Greeting(ctx ocontext.Context, r *pb.GreetingRequest) (*pb.GreetingResponse, error) {
return &pb.GreetingResponse{Greeting: "Hola Gizmo RPC " + r.Name}, nil
}
Go Kit和Gizmo之间的明显区别在于传输方式。 Gizmo提供了几种可以利用的服务类型。要做的就是将HTTP路径映射到端点定义。低级HTTP请求/响应处理由Gizmo处理。
Go Micro是启动微服务系统的最快方法。框架提供了许多功能。因此,无需重新发明轮子。但是,这种舒适性和速度会牺牲灵活性。更改或更新系统部件并不像Go Kit那样容易。并且将gRPC强制为一等的通信类型。
可能需要一些时间来熟悉Go Kit。它需要具备Go语言特性的丰富知识和软件架构方面的经验。另一方面,没有框架限制。所有部件均可独立更改和更新。
Gizmo位于Go Micro和Go Kit之间。它提供了一些更高级别的抽象,例如Service包。但是缺少文档和示例,这意味着不得不通读源代码以了解不同服务类型的工作方式。使用Gizmo比使用Go Kit容易。但是它不像Go Micro那样流畅。