PolarSPARC



Bhaskar S 11/29/2020


Overview

gRPC is a distributed, heterogeneous, high performance, high throughput, modern, open source, remote procedure call (RPC) framework from Google with the following features:

In short, with gRPC, services deployed on distributed systems can communicate with each other in an efficient and secure manner.

The following diagram illustrates the high-level architecture of gRPC (along with some circled number annotations):

gRPC Architecture
Figure-1

The circled number annotations indicate the steps to implement, build, and deploy a gRPC based service and are as follows:

  1. Create a .proto file with the definitions for the request message Req, the response message Res, and the service interface Service

  2. Compile the .proto file to generate the gRPC code for the server gRPC Server and the gRPC code for the client gRPC Stub for the chosen language

  3. Extend the gRPC Server code to implement and build the server side or the service provider

  4. Extend the gRPC Stub code to implement and build the cide side or the service consumer

We will demonstrate gRPC using the Go and the Java 11 programming languages.

Installation and Setup

The installation is on a Ubuntu 20.04 LTS based Linux desktop.

We will also assume that the logged in user-id is alice with the home directory located at /home/alice.

We need to install the packages for the Go programming language called golang, the Java 11 programming language called openjdk-11-jdk, the Maven build management tool for Java called maven, and the protobuf compiler called protobuf-compiler from the Ubuntu repository.

To install the mentioned packages, execute the following commands:

$ sudo apt-get update

$ sudo apt-get install golang openjdk-11-jdk maven protobuf-compiler -y

For the Go language, we will create a directory called go under the home directory of the logged in user and set the GOPATH environment variable by executing the following commands:

$ cd $HOME

$ mkdir go

$ export GOPATH=$HOME/go

To setup the directory structure and Go dependencies for the demonstrations, execute the following commands:

$ cd $GOPATH

$ mkdir -p src/polarsparc.com/grpc

$ cd $GOPATH/src/polarsparc.com/grpc

$ go mod init polarsparc.com/grpc

$ GO111MODULE=on go get -u google.golang.org/grpc

$ GO111MODULE=on go get github.com/golang/protobuf/protoc-gen-go

To setup the Java directory structure for the demonstrations, execute the following commands:

$ cd $HOME

$ mkdir -p java/grpc

$ cd $HOME/java/grpc

$ mkdir -p src/main/java src/main/proto src/test/java target

$ mkdir -p src/main/java/com/polarsparc src/test/java/com/polarsparc

For the Java language, we will leverage Maven to manage the build as well as the package dependencies.

The following is the Maven pom.xml file located in the directory $HOME/java/grpc:


pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.polarsparc.grpc</groupId>
    <artifactId>gRPC</artifactId>
    <version>1.0</version>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>1.33.1</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <version>1.33.1</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>1.33.1</version>
        </dependency>
        <!-- Needed for Java 9 and above -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>annotations-api</artifactId>
            <version>6.0.53</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.6.3</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.6.2</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.14.0:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.33.1:exe:${os.detected.classifier}</pluginArtifact>
                    <protoSourceRoot>${basedir}/src/main/proto</protoSourceRoot>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Hands-on with gRPC

One of the prerequisites for gRPC is have a fundamental understanding of Protocol Buffers.

gRPC supports four types of communication patterns which are as follows:

Unary RPC

The following diagram illustrates the high-level architecture of Unary communication pattern:

Unary Architecture
Figure-2

For the Unary RPC demonstration, we will implement a simple Greet service, where the client sends a name in the request and the server responds back with an appropriate greeting message based on the time of the day.

We will first demonstrate the Greet service using the Go programming language.

In the $GOPATH directory, create the project directory hierarchy by executing the following commands:

$ cd $GOPATH/src/polarsparc.com/grpc

$ mkdir -p unary unary/greetpb unary/server unary/client

The following are the contents of the file greet.proto located in the directory $GOPATH/src/polarsparc.com/grpc/unary/greetpb as shown below:


greet.proto
/*
    @Author: Bhaskar S
    @Blog:   https://www.polarsparc.com
    @Date:   28 Nov 2020
*/

syntax = "proto3";

package unary;

option go_package = "polarsparc.com/grpc/unary/greetpb";

message GreetRequest {
  string name = 1;
}

message GreetResponse {
  string message = 1;
}

service GreetService {
  rpc greet(GreetRequest) returns (GreetResponse);
}

The request message is defined as GreetRequest and the response message is defined as GreetResponse. The service interface is defined as GreetService with an RPC method greet that takes in a GreetRequest as an input and returns a GreetResponse.

To compile the greet.proto file, execute the following commands:

$ cd $GOPATH/src/polarsparc.com/grpc/unary

$ protoc greetpb/greet.proto --go_out=plugins=grpc:$GOPATH/src

On success, this will generate the Go code file called greet.pb.go located in the directory $GOPATH/src/polarsparc.com/grpc/unary/greetpb.

From the file greet.pb.go, we see the GreetServiceServer interface, as shown below, that the server needs to implements:


greet.pb.go
.
.
.
type GreetServiceServer interface {
    Greet(context.Context, *GreetRequest) (*GreetResponse, error)
}
.
.
.

The following are the contents of the file server.go for the Unary RPC server that implements the GreetServiceServer interface and is located in the directory $GOPATH/src/polarsparc.com/grpc/unary/server as shown below:


server.go
/*
  @Author: Bhaskar S
  @Blog:   https://www.polarsparc.com
  @Date:   28 Nov 2020
*/

package main

import (
  "context"
  "google.golang.org/grpc"
  "log"
  "net"
  "polarsparc.com/grpc/unary/greetpb" // [1]
  "time"
)

type server struct {} // [2]

func (s *server) Greet(_ context.Context, req *greetpb.GreetRequest) (*greetpb.GreetResponse, error) { // [3]
  log.Printf("Received a Greet request with req: %v\n", req)

  name := req.GetName()
  message := getMessage(name)
  res := &greetpb.GreetResponse{
    Message: message,
  }

  return res, nil
}

const (
  addr = "127.0.0.1:20001"
)

func main() {
  log.Printf("Ready to start the Greet server on %s", addr)

  lis, err := net.Listen("tcp", addr)
  if err != nil {
    log.Fatalf("Failed to create listener on %s", addr)
  }

  srv := grpc.NewServer() // [4]

  greetpb.RegisterGreetServiceServer(srv, &server{}) // [5]

  if err = srv.Serve(lis); err != nil {
    log.Fatalf("Failed to start server: %v", err)
  }
}

func getMessage(name string) string {
  hour := time.Now().Hour()

  msg := "Hello, " + name + ", "
  if hour < 12 {
    msg = msg + "Good Morning !!!"
  } else if hour < 16 {
    msg = msg + "Good Afternoon !!!"
  } else if hour < 21 {
    msg = msg + "Good Evening !!!"
  } else {
    msg = msg + "Good Night !!!"
  }

  return msg
}

The following are brief descriptions for some of the Go type(s)/method(s) used in the code above:

The following are the contents of the file client.go that implements the Unary RPC client for the GreetService located in the directory $GOPATH/src/polarsparc.com/grpc/unary/client as shown below:


client.go
/*
  @Author: Bhaskar S
  @Blog:   https://www.polarsparc.com
  @Date:   28 Nov 2020
*/

package main

import (
  "golang.org/x/net/context"
  "google.golang.org/grpc"
  "log"
  "polarsparc.com/grpc/unary/greetpb"
)

const (
  addr = "127.0.0.1:20001"
)

func main()  {
  log.Println("Ready to start the Greet client...")

  conn, err := grpc.Dial(addr, grpc.WithInsecure())
  if err != nil {
    log.Fatalf("Failed to connect to %s", addr)
  }
  defer conn.Close()

  cl := greetpb.NewGreetServiceClient(conn) // [1]

  req := &greetpb.GreetRequest{ // [2]
    Name: "Alice",
  }
  res, err := cl.Greet(context.Background(), req) // [3]
  if err != nil {
    log.Fatalf("Failed to send Greet request to %s [%v]", addr, err)
  }

  log.Printf("%s\n", res.Message)
}

The following are brief descriptions for some of the Go type(s)/method(s) used in the code above:

The following diagram illustrates the contents of the directory $GOPATH/src/polarsparc.com/grpc:

grpc Directory
Figure-3

Open two Terminal windows - one for the server and one for the client.

In the server Terminal, execute the following commands:

$ cd $GOPATH/src/polarsparc.com/grpc/unary/server

$ go run server.go

The following would be the typical output:

Output.1

2020/11/28 20:48:59 Ready to start the Greet server on 127.0.0.1:20001

In the client Terminal, execute the following commands:

$ cd $GOPATH/src/polarsparc.com/grpc/unary/client

$ go run client.go

The following would be the typical output:

Output.2

2020/11/28 20:50:55 Ready to start the Greet client...
2020/11/28 20:50:55 Hello, Alice, Good Evening !!!

AWESOME !!! We have successfully demonstrated the Unary gRPC communication style using the Go language.

The following are the contents of the file greet.proto located in the directory $HOME/java/grpc/src/main/proto as shown below:


greet.proto
/*
  @Author: Bhaskar S
  @Blog:   https://www.polarsparc.com
  @Date:   28 Nov 2020
*/

syntax = "proto3";

package unary;

option java_multiple_files = true;
option java_package = "com.polarsparc.gun";

message GreetRequest {
  string name = 1;
}

message GreetResponse {
  string message = 1;
}

service GreetService {
  rpc greet(GreetRequest) returns (GreetResponse);
}

To compile the greet.proto file, execute the following commands:

$ cd $HOME/java/grpc

$ mvn compile

On success, this will generate some files in the directory $HOME/java/grpc/target/generated-sources/protobuf/java/com/polarsparc/gun.

The following diagram illustrates the contents of the directory $HOME/java/grpc/target/generated-sources :

generated-sources Directory
Figure-4

The following are the contents of the Java program called GreetService.java that implements the Unary gRPC service GreetService located in the directory $HOME/java/grpc/src/main/java/com/polarsparc/gun/server as shown below:


GreetService.java
/*
  @Author: Bhaskar S
  @Blog:   https://www.polarsparc.com
  @Date:   28 Nov 2020
*/

package com.polarsparc.gun.server;

import com.polarsparc.gun.GreetRequest;
import com.polarsparc.gun.GreetResponse;
import com.polarsparc.gun.GreetServiceGrpc;
import io.grpc.stub.StreamObserver;

import java.time.LocalTime;

public class GreetService extends GreetServiceGrpc.GreetServiceImplBase { // [1]
    @Override
    public void greet(GreetRequest request, StreamObserver<GreetResponse> responseObserver) { // [2]
        String message = getMessage(request.getName());

        GreetResponse response = GreetResponse.newBuilder()
                .setMessage(message)
                .build();

        responseObserver.onNext(response); // [3]
        responseObserver.onCompleted(); // [4]
    }

    private static String getMessage(String name) {
        LocalTime lt = LocalTime.now();

        int hour = lt.getHour();

        StringBuilder sb = new StringBuilder("Hello ").append(name).append(", ");
        if (hour < 12) {
            sb.append("Good Morning !!!");
        } else if (hour < 16) {
            sb.append("Good Afternoon !!!");
        } else if (hour < 21) {
            sb.append("Good Evening !!!");
        } else {
            sb.append("Good Night !!!");
        }

        return sb.toString();
    }
}

The following are brief descriptions for some of the Java class(es)/method(s) used in the code above:

The following are the contents of the Java program called GreetServer.java that implements the Unary RPC service GreetService located in the directory $HOME/java/grpc/src/main/java/com/polarsparc/gun/server as shown below:


GreetServer.java
/*
  @Author: Bhaskar S
  @Blog:   https://www.polarsparc.com
  @Date:   28 Nov 2020
*/

package com.polarsparc.gun.server;

import io.grpc.Server;
import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;

import java.io.IOException;
import java.net.InetSocketAddress;

public class GreetServer {
    public static void main(String[] args) {
        Server server = NettyServerBuilder // [1]
                .forAddress(new InetSocketAddress("127.0.0.1", 20001))
                .addService(new GreetService()) // [2]
                .build();

        try {
            server.start(); // [3]
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.print("Started the gRPC GreetService on 127.0.0.1:20001 ...\n");

        try {
            server.awaitTermination(); // [4]
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

The following are brief descriptions for some of the Java class(es)/method(s) used in the code above:

The following are the contents of the Java program called GreetClientTest.java that implements the Unary RPC client for GreetService located in the directory $HOME/java/grpc/src/test/java/com/polarsparc/gun/client as shown below:


GreetClientTest.java
/*
  @Author: Bhaskar S
  @Blog:   https://www.polarsparc.com
  @Date:   28 Nov 2020
*/

package com.polarsparc.gun.client;

import com.polarsparc.gun.GreetRequest;
import com.polarsparc.gun.GreetResponse;
import com.polarsparc.gun.GreetServiceGrpc;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class GreetClientTest {
    private GreetServiceGrpc.GreetServiceBlockingStub stub;

    @BeforeAll
    public void setup() {
        ManagedChannel channel = ManagedChannelBuilder.forAddress("127.0.0.1", 20001) // [1]
                .usePlaintext() // [2]
                .build();

        this.stub = GreetServiceGrpc.newBlockingStub(channel); // [3]
    }

    @Test
    public void greetTest() {
        GreetRequest request = GreetRequest.newBuilder() // [4]
                .setName("Bob")
                .build();
        GreetResponse response = this.stub.greet(request); // [5]
        System.out.printf("%s\n", response.getMessage());
    }
}

The following are brief descriptions for some of the Java class(es)/method(s) used in the code above:

The following diagram illustrates the contents of the directory $HOME/java/grpc:

Java grpc Directory
Figure-5

Open two Terminal windows - one for the server and one for the client.

In the server Terminal, execute the following commands:

$ cd $HOME/java/grpc

$ mvn exec:java -Dexec.mainClass=com.polarsparc.gun.server.GreetServer

The following would be the typical output:

Output.3

Started the gRPC GreetService on 127.0.0.1:20001 ...

In the client Terminal, execute the following commands:

$ cd $HOME/java/grpc

$ mvn test -Dtest=com.polarsparc.gun.client.GreetClientTest


!!! PITFALL !!!

Without the maven plugin maven-surefire-plugin defined in the pom.xml file, the maven command mvn test ... will NOT execute any of the tests

The following would be the typical output:

Output.4

Hello Bob, Good Night !!!

One could also test with the Go server running and using the Java client and vice versa.

!!! PITFALL !!!

Without the same package unary; defined in the greet.proto file, the Go server and the Java client or vice versa will not be able communicate with each other

References

Introduction to Google Protocol Buffers

gRPC Go Documentation

gRPC Java Documentation


© PolarSPARC