Go + gRPC with Go kit
Tags: microservices, go, tutorial
Published: 2020-11-29 01:59:13

Building applications with Go can be sometimes intimidating especially when you’re just starting to learn it. Let alone transporting your APIs through gRPC. In this post, I will guide you on writing a simple Go microservice using gRPC with the help of Go kit.

## What is Go kit? and why?

[Go kit](https://pkg.go.dev/github.com/go-kit/kit) is the brain child of [Peter Bourgon](http://peter.bourgon.org/go-kit/#what-go-needs) which he talked about during a FOSDEM and the Google Campus London meetup in 2015. As what he has envisioned, Go kit is a toolkit for building microservices in Go.

It provides you a set of standard libraries (packages) that are considered essentials when building microservices so you can focus on what’s important, your application’s business logic. Here is a list of packages Go kit provides

- **Authentication** (Basic/Casbin/JWT)
- **Circuit Breaker** (Hystrix/GoBreaker/HandyBreaker)
- **Logging**: Provides an interface for structured logging.
- **Metrics**: Provides an interface for service instrumentation. Has adapters for CloudWatch, Prometheus, Graphite, and more.
- **Rate Limiting**
- **Service Discovery Utilities**
- **Tracing** (OpenCensus, OpenTracing, Zipkin)
- **Transport** (HTTP/gRPC/ NATS/AWS Lambda, and more)

Other than being a toolkit, it also encourages good design principles for your application such as SOLID design principles, Domain-Driven Design (DDD), and Hexagonal architecture.

## Go kit's concept
Go kit's concept is based on three major components:
- Services
- Endpoints
- Transports
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/nbjbryqrh28s3kl1vy3z.png)

### Services (Business Logic)
This is where the core business logic of your API is located. In Go kit, each service method are converted into an Endpoint.

### Endpoints
An endpoint represents a single RPC method. Each Service method converts into an Endpoint to make RPC style communication between servers and clients.

### Transports
In a microservice architecture, a microservice often communicate through transports such as HTTP or gRPC. A single endpoint can be exposed by multiple transports.

## Let's build our microservice
For our example microservice, we will build a very simple function that would get the sum of two numbers. Before we get started, make sure that you have [Protocol buffer and Go plugins](https://grpc.io/docs/languages/go/quickstart/) for the protocol compiler installed.

To give you an overview what our project structure would look like by the end of this article, you can refer to the directory tree below.

```bash
.
├── cmd
│   └── main.go         # main entrypoint file          
├── endpoints
│   └── endpoints.go    # contains the endpoint definition
├── pb
│   ├── math.pb.go      # our gRPC generated code
│   └── math.proto      # our protobuf definitions
├── service
│   └── api.go          # contains the service's core business logic
└── transports
    └── grpc.go         # contains the gRPC transport
```

### Protobuf
First of, we would need to create our Protobuf file inside our pb directory. Since you’re reading this, I would assume that you already know the basics of Protobuf; and the code snippet below should be pretty straightforward to understand 😄

```proto
syntax = "proto3";

option go_package = "github.com/junereycasuga/gokit-grpc-demo/pb";

service MathService {
  rpc Add(MathRequest) returns (MathResponse) {}
}

message MathRequest {
  float numA = 1;
  float numB = 2;
}

message MathResponse {
  float result = 1;
}
```

Once we have our Protobuf file set, we would then need to generate our gRPC code using the following command.

```bash
protoc --go_out=plugins=grpc:. pb/math.proto
```

### Service
Next up, we will create the core business logic of our microservice. So in service/api.go , we would add our service definition by using the following code below.

```go
package service

import (
	"context"

	"github.com/go-kit/kit/log"
)

type service struct {
	logger log.Logger
}

// Service interface describes a service that adds numbers
type Service interface {
	Add(ctx context.Context, numA, numB float32) (float32, error)
}

// NewService returns a Service with all of the expected dependencies
func NewService(logger log.Logger) Service {
	return &service{
		logger: logger,
	}
}

// Add func implements Service interface
func (s service) Add(ctx context.Context, numA, numB float32) (float32, error) {
	return numA + numB, nil
}
```

### Endpoint
```go
package endpoints

import (
	"context"

	"github.com/go-kit/kit/endpoint"
	"github.com/junereycasuga/gokit-grpc-demo/service"
)

// Endpoints struct holds the list of endpoints definition
type Endpoints struct {
	Add endpoint.Endpoint
}

// MathReq struct holds the endpoint request definition
type MathReq struct {
	NumA float32
	NumB float32
}

// MathResp struct holds the endpoint response definition
type MathResp struct {
	Result float32
}

// MakeEndpoints func initializes the Endpoint instances
func MakeEndpoints(s service.Service) Endpoints {
	return Endpoints{
		Add: makeAddEndpoint(s),
	}
}

func makeAddEndpoint(s service.Service) endpoint.Endpoint {
	return func(ctx context.Context, request interface{}) (response interface{}, err error) {
		req := request.(MathReq)
		result, _ := s.Add(ctx, req.NumA, req.NumB)
		return MathResp{Result: result}, nil
	}
}
```

From the code above for `endpoints/endpoints.go`, we have the Endpoints struct that serves as a helper struct to collect all of the endpoints into a single parameter.

The `MathReq` and `MathResp` simply defines the request parameters and response values for the Add method.

`makeAddEndpoint` func constructs our `Add` endpoint wrapping the service.

And finally, `MakeEndpoints` func returns an `Endpoints` that wraps the provided server and wires in all of the expected endpoints.

### Transport

```go
package transport

import (
	"context"

	"github.com/go-kit/kit/log"
	gt "github.com/go-kit/kit/transport/grpc"
	"github.com/junereycasuga/gokit-grpc-demo/endpoints"
	"github.com/junereycasuga/gokit-grpc-demo/pb"
)

type gRPCServer struct {
	add gt.Handler
}

// NewGRPCServer initializes a new gRPC server
func NewGRPCServer(endpoints endpoints.Endpoints, logger log.Logger) pb.MathServiceServer {
	return &gRPCServer{
		add: gt.NewServer(
			endpoints.Add,
			decodeMathRequest,
			encodeMathResponse,
		),
	}
}

func (s *gRPCServer) Add(ctx context.Context, req *pb.MathRequest) (*pb.MathResponse, error) {
	_, resp, err := s.add.ServeGRPC(ctx, req)
	if err != nil {
		return nil, err
	}
	return resp.(*pb.MathResponse), nil
}

func decodeMathRequest(_ context.Context, request interface{}) (interface{}, error) {
	req := request.(*pb.MathRequest)
	return endpoints.MathReq{NumA: req.NumA, NumB: req.NumB}, nil
}

func encodeMathResponse(_ context.Context, response interface{}) (interface{}, error) {
	resp := response.(endpoints.MathResp)
	return &pb.MathResponse{Result: resp.Result}, nil
}
```

In our code for `transports/grpc.go`, we have NewGRPCServer which makes a set of endpoints available as a gRPC MathServiceServer.

### Main entrypoint
This is where we stitch things together. Here we initiate our services, endpoints and our gRPC server, then we serve it using `google.golang.org/grpc` package.

```go
package main

import (
	"fmt"
	"net"
	"os"
	"os/signal"
	"syscall"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/junereycasuga/gokit-grpc-demo/endpoints"
	"github.com/junereycasuga/gokit-grpc-demo/pb"
	"github.com/junereycasuga/gokit-grpc-demo/service"
	transport "github.com/junereycasuga/gokit-grpc-demo/transports"
	"google.golang.org/grpc"
)

func main() {
	var logger log.Logger
	logger = log.NewJSONLogger(os.Stdout)
	logger = log.With(logger, "ts", log.DefaultTimestampUTC)
	logger = log.With(logger, "caller", log.DefaultCaller)

	addservice := service.NewService(logger)
	addendpoint := endpoints.MakeEndpoints(addservice)
	grpcServer := transport.NewGRPCServer(addendpoint, logger)

	errs := make(chan error)
	go func() {
		c := make(chan os.Signal)
		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGALRM)
		errs <- fmt.Errorf("%s", <-c)
	}()

	grpcListener, err := net.Listen("tcp", ":50051")
	if err != nil {
		logger.Log("during", "Listen", "err", err)
		os.Exit(1)
	}

	go func() {
		baseServer := grpc.NewServer()
		pb.RegisterMathServiceServer(baseServer, grpcServer)
		level.Info(logger).Log("msg", "Server started successfully 🚀")
		baseServer.Serve(grpcListener)
	}()

	level.Error(logger).Log("exit", <-errs)
}
```

## Source Code
You can find the source code of this example at [Github](https://github.com/junereycasuga/gokit-grpc-demo)