Getting Started with gRPC

Getting Started with gRPC

Implementing basic server and client

What is gRPC?

gRPC is an interface definition language IDL and message format.

What does that mean?

We can describe the functionality of services in terms of the payload that is sent and what response is return, we can also describe the type of connection we want to create (uni directional, bi directional a stream etc).

After we describe our service, the interface can be generated for a specific language and then be implemented by clients and servers to share serialized data in protocol buffer format.

Let's create a simple service and client.

Describing the service

Let's say we want to create a simple blog post app, to do so we first describe the data structures and services in a file with a .proto extension.

//Add which syntax is used, proto2 by default
syntax = 'proto3';

package blog;

//Define the service
service BlogPostService {
    //Add methods to the services
    //Here the communication type is the simplest 'Unary rpc'
    rpc GetOne (GetOneRequest) returns (GetOneResponse);
}

//Describe the methods data structure
message GetOneRequest {
    int32 post_id = 1;
}

message GetOneResponse {
      BlogPost post = 1;
      Author author = 2;
}

message BlogPost {
    string title = 1;
    string description = 2;
    string content = 3;
    repeated string tags = 4;
}

message Author {
    string first_name = 1;
    string last_name = 2;
}

As you have probably noticed, each message field is followed by a number. This number is unique and represents the field in the binary payload. Once it is used this number should not be used by any other filed. If we don't need the field any more we can replaced it by the 'reserved' keyword.

message BlogPost {
    string title = 1;
    string description = 2;
    string content = 3;
    reserved 4;
}

We mentioned early that the rpc method in the previous BlogPostService was 'Unary'. This means that the client will call to the server, wait for the server to create its response , get the response or timeout and then close the underlining HTTP/2.0 connection.

Implementing the Interface

Let's say we want to implement the server in python and the client in nodejs.

Assuming we have the following directory structure.

.
├── client
│   └── proto_build   
├── proto
│   └── blog.proto
└── server
    └── proto_build

To generate the interfaces we need the protoc tool.

One quick way of installing the compiler for python.

pip install grpcio-tools

And running protoc to generate the service interface.

python -m grpc_tools.protoc \
 -I=proto --python_out=server/proto_build \
 --grpc_python_out=server/proto_build \
proto/blog.proto

For the nodejs client.

npm install -g grpc-tools
grpc_tools_node_protoc \
 --proto_path=proto/ \
 --js_out=import_style=commonjs,binary:client/proto_build/ \
 --grpc_out=grpc_js:client/proto_build/ \
 proto/blog.proto

We will get the following files generated.

.
├── client
│   └── proto_build
│       ├── blog_pb.js
│       └── blog_pb_grpc.js
├── proto
│   └── blog.proto
└── server
    └── proto_build
        ├── blog_pb2.py
        └── blog_pb2_grpc.py

Server

To implement the server we need to:

  • Start a gRPC server to listen for request
  • Implement the business logic using the generated interface

gRPC Server

import grpc
from concurrent import futures
from proto_build import blog_pb2_grpc
from blog import BlogPostUseCase

def server() -> None:
      # Create Grpc Server
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    try:
        # Add Implementation for BlogPostService
        blog_pb2_grpc.add_BlogPostServiceServicer_to_server(
                servicer=BlogPostUseCase(), 
                server=server
            )

        # Intitialize and start the server
        server.add_insecure_port('[::]:50051')
        server.start()
        print('Listening on Port 50051')
        server.wait_for_termination()
    except:
        server.stop(grace=None)

server()

BlogPostService

from proto_build.blog_pb2_grpc import BlogPostServiceServicer
from proto_build.blog_pb2 import Author, BlogPost, GetOneRequest, GetOneResponse

# Extend Servicer
class BlogPostUseCase(BlogPostServiceServicer):

    def GetOne(self, request: GetOneRequest, context) -> GetOneResponse:
        # Fetch Data from repository and create response messages
        post =BlogPost(
                title="gRPC is very fast",
                description="Using gRPC for micro services communications",
                content="gRPC ...",
                tags=["gRPC","backend","micro services"]
            )
        author = Author(first_name="Frank",last_name="Cara")
        return GetOneResponse(post=post,author=author)

Client

To implement the client we need to:

  • Start a gRPC client to make the request
  • Crete the message to sent to the rpc metod
  • Call rpc method with the request message
  • Handle the response
let grpc = require('@grpc/grpc-js')

let services = require('./proto_build/blog_grpc_pb')
let messages = require('./proto_build/blog_pb')

const serverHost = 'localhost'
const serverPort = '50051'

function main() {
    //Create grpc client
    let client = new services.BlogPostServiceClient(
        `${serverHost}:${serverPort}`,
        grpc.credentials.createInsecure()
    )
    //Create request for rpc method
    let request = new messages.GetOneRequest()
    request.setPostId(1)
    //Call rpc method
    client.getOne(request, getOneRequestHandler)
}

//Handle Response
function getOneRequestHandler(err, response) {
    if (response) {
        let author = {
            firstName: response.getAuthor().getFirstName(),
            lastName: response.getAuthor().getLastName()
        }

        let post = {
            title: response.getPost().getTitle(),
            description: response.getPost().getDescription(),
            content: response.getPost().getContent(),
            tags: response.getPost().getTagsList()
        }

        console.log(author);
        console.log(post);
    }
}

main()

GitHub Repo

For more information about how to create proto files and language specific interface generation please check up google's protobuff Guide.

Happy Coding.