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…