gRPC Go Microservice

Rodrigo Ancavil
4 min readNov 5, 2023

The main idea behind RPC (Remote Procedure Call) is to allow that clients can directly call methods on a server application on a different machine, making it easier to create distributed applications and microservices.

gRPC is an Open Source high performance Remote Procedure Call (RPC) framework implemented by Google.

A service has methods that can be called remotely by clients, passing parameters and getting a response.

gRPC doesn’t depend on a programming language, we could have our services written in go, and clients written in Python, C++, Java, etc.

gRPC has been implemented over HTTP/2 which some charasteristics are:

  • Multiplexing
  • Stream prioritization.
  • Header compression
  • And binary protocol.

These features reduce latency, increase parallelism, and optimize resource delivery. So for these reasons gRPC is a good option to implement high-demand and distributed services.

We are going to develop a simple gRPC microservice in go. See/get the code here.

Install

First we have to set up our environment, installing the following (this is for Ubuntu, check the documentation for your OS).

$ apt install protobuf protoc-gen-go protoc-gen-go-grpc

Protobuf will create for us the code from the file.proto where we define our service using a descriptive and agnostic language. For example, with the following lines (file microservice.proto) we are defining the Methods and Messages for the service that we want to develop.

syntax = "proto3";
option go_package = "grpc/ms/pb";

service PaymentService {
rpc PayTo(PaymentServiceRequest) returns (PaymentServiceReply) {}
}

message PaymentServiceRequest {
string id = 1;
}

message PaymentServiceReply {
string message = 1;
}

Since, we are going to work with go, we need to install protoc-gen-go and protoc-gen-go-grpc to create the go gRPC code (as a go package).

In the proto file we define the service PaymentService with the method PayTo() and the messages:

  • PaymentServiceRequest
  • PaymentServiceReply.

Setting up

Create a directory called microservice.

$ mkdir microservice

Initialize the go module.

$ cd microservice
$ go mod init grpc/ms

Creating microservice.proto file

Create the file microservice.go with the following lines.

syntax = "proto3";
option go_package = "grpc/ms/pb";

service PaymentService {
rpc PayTo(PaymentServiceRequest) returns (PaymentServiceReply) {}
}

message PaymentServiceRequest {
string id = 1;
}

message PaymentServiceReply {
string message = 1;
}

And create the pb directory inside the directory microservice.

$ mkdir pb

And finally execute the command.

$ protoc --go_out=pb --go_opt=paths=source_relative --go-grpc_out=pb --go-grpc_opt=paths=source_relative microservice.proto

This will create the files:

  • microservice_grpc.pb.go
  • microservice_grpc.pb.go

Both files with the code needed by the service.

Service Code

Create the directory service inside microservice directory and go into.

$ mkdir server
$ cd server

Create the file main.go with the following code:

package main

import (
"context"
"fmt"
pb "grpc/ms/pb"
"log"
"net"

"google.golang.org/grpc"
)

type server struct {
pb.PaymentServiceServer
}

func (s *server) PayTo(ctx context.Context, req *pb.PaymentServiceRequest) (*pb.PaymentServiceReply, error) {
log.Println("Paying Id :", req.Id)
return &pb.PaymentServiceReply{
Message: fmt.Sprintf("Processing payment id : %v", req.Id),
}, nil
}

func main() {
listener, err := net.Listen("tcp", ":9999")
if err != nil {
panic(err)
}
s := grpc.NewServer()
pb.RegisterPaymentServiceServer(s, &server{})

log.Println("Service running")

if err := s.Serve(listener); err != nil {
log.Fatalf("Failed to serve : %v", err)
}
}

Notice that we are using the “grpc/ms/pb” package created from microservice.proto.

Before running our service, execute go mod tidy to get all packages needed by the service (in particular “google.golang.org/grpc”).

$ go mod tidy

Client Code

Create the directory client inside microservice directory and go into. And create the file main.go inside.

$ mkdir client
$ cd client

File main.go:

package main

import (
"context"
"fmt"
"log"

pb "grpc/ms/pb"

"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

func main() {
opts := grpc.WithTransportCredentials(insecure.NewCredentials())
cc, err := grpc.Dial("localhost:9999", opts)
if err != nil {
log.Fatal(err)
}
defer cc.Close()

client := pb.NewPaymentServiceClient(cc)
request := &pb.PaymentServiceRequest{Id: "21111036"}

resp, err := client.PayTo(context.Background(), request)

if err != nil {
log.Fatal(err)
}
fmt.Println("Received :", resp.Message)
}

Running server and client

Verify you are in microservice directory.

$ cd microservice

Then run in a terminal:

$ go run server/main.go
2023/11/01 12:24:48 Service running

Note: stop with CTRL-C

In another terminal run the client.

$ go run client/main.go

We should receive a response like this:

Received : Processing payment id : 21111036

And the terminal server we should see:

2023/11/01 12:28:01 Paying Id : 21111036

Testing with a Python Client

We need to install the following python packages

$ pip install grpcio-tools grpcio

Create a directory for our python client.

$ mkdir pygRPC
$ cd pygRPC

Create or copy the microservice.go to the directory pygRPC.

syntax = "proto3";


message PaymentServiceRequest {
string id = 1;
}


message PaymentServiceReply {
string message = 1;
}


service PaymentService {
rpc PayTo(PaymentServiceRequest) returns (PaymentServiceReply) {}
}

And run:

$ python -m grpc_tools.protoc -I. --python_out=. --pyi_out=. --grpc_python_out=. microservice.proto

As a result we will get the following files in the pygRPC directory.

  • microservice_pb2.py
  • microservice_pb2.pyi
  • microservice_pb2_grpc.py

We just need to create the python client (I called client.py).

import grpc
import microservice_pb2 as pb2
import microservice_pb2_grpc as pb2_grpc


class PayService(object):
def __init__(self):
self.channel = grpc.insecure_channel("localhost:9999")
self.stub = pb2_grpc.PaymentServiceStub(self.channel)

def payTo(self, id):
request = pb2.PaymentServiceRequest(id=id)
response = self.stub.PayTo(request)
return response


if __name__ == '__main__':
print("Sending request...")
client = PayService()
response = client.payTo(id="10001")
print(response)

Now, verify that your server is running and execute the client in a different terminal.

$ python client.py
Sending request...
message: "Processing payment id : 10001"

That’s all for now.

--

--

Rodrigo Ancavil
Rodrigo Ancavil

Written by Rodrigo Ancavil

IT Architect and Software Engineer

No responses yet