gRPC

A traditional way in microservices, the way they communicate to each other is using HTTP / REST API. A service consumes an HTTP endpoint. It puts the payload as JSON and gets the response as JSON. It is slow and a little bit complicated. We need to transform the data to JSON and vice versa on every single request. The JSON is also heavy and sometimes not efficient.

With gRPC, you can send a request with an object then the response is an object too. The object is just like a variable of your programming language that you can set or get the value by a function. There's no need to decode or encode JSON. It is simple.

gRPC is a modern open-source high-performance Remote Procedure Call (RPC) framework. It was initially created by Google in 2015 to connect a large number of microservices. You can visit this article for more stories.

gRPC communicates using binary data over HTTP/2. It is more compact and efficient than REST API. Many benchmarks said that it is way faster than REST API such as this one.

It supports 11 programming languages and Android/Web platforms. Let me show you here a simple example of gRPC in GoLang and NodeJS.

Protocol Buffers

gRPC is using Protocol Buffers. We need to install the compiler first from here. After that, we need to write the Protobuf. More references about the syntax can be found here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
syntax = "proto3";

option go_package = "grpc/article";

service ArticleService {
  rpc ReadArticles(Params) returns (Articles) {};
}

message Params {
  string query = 1;
  uint64 page = 2;
  uint64 per_page = 3;
}

message Articles {
  repeated Article articles = 1;
}

message Article {
  uint64 id = 1;
  string title = 2;
  string description = 3;
}

We create the Protobuf for articles. Then, we compile it to the preferred programming language. In this example, I compile it to GoLang.

go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1

Install the protocol compiler plugins for Go using the commands above.

export PATH="$PATH:$(go env GOPATH)/bin"

Update the PATH so that the protoc compiler can find the plugins.

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative article/article.proto

The command above will create an article.pb.go and article_grpc.pb.go.

Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package main

import (
    "context"
    "log"
    "net"
    "os"

    pb "github.com/aristorinjuang/grpc/article"

    "github.com/joho/godotenv"
    "google.golang.org/grpc"
)

type server struct {
    pb.UnimplementedArticleServiceServer
}

func (s *server) ReadArticles(ctx context.Context, in *pb.Params) (*pb.Articles, error) {
    log.Println("Params:", in.GetQuery(), in.GetPage(), in.GetPerPage())

    return &pb.Articles{
        Articles: []*pb.Article{
            {
                Id:          1,
                Title:       "Title 1",
                Description: "This is the description 1",
            },
            {
                Id:          2,
                Title:       "Title 2",
                Description: "This is the description 2",
            },
            {
                Id:          3,
                Title:       "Title 3",
                Description: "This is the description 3",
            },
        },
    }, nil
}

func init() {
    godotenv.Load()
}

func main() {
    listen, err := net.Listen("tcp", ":"+os.Getenv("PORT"))
    if err != nil {
        log.Panic(err)
    }
    s := grpc.NewServer()
    pb.RegisterArticleServiceServer(s, &server{})
    if err := s.Serve(listen); err != nil {
        log.Panic(err)
    }
}

In this example, the article data is hardcoded. In a real project, you should get them from the database. PORT=50051. Execute the server.

Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
    "context"
    "log"
    "os"
    "time"

    pb "github.com/aristorinjuang/grpc/article"

    "github.com/joho/godotenv"
    "google.golang.org/grpc"
)

func init() {
    godotenv.Load()
}

func main() {
    conn, err := grpc.Dial(os.Getenv("SERVER"), grpc.WithInsecure(), grpc.WithBlock())
    if err != nil {
        log.Panic(err)
    }
    defer conn.Close()

    c := pb.NewArticleServiceClient(conn)

    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()

    r, err := c.ReadArticles(ctx, &pb.Params{
        Query:   "Test...",
        Page:    2,
        PerPage: 10,
    })
    if err != nil {
        log.Panic(err)
    }
    log.Println("Aritcles:", r.GetArticles())
}

SERVER="localhost:50051". Execute the client then we will get the result.

Check the terminal or Stdout for the request and response.

NodeJS

We can let GoLang as a server and NodeJS as a client.

npm install -g grpc-tools
grpc_tools_node_protoc --js_out=import_style=commonjs,binary:node --grpc_out=grpc_js:node article/article.proto

Execute those commands above the compile the Protobuff for NodeJS.

1
2
3
4
5
6
7
8
9
{
  "name": "grpc",
  "version": "0.1.0",
  "dependencies": {
    "@grpc/grpc-js": "^1.1.0",
    "dotenv": "^10.0.0",
    "google-protobuf": "^3.0.0"
  }
}

The package.json file. Make sure to install the node modules before coding the client.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var messages = require('./article/article_pb');
var services = require('./article/article_grpc_pb');

var grpc = require('@grpc/grpc-js');

function main() {
  require('dotenv').config()

  var client = new services.ArticleServiceClient(process.env.SERVER, grpc.credentials.createInsecure());
  var params = new messages.Params();

  params.setQuery('Test...');
  params.setPage(2);
  params.setPerPage(10);

  client.readArticles(params, function(err, response) {
    if (err) {
      console.error(err);
    }
    console.log('Articles:', response.getArticlesList());
  });
}

main();

That is the NodeJS / JavaScript version of the client. The intuition is the same as the GoLang one.

Closing

I tried to create another client in PHP but I failed to install some prerequisites. It also happened to the Web. I have no clue. Maybe gRPC is not ready yet, or maybe it is my mistake.

This is just an introduction to gRPC. I hope this simple explanation and tutorial can help you. I think it is maybe enough to inspire you to migrate from REST API to gRPC. There are some features of gRPC I didn't cover here. Maybe in the next articles.

References