How to use gRPC in Go

 gRPC: The High-Performance Communication Protocol Revolutionizing Distributed Systems

In the realm of modern distributed systems, efficient and reliable communication between microservices is crucial. Traditional protocols like REST have served well, but as systems grow in complexity and demand real-time communication, gRPC emerges as a powerful solution. In this article, we'll delve into gRPC, understand its benefits, and explore a practical example in Go.

What is gRPC?

gRPC is an open-source high-performance Remote Procedure Call (RPC) framework developed by Google. It enables seamless communication between services running on different platforms and languages. gRPC is based on HTTP/2, which provides multiplexing, stream prioritization, and header compression, leading to faster and more efficient communication compared to REST.

gRPC


Key Features and Advantages of gRPC

Protocol Buffers (Protobuf): gRPC uses Protocol Buffers as the default data serialization mechanism. Protobuf offers efficient binary serialization, reducing payload size and facilitating faster data transmission.

Bidirectional Streaming: Unlike REST, where the client sends a request and waits for a response, gRPC supports bidirectional streaming. This means both the client and server can send multiple messages asynchronously over a single connection, ideal for real-time applications.

Code Generation: gRPC generates client and server code in various languages (including Go, Java, Python, and more) from a single Protobuf definition. This reduces the manual effort and minimizes the risk of errors when integrating services.

Strong Typing: Protobuf enforces strong typing for message structures, ensuring the consistency and integrity of data exchanged between services.

Unary and Streaming Calls: gRPC supports unary calls (single request and response) as well as streaming calls (continuous stream of requests or responses). This flexibility caters to various communication scenarios.


Practical Example: Building a Greet Service in Go

Let's create a simple gRPC server and client to demonstrate the power of gRPC using Go. They will show an example of single request and response. As well as an example of a continuous stream from the server to the client.

Install Protobuf Compiler

First we need to install the protobuf compiler. We must ensure that the version is 3+.

    sudo apt install -y protobuf-compiler
  


Then, we have to install the Go plugins for the protobuf compiler.

    go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
    go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
  


Define the Protobuf Messages

Create a file named idl/service/service.proto and define the Greeter service and message structure using Protocol Buffers.


syntax = "proto3";

option go_package = "internal/service";

package service;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}

  // Subscription to receive a continuous stream of notifications
  rpc Subscribe (Subscription) returns (stream Notification) {}
}

// Messages definition

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

message Subscription {
	string address = 1;
}

message Notification {
	string message = 1;
}
  


Generate Go Code from Protobuf

Run the protobuf compiler to compile the idl/service/service.proto file and generate the internal/service/service.pb.go and the internal/service/service_grpc.pb.go files containing the Go implementation.


    protoc --proto_path=idl --go_out=internal --go_opt=paths=source_relative --go-grpc_out=internal --go-grpc_opt=paths=source_relative service/service.proto
  


Implement the server in Go

Create a Go file named cmd/server/main.go and implement the server.


package main

import (
	"context"
	"flag"
	"log"
	"net"
	"time"

	"google.golang.org/grpc"

	"go-grpc/internal/service"
)

type server struct {
	service.UnimplementedGreeterServer
}

func (s *server) SayHello(ctx context.Context, in *service.HelloRequest) (*service.HelloReply, error) {
	log.Printf("Received: %v", in.GetName())
	return &service.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func (s *server) Subscribe(c *service.Subscription, stream service.Greeter_SubscribeServer) error {
	for {
		stream.Send(&service.Notification{Message: "notification"})
		time.Sleep(time.Second * 3)
	}
	return nil
}

func main() {
	flag.Parse()
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	service.RegisterGreeterServer(s, &server{})
	log.Printf("server listening at %v", lis.Addr())
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}
  

Now that we have the server implementation ready, we can compile it.


    go build -o bin/server cmd/server/main.go
  


Implement the client in Go

Create a Go file named cmd/client/main.go and implement the client.


package main

import (
	"bufio"
	"context"
	"io"
	"log"
	"os"
	"time"

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

	"go-grpc/internal/service"
)

func sayHello(c service.GreeterClient, name string) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.SayHello(ctx, &service.HelloRequest{Name: name})
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}
	log.Printf("Greeting: %s", r.GetMessage())
}

func subscribe(c service.GreeterClient) {
	ctx := context.Background()
	stream, err := c.Subscribe(ctx, &service.Subscription{Address: "client"})
	if err != nil {
		log.Fatalf("could not subscribe: %v", err)
	}
	for {
		notification, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatalf("%v.Subscribe(_) = _, %v", c, err)
		}
		log.Println(notification)
	}
}

func main() {
	conn, err := grpc.Dial(":50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := service.NewGreeterClient(conn)
	go subscribe(c)

	reader := bufio.NewReader(os.Stdin)
	for {
		text, _ := reader.ReadString('\n')

		sayHello(c, text)
	}
}  

We now compile the client to generate the corresponding executable.

    go build -o bin/client cmd/client/main.go
  

Run the example

Once we have the server and client executables ready, we can run them to test the example. We start the server in one terminal.

    >./bin/server
    ./bin/server
    2023/08/01 11:07:51 server listening at [::]:50051
  

And the client in another one. We'll start seeing messages coming up on the client terminal. And if we type a string on the client terminal, we will see the reply from the server.

    >./bin/client
    ./bin/client
    2023/08/01 11:08:01 message:"notification"
    2023/08/01 11:08:04 message:"notification"
    2023/08/01 11:08:07 message:"notification"
    John
    2023/08/01 11:08:08 Greeting: Hello John
    2023/08/01 11:08:10 message:"notification"
  

And we can also check on the server terminal that the message from the client was received.

    >./bin/server
    ./bin/server
    2023/08/01 11:07:51 server listening at [::]:50051
    2023/08/01 11:08:08 Received: John
  

Conclusion

gRPC is a game-changer in the world of distributed systems, providing a fast, efficient, and flexible communication framework. In this article, we explored the key features and advantages of gRPC and demonstrated a practical example using Go. As you venture into building distributed systems, consider integrating gRPC to experience the full potential of this cutting-edge technology.

Popular posts from this blog

How to setup NeoVim configuration file

WebAssembly (Wasm): Fixing the Flaws of Applets

How to query Outlook from PowerShell