gRPC and Protobuf

Microservices have become increasingly popular among the organisations because of benefits they offer, A lot of organisations are moving towards Service Oriented Architecture (SOA) and breaking their complex monolithic applications into smaller and modular services which gives a liberty to deploy smaller components of system without having much impact on overall product. Currently REST is a widely adopted standard for services to communicate with each other, since JSON is a human readable data structure it usually takes more bandwidth and each service has to implement their own serializer for consuming this data.

gRPC is an open source high performance framework developed by Google used for calling remote procedures. It uses HTTP 2.0 for transporting data in the form of bytes instead of JSON. gRPC is language agnostic and it’s support for multiple languages gives an ability to write services in different languages.

In this blog I’ll give an overview on how you can build an API using gRPC and protobuf.

Installation

In order to get started with gRPC there are few things that we need to install

Code

We’ll create a calculator service which takes input values along with an operation and returns a result.

Before writing server and client, we first need a contract between client and server which specifies inputs, outputs and methods. We’ll create calculator.proto file for this

syntax = "proto3";

package calculatorpb;

//A request message which'll take two variables as input
message Request {
    float value1 = 1;
    float value2 = 2;
    string operation = 3;
}

//Response message which contains result of the operation
message Response {
    float result = 1;
}

service Calculator {
    rpc Calculate(Request) returns (Response);
}

The above file informs GoLang about how it should encode and decode the data, and package we installed earlier will help us generate code using this stub.

Next step is to compile this file using protoc compiler that we installed earlier. In order to compile this file run the below command

$ protoc --proto_path=calculatorpb --proto_path=vendors --go_out=plugins=grpc:calculatorpb calculator.proto

Note that I have this vendors folder in proto_path which includes all files that came with protoc compiler. You can create a similar folder and specify the path here in the above command.

calculator.pb.go file will be generated under the same folder upon successful execution

Let’s jump to see how the client and server code looks like

Server

Server imports the package that we generated earlier from our proto file and provides an implementation of Calculate method

package main

import (
	"context"
	"errors"
	"grpc-demo/calculatorpb"
	"net"

	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
)

type server struct{}

func main() {
	listener, err := net.Listen("tcp", ":3030")
	if err != nil {
		panic(err)
	}

	srv := grpc.NewServer()
	calculatorpb.RegisterCalculatorServer(srv, &server{})
	reflection.Register(srv)

	serverErr := srv.Serve(listener)
	if serverErr != nil {
		panic(err)
	}
}

func (s *server) Calculate(ctx context.Context, request *calculatorpb.Request) (*calculatorpb.Response, error) {
	val1, val2 := request.GetValue1(), request.GetValue2()
	var result float32

	switch request.GetOperation() {
	case "add":
		result = val1 + val2
	case "sub":
		result = val1 - val2
	case "mult":
		result = val1 * val2
	case "div":
		if val2 == 0 {
			return nil, errors.New("division by zero not possible")
		}
		result = val1 / val2
	default:
		return nil, errors.New("operation not found")
	}

	return &calculatorpb.Response{Result: result}, nil
}

Run the below command for server to start listening for requests

$ go run server/main.go

Client

Next we’ll create a go-client to initiate a lightweight http server and upon receiving requests from external clients over HTTP it’ll parse those requests and invoke the remote method using gRPC client.

package main

import (
	"fmt"
	"grpc-demo/calculatorpb"
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
	"google.golang.org/grpc"
)

func main() {
	connection, err := grpc.Dial("localhost:3030", grpc.WithInsecure())
	if err != nil {
		panic(err)
	}

	client := calculatorpb.NewCalculatorClient(connection)

	g := gin.Default()
	g.GET("calculate/:val1/:op/:val2/", func(ctx *gin.Context) {
		val1, err := strconv.ParseFloat(ctx.Param("val1"), 64)
		if err != nil {
			ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid Request Paramater val1"})
			return
		}

		val2, err := strconv.ParseFloat(ctx.Param("val2"), 64)
		if err != nil {
			ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid Request Paramater val2"})
			return
		}

		req := &calculatorpb.Request{Value1: float32(val1), Value2: float32(val2), Operation: ctx.Param("op")}
		response, err := client.Calculate(ctx, req)
		if err != nil {
			ctx.JSON(http.StatusInternalServerError, gin.H{
				"error": err,
			})
			return
		}

		ctx.JSON(http.StatusOK, gin.H{
			"result": fmt.Sprint(response.Result),
		})
	})

	runErr := g.Run(":9002")
	if runErr != nil {
		fmt.Print("Error running server")
	}
}

Open another terminal window and run same command for your client to start listening http requests on port :9002

go run go-client/main.go

For E2E testing, open your browser and try calling your endpoint with the params like in example below.

I hope the above example will give a basic overview on how to use protobuf and communicate between different services using gRPC. For the complete code you can check this repo and if you have any questions/thoughts/feedback you can reach out via email.