The picture is from the authorSimon AbramsWeb siteUnsplash
I recently gave a talk at the Golang conference in Melbourne on how to develop microservices and frameworks. In this article, I’ll share my knowledge with you (plus, it’s a good reminder for me).
Here’s how I’ll frame the comparison:
- Go Micro
- Go Kit
- Gizmo
- Kite
Introduction of framework
Go Micro
In my opinion, this is one of the most popular frameworks. There are many blog posts and simple examples. You can follow MicroHQ on Medium or @MicroHQ to get the latest updates from Go Micro.
Ok, what is Go Micro? It is a pluggable RPC framework for writing microservices in Go. Out of the box, you will receive:
- Service discovery – Applications that are automatically registered with the service discovery system.
- Load Balancing – Client load balancing to balance requests between service instances.
- Synchronous communication – Provides a request/response transport layer.
- Asynchronous communication – Built-in publish/subscribe functionality.
- Message encoding – Encoding/decoding based on the message’s content-type header.
- RPC client/server package – leverage the above features and exposed interfaces to build microservices.
The Go Micro architecture can be described as a three-tier stack.
Figure 1. Go Micro architecture
The top layer consists of a client-server model and a service abstraction. Servers are the building blocks for writing services. The client provides an interface to make requests to the service.
The bottom layer consists of the following types of plug-ins:
- Broker – Provides an interface to a message Broker for asynchronous publish/subscribe traffic.
- Codec – Used to encode/decode messages. Supported formats include JSON, Bson, Protobuf, msgpack, etc.
- Registry – Provides a service discovery mechanism (default: Consul).
- Selector – load balancing abstraction built on the registry. It allows “select” services using algorithms such as Random, Roundrobin, Leastconn, and so on.
- Transport – Interface for synchronous request/response communication between services.
Go Micro also offers features such as Sidecar. This allows you to use services written in languages other than Go. Sidecar provides service registration, gRPC encoding/decoding, and HTTP handlers. There are many languages to choose from.
Go Kit
Go Kit is a programming toolkit for building microservices in Go. Unlike Go Micro, it is a library that can be imported into binary packages.
Go Kit follows these simple rules:
- No global state
- Declarative combination
- Explicit dependencies
- Interface as a convention
- Domain-driven design
In Go Kit, you can find the following packages:
- Authentication – Basic Authentication and JWT Authentication.
- Transport – HTTP, Nats, gRPC and others.
- Logging – A common interface for structured login services.
- Metrics – CloudWatch, Statsd, Graphite and others.
- Tracing Tracing – Zipkin and Opentracing.
- Service Discovery – Consult, Etcd, Eureka, and others.
- Circuitbreaker – Implement Hystrix in Go.
You can find one of the best descriptions of the Go toolkit in Peter Bourgon’s article and presentation slides:
Go Kit: Use Go in modern enterprises
Go + micro services
Also, in the Go + MicroServices slide show, you’ll see an example of a service architecture built using the Go Kit. For quick reference, here is a service architecture diagram.
Figure 2. Example service architecture built using Go Kit (original image in Go + MicroServices slideshow)
Gizmo
Gizmo is a microservices toolkit for the New York Times. It provides packages that combine the server and pubSub daemons. It exposes the following packages:
- Server – Provides two server implementations: SimpleServer (over HTTP) and RPCServer (over gRPC).
- Server/Kit – Go Kit based package, currently under test.
- Config – contains functions configured from JSON files, JSON blobs in Cordon K/V, or environment variables.
- Pubsub – Provides a generic interface for publishing and using data in queues.
- Publisher/Pubsetst – Contains test interfaces for publishers and subscribers.
- Web – Exposes functions for querying and payload resolution types from requests.
The Pubsub package provides interfaces for handling the following queues:
- Pubsub/AWS – For Amazon SNS/SQS.
- Pubsub/GCP – For Google PubSub.
- Pubsub /kafka – Used for kafka.
- Pubsub/HTTP – For HTTP publishing.
So, in my opinion, Gizmo is somewhere between Go Micro and Go Kit. It’s not a complete “black box” like Go Micro. At the same time, it’s not as primitive as the Go Kit. It provides higher-level build components, such as the Config and PubSub packages.
Kite
Kite is a framework for developing Go microservices. It exposes the RPC client and server packages. The created service is automatically registered with Kontrol, the service discovery system. Kontrol is written in Kite and is itself a Kite service. This means that Kite microserver works well in its own environment. Customization is required if you need to connect the Kite microservice to another service discovery system. This is the main reason I didn’t think Kite would work and decided not to review the framework.
Comparative framework
I will use four categories comparison frameworks:
- Objective comparison – GitHub statistics
- Documentation and Examples
- Users and Communities
- The code quality
GitHub statistics
Table 1. Go Microservice framework statistics (collected in April 2018)
Documentation and Examples
Simply put, no framework provides reliable documentation. Often, the only formal document is the READme on the front page of the project.
For Go Micro, you can read about it at Micro. Mu, Microhq, and @microhq.
For Go Kit, the best documentation is at Peter Bourgon’s blog. One of the best examples I found was in the Ru-Rocker blog.
For Gizmo, the source code provides the best documentation and examples.
All in all, if you come from the NodeJS world and expect a tutorial like ExpressJS, you’ll be disappointed. On the other hand, this is a great opportunity to create your own tutorials.
Users and Communities
Based on GitHub’s statistics,
Go Kit is the most popular microservices framework — more than 10K stars at the time of publication. It has a large number of contributors (122) and over 1000 forks. Finally, Go Kit has gained support from DigitalOcean.
Go Micro ranks second with over 3,600 stars, 27 contributors and 385 forks. One of Go Micro’s biggest sponsors is Sixt.
Gizmo ranks third with over 2,200 stars, 31 contributors and 137 forks. Supported and created by The New York Times.
The code quality
Go Kit ranked first in the code quality category. It has almost 80% code coverage and excellent Go report ratings. Gizmo also has a good Go report rating. But its code coverage is only 46%. Go Micro doesn’t provide reporting information, but it does have a great Go report rating.
Microservice Examples
All right, enough theory. To better understand these frameworks, I created three simple microservices.
Figure 3. Example architecture
These services implement a business function: greetings. When the user passes the name parameter to the service, the service sends a greeting response. In addition, all services meet the following requirements:
- The service should be self-registered in the service discovery system.
- The service should have health check endpoints.
- The service should support at least HTTP and gRPC transport.
For those who like to read the source code. You can stop here to read the source code in the repository.
Go Micro greeter
To create a service using Go Micro, you first need to define a Protobuf description. Next, all three services use the same Protobuf definition. I created the following service description:
go-micro-greeter.proto
syntax = "proto3";
package pb;
service Greeter {
rpc Greeting(GreetingRequest) returns (GreetingResponse) {}
}
message GreetingRequest {
string name = 1;
}
message GreetingResponse {
string greeting = 2;
}
Copy the code
Greetings service protocol definition
The interface consists of one method, Greeting. The request has one parameter – name, and the response has one parameter – greeting.
I then use the modified Protoc to generate the service interface from protobuf. The generator was written by Go Micro Forked and modified to support some features of the framework. I brought it all together in greeter service. At this point, the service is being started and registered with the service discovery system. It only supports the gRPC transport protocol:
go-micro-greeter-grpc-main.go
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(a) {
service := micro.NewService(
micro.Name("go-micro-srv-greeter"),
micro.Version("latest"),
)
service.Init()
pb.RegisterGreeterHandler(service.Server(), new(Greeter))
iferr := service.Run(); err ! =nil {
log.Fatal(err)
}
}
Copy the code
Go Micro gRP C service implementation
To support HTTP transport, I had to add another module. It maps HTTP requests to requests defined by Protobuf. And call the gRPC service. The service response is then mapped to the HTTP response and sent back to the user.
go-micro-greeter-http-main.go
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(a) {
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})
iferr ! =nil {
http.Error(w, err.Error(), 500)
return
}
js, err := json.Marshal(rsp)
iferr ! =nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type"."application/json")
w.Write(js)
return}})iferr := service.Init(); err ! =nil {
log.Fatal(err)
}
iferr := service.Run(); err ! =nil {
log.Fatal(err)
}
}
Copy the code
Implementation of Web services in Go Micro
Very simple and straightforward. Many things are handled behind the scenes by Go Micro, such as registering in the service discovery system. On the other hand, it is difficult to create pure HTTP services.
Go Kit greeter
After I finished Go Micro, I switched to the Go Kit service implementation. I spent a lot of time reading code examples provided in the Go Kit library. It took me a lot of time to understand the concept of endpoints. The next time-consuming challenge is the service discovery registry code. After I found a good example, I implemented it.
Finally, I created four packages for the following:
- Service logic implementation.
- Transport unknown service endpoints.
- Transport specific endpoints (gRPC, HTTP)
- The service discovery registry.
go-kit-greeter-service.go
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(a) bool {
return true
}
// Greeting implementation of the Service.
func (GreeterService) Greeting(name string) (greeting string) {
greeting = "GO-KIT Hello " + name
return
}
Copy the code
Go Kit service logic implementation
As you can see, the code has no dependencies. It just implements the logic. The next code service endpoint definition:
go-kit-greeter-endpoints.go
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"
)
// Endpoints collects all of the endpoints that compose a greeter service. It's
// meant to be used as a helper struct, to collect all of the endpoints into a
// single parameter.
type Endpoints struct {
HealthEndpoint endpoint.Endpoint // used by Consul for the healthcheck
GreetingEndpoint endpoint.Endpoint
}
// MakeServerEndpoints returns service Endoints, and wires in all the provided
// middlewares.
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 constructs a Health endpoint wrapping the service.
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}}// MakeGreetingEndpoint constructs a Greeter endpoint wrapping the service.
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 is an interface that should be implemented by response types.
// Response encoders can check if responses are Failer, and if so if they've
// failed, and if so encode them using a separate write path based on the error.
type Failer interface {
Failed() error
}
// 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"`
Err error `json:"err,omitempty"`
}
// Failed implements Failer.
func (r HealthResponse) Failed(a) error { return r.Err }
// 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"`
Err error `json:"err,omitempty"`
}
// Failed implements Failer.
func (r GreetingResponse) Failed(a) error { return r.Err }
Copy the code
Go Kit Service endpoint definition (transport independent)
After defining the service and endpoint, I began to expose the endpoint over a different transport protocol. I’ll start with HTTP transport:
go-kit-greeter-http.go
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 is returned when an expected path variable is missing.
ErrBadRouting = errors.New("inconsistent mapping between route and handler"))// NewHTTPHandler returns an HTTP handler that makes a set of endpoints
// available on predefined paths.
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 is a transport/http.EncodeResponseFunc that encodes
// the response as JSON to the response writer
func EncodeHTTPGenericResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
iff, 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)
}
Copy the code
Go Kit service HTTP endpoint
I don’t need a Protobuf definition before STARTING the gRPC endpoint implementation. I copied the Go Micro service agreement. But in the Go Kit example, I used the default service generator to create the service interface.
go-greeter-gen.sh
#! /usr/bin/env sh protoc greeter.proto --go_out=plugins=grpc:.Copy the code
Service interface generator from protobuf definition
go-kit-grpc.go
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 makes a set of endpoints available as a 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 implementation of the method of the GreeterService interface.
func (s *grpcServer) Greeting(ctx oldcontext.Context, req *pb.GreetingRequest) (*pb.GreetingResponse, error) {
_, res, err := s.greeter.ServeGRPC(ctx, req)
iferr ! =nil {
return nil, err
}
return res.(*pb.GreetingResponse), nil
}
// decodeGRPCGreetingRequest is a transport/grpc.DecodeRequestFunc that converts
// a gRPC greeting request to a user-domain greeting request.
func decodeGRPCGreetingRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*pb.GreetingRequest)
return greeterendpoint.GreetingRequest{Name: req.Name}, nil
}
// encodeGRPCGreetingResponse is a transport/grpc.EncodeResponseFunc that converts
// a user-domain greeting response to a gRPC greeting response.
func encodeGRPCGreetingResponse(_ context.Context, response interface{}) (interface{}, error) {
res := response.(greeterendpoint.GreetingResponse)
return &pb.GreetingResponse{Greeting: res.Greeting}, nil
}
Copy the code
GRPC endpoint of Go Kit service
Finally, I implemented the service discovery registry:
go-kit-sd.go
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)
iferr ! =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
}
Copy the code
The Go Kit service discovers the registry
With all the building blocks ready, I merged them into the startup service:
go-kit-service-starter.go
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(a) {
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)
iferr ! =nil {
logger.Log("transport"."debug/HTTP"."during"."Listen"."err", err)
os.Exit(1)
}
g.Add(func(a) 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(a) 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)
iferr ! =nil {
logger.Log("transport"."gRPC"."during"."Listen"."err", err)
os.Exit(1)
}
g.Add(func(a) 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(a) 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(a) {
return func(a) {
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")}}Copy the code
Go Kit service startup program
go-kit-greeter-endpoints-middleware.go
package greeterendpoint
import (
"context"
"time"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/log"
)
// LoggingMiddleware returns an endpoint middleware that logs the
// duration of each invocation, and the resulting error, if any.
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)
}
}
}
Copy the code
Endpoints layer logging middleware
Gizmo greeter
I created the Gizmo service in a similar way. I defined four packages for the service, endpoint, transport, and service discovery registry.
The service implementation and Service discovery System registry shares the same code as the Go Kit service. But the endPoints definition and transport implementation must be done according to the Gizmo features.
gizmo-greeter-endpoints.go
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"`
}
Copy the code
Gizmo greeter endpoints
As you can see, the snippet is similar to the Go Kit. The main difference is the type of interface that should be returned:
gizmo-greeter-http.go
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(a) string {
return ""
}
// Service provides the TService with a description of the service to serve and
// the implementation.
func (s *TService) Service(a) (*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)
iferr ! =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(a) 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(a) 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,
},
}
}
Copy the code
Gizmo greeter HTTP endpoints
gizmo-greeter-grpc.go
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
}
Copy the code
GIzmo greeter gRPC
The significant difference between Go-Kit and Gizmo is the transportation implementation. Gizmo provides several service types that you can use. All I have to do is map the HTTP path to the EndPoints definition. Gizmo handles low-level HTTP request/response processing.
conclusion
Go Micro is the fastest way to start a microservices system. The framework provides many features. So you don’t have to reinvent the wheel. But this comfort and speed comes at the expense of flexibility. Changing or updating parts of a system is not as simple as the Go Kit. And take gRPC as the first level communication type.
You might need some time to get used to the Go Kit. It requires a good knowledge of GoLang’s features and experience in software architecture. On the other hand, there are no framework constraints. All parts can be replaced and updated independently.
Gizmo is between Go Micro and Go Kit. It provides some higher-level abstractions, such as service packs. But the lack of documentation and examples meant I had to read the source code to understand how the different service types worked. Gizmo is easier to use than Go Kit. But it’s not as slick as the Go Micro.
That’s all for today. Thank you for reading. For more information, see the MicroServices code repository. If you have any experience with the Go and MicroService frameworks, please share them in the comments below.
medium.com/seek-blog/m…