To round off the list of useful Google technologies for now, let’s look at gRPC. Building on top of Protocol Buffers, it enables a language agnostic RPC definition. Similar to protobufs, auto-generated source code for a long list of compatible languages can easily be integrated. This includes the source for both the client and the server side. The communication is by protobuf objects, over HTTP.

Proto Service

Like protobufs, the RPC service API is defined in a .proto file.

Notice the service and rpc keywords. Within a service, there can be one or more rpc methods. The method declaration follows the pattern “rpc MethodName (InputType) returns (ReturnType) {}”. However, as opposed to many languages, there can only be one input and one return object, which means that it is common to have dedicated Request and Response types with nested fields.

Finally, the import statement includes the proto we looked in the previous example. The Person message can then be used in the fields (or grpc) of this proto file.

import "_includes/src/com/rememberjava/protobuf/person.proto";

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  Person from = 1;
  Person to = 2;

  string message = 3;
}

message HelloReply {
  string reply = 1;
}

Bazel

To compile the gRPC service code, we need an additional Bazel rule. Now, gRPC compile rules have changed frequently over the years, often with incompatible versions and upgrades. For now, we’re using the java_grpc_library rule from rules_proto_grpc_java. This rule will also compile the imported person.proto.

Notice the protos field, similar to srcs in other rules. It takes the proto_library dependencies of all protos and dependencies to compile.

load("@rules_proto_grpc_java//:defs.bzl", "java_grpc_library")

proto_library(
    name = "hello_proto",
    srcs = ["hello.proto"],
    deps = [
        "//_includes/src/com/rememberjava/protobuf:person_proto",
    ],
)

java_grpc_library(
    name = "hello_java_grpc",
    protos = [
        ":hello_proto",
        "//_includes/src/com/rememberjava/protobuf:person_proto",
    ],
)

Finally, there are two java_binary rules for the server and client which we’ll look at next.

java_binary(
    name = "HelloServer",
    srcs = ["HelloServer.java"],
    deps = [":hello_java_grpc"],
)

java_binary(
    name = "HelloClient",
    srcs = ["HelloClient.java"],
    deps = [":hello_java_grpc"],
)

Server

This examples includes a very minimal server class, which combines the main, start and RPC methods. Typically, these would be split over multiple classes and files.

First, the sayHello RPC method, which takes the protobufs HelloRequest input and HelloReply output as parameters. Within the methods, the usual protobuf generated API methods apply.

    @Override
    public void sayHello(HelloRequest request,
			 StreamObserver<HelloReply> responseObserver) {
	String from = request.getFrom().getFirstname();
	String to = request.getTo().getFirstname();
	System.out.println("Server: " + from + " says hello to " + to);

	HelloReply response = HelloReply
	    .newBuilder()
	    .setReply(to + " sends greetings.")
	    .build();

	responseObserver.onNext(response);
	responseObserver.onCompleted();
    }

To start and let the server run, a io.grpc.Server objected is constructed our GreeterGrpc as the Service. This runs in blocking mode.

    void start() throws IOException, InterruptedException {
	int port = 1234;

	System.out.println("Starting gRPC HelloServer on port " + port);
	
	ServerBuilder<?> serverBuilder = Grpc.newServerBuilderForPort(
	    port, InsecureServerCredentials.create());
	Server server = serverBuilder.addService(this).build();
	server.start();
	server.awaitTermination();
    }

Client

Finally, we fire up a minimal gRPC client to make a request on the server. It news a channel, a stub (which has the service API) and a request. After the call, the reply from the server is printed and we shutdown and exit.

    void hello() {
	int port = 1234;
	
	ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", port)
          .usePlaintext().build();

	GreeterGrpc.GreeterBlockingStub stub =
	    GreeterGrpc.newBlockingStub(channel);

	HelloRequest request = HelloRequest.newBuilder()
	    .setFrom(Person.newBuilder()
		     .setFirstname("Foo").build())
	    .setTo(Person.newBuilder()
		   .setFirstname("Bar").build())
	    .build();
	    
	HelloReply reply = stub.sayHello(request);
	System.out.println("Reply on client: " + reply.getReply());

	channel.shutdown();
    }

Run it

To run the server and client, use two separate terminal windows and call, in order:

bazel run :HelloServer
bazel run :HelloClient

Files

Here are files includes in this example:

hello.proto
GitHub Raw
syntax = "proto3";

import "_includes/src/com/rememberjava/protobuf/person.proto";

option java_package = "com.rememberjava.grpc";
option java_multiple_files = true;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  Person from = 1;
  Person to = 2;

  string message = 3;
}

message HelloReply {
  string reply = 1;
}
person.proto
GitHub Raw
syntax = "proto3";

option java_package = "com.rememberjava.protobuf";
option java_multiple_files = true;

message Person {
  string firstname = 1;
  string lastname = 2;
  int32 id = 3;
  string email = 4;
}
BUILD
GitHub Raw
load("@rules_proto_grpc_java//:defs.bzl", "java_grpc_library")

proto_library(
    name = "hello_proto",
    srcs = ["hello.proto"],
    deps = [
        "//_includes/src/com/rememberjava/protobuf:person_proto",
    ],
)

java_grpc_library(
    name = "hello_java_grpc",
    protos = [
        ":hello_proto",
        "//_includes/src/com/rememberjava/protobuf:person_proto",
    ],
)

java_binary(
    name = "HelloServer",
    srcs = ["HelloServer.java"],
    deps = [":hello_java_grpc"],
)

java_binary(
    name = "HelloClient",
    srcs = ["HelloClient.java"],
    deps = [":hello_java_grpc"],
)
HelloServer.java
GitHub Raw
package com.rememberjava.grpc;

import java.io.IOException;

import io.grpc.Grpc;
import io.grpc.InsecureServerCredentials;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

import com.rememberjava.grpc.GreeterGrpc;
import com.rememberjava.grpc.HelloReply;
import com.rememberjava.grpc.HelloRequest;


class HelloServer extends GreeterGrpc.GreeterImplBase {

    @Override
    public void sayHello(HelloRequest request,
			 StreamObserver<HelloReply> responseObserver) {
	String from = request.getFrom().getFirstname();
	String to = request.getTo().getFirstname();
	System.out.println("Server: " + from + " says hello to " + to);

	HelloReply response = HelloReply
	    .newBuilder()
	    .setReply(to + " sends greetings.")
	    .build();

	responseObserver.onNext(response);
	responseObserver.onCompleted();
    }

    void start() throws IOException, InterruptedException {
	int port = 1234;

	System.out.println("Starting gRPC HelloServer on port " + port);
	
	ServerBuilder<?> serverBuilder = Grpc.newServerBuilderForPort(
	    port, InsecureServerCredentials.create());
	Server server = serverBuilder.addService(this).build();
	server.start();
	server.awaitTermination();
    }

    public static void main(String[] args) throws IOException, InterruptedException {
	new HelloServer().start();
    }
}
HelloClient.java
GitHub Raw
package com.rememberjava.grpc;

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

import com.rememberjava.protobuf.Person;
import com.rememberjava.grpc.GreeterGrpc;
import com.rememberjava.grpc.HelloReply;
import com.rememberjava.grpc.HelloRequest;


class HelloClient {

    void hello() {
	int port = 1234;
	
	ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", port)
          .usePlaintext().build();

	GreeterGrpc.GreeterBlockingStub stub =
	    GreeterGrpc.newBlockingStub(channel);

	HelloRequest request = HelloRequest.newBuilder()
	    .setFrom(Person.newBuilder()
		     .setFirstname("Foo").build())
	    .setTo(Person.newBuilder()
		   .setFirstname("Bar").build())
	    .build();
	    
	HelloReply reply = stub.sayHello(request);
	System.out.println("Reply on client: " + reply.getReply());

	channel.shutdown();
    }

    public static void main(String[] args) {
	new HelloClient().hello();
    }
}