gRPC is a high-performance, open-source RPC (Remote Procedure Call) framework created by Google. It’s designed for efficient communication between distributed systems and microservices. In this post, I will cover the common uses of gPRC.
Getting started with gRPC
In this section, I’m gonna to use the hello-world demo to show how to develop a gRPC application. The process of developing a gPRC application:
Creating and initializing a gRPC project with Java.
Creating a service interface definition using protocol buffers.
Generating server-side (server skeleton) and client-side (client stub) code for the programming language of your choice, which simplifies the server- and client-side logic by providing the low-level communication abstractions.
Implementing a service’s business logic.
Running a gRPC server with the service you implemented.
Invoking the service through the gRPC client application.
Creating and initializing a gRPC project with Java
Using Gradle
1. Create a Java application with the gradle init command.
The following is the complete Gradle configuration.
app/build.gradle.kts
import com.google.protobuf.gradle.id
plugins { // Apply the application plugin to add support for building a CLI application in Java. application // Add protobuf plugin id("com.google.protobuf") version "0.9.5" }
repositories { // Use Maven Central for resolving dependencies. mavenCentral() }
dependencies { // Use JUnit Jupiter for testing. testImplementation(libs.junit.jupiter) testRuntimeOnly("org.junit.platform:junit-platform-launcher") // This dependency is used by the application. implementation(libs.guava) // grpc implementation("io.grpc:grpc-netty:${Versions.grpc}") implementation("io.grpc:grpc-protobuf:${Versions.grpc}") implementation("io.grpc:grpc-stub:${Versions.grpc}") // protobuf implementation("com.google.protobuf:protobuf-java:${Versions.protobuf}") }
// Apply a specific Java toolchain to ease working on different environments. java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }
application { // Define the main class for the application. mainClass = "com.taogen.grpc.App" }
/** * Customizing Protobuf compilation */ protobuf { // Locate external executables // By default, the plugin will search for the protoc executable in the system search path. We recommend you to take the advantage of pre-compiled protoc that published on Maven Central. protoc { artifact = "com.google.protobuf:protoc:${Versions.protobuf}" }
// Locate the codegen plugins plugins { // Locate a plugin with name 'grpc'. id("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:${Versions.grpc}" } } // Customize code generation tasks generateProtoTasks { all().forEach { it.builtins { java { // Optionally specify options for the builtin java codegen. // This yields "--java_out=example_option1=true,example_option2:/path/to/output" } } it.plugins { id("grpc") { // Add grpc output without any option. grpc must have been defined in the protobuf.plugins block. // This yields "--grpc_out=/path/to/output" on the protoc commandline. } } } } }
tasks.named<Test>("test") { // Use JUnit Platform for unit tests. useJUnitPlatform() }
<!-- Vanilla protoc plugins - these are platform-specific executables just like protoc is. --> <binaryMavenPlugins> <binaryMavenPlugin> <groupId>io.grpc</groupId> <artifactId>protoc-gen-grpc-java</artifactId> <version>${grpc.version}</version> </binaryMavenPlugin> </binaryMavenPlugins>
<!-- JVM plugins are distributed as JARs rather than native system executables. The protobuf-maven-plugin will automatically bootstrap them for you, without the need for additional tooling for your platform. It should "just work". --> <jvmMavenPlugins> <jvmMavenPlugin> <groupId>com.salesforce.servicelibs</groupId> <artifactId>reactor-grpc</artifactId> <version>${reactor-grpc.version}</version> </jvmMavenPlugin> </jvmMavenPlugins> </configuration> <executions> <execution> <goals> <goal>generate</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
Creating a service interface definition using protocol buffers
option java_multiple_files = true: By default, the compiler generates all the Java code in a single Java file. Setting the option java_multiple_files to true, the gRPC base code will be generated in individual files.
Generating server-side (server skeleton) and client-side (client stub) code
Using Gradle
Running the following command line to generate gRPC base code using the protobuf plugin protobuf-gradle-plugin.
./gradlew generateProto # or ./gradlew build
The directory of generated code is app/build/generated/sources/proto.
Using Maven
Running the following command line to generate gRPC base code using the protobuf plugin io.github.ascopes:protobuf-maven-plugin.
mvn protobuf:generate # or mvn clean package
The directory of generated code is target/generated-sources.
Implementing a service’s business logic
Override the methods of HelloServiceGrpc.HelloServiceImplBase.
Running the main() method to see the message from the gRPC server response.
Response: Hello, Thomas Jones
Streaming
Server-side streaming
In simple RPC you always had a single request and single response in the communication between the gRPC server and gRPC client. In server-side streaming RPC, the server sends back a sequence of responses after getting the client’s request message. This sequence of multiple responses is known as a “stream.” After sending all the server responses, the server marks the end of the stream by sending the server’s status details as trailing metadata to the client.
1. Create the protobuf file
user_info.proto
syntax = "proto3";
import"google/protobuf/wrappers.proto";
option java_multiple_files = true;
package com.taogen.grpc;
service UserService { rpc searchUsers(google.protobuf.StringValue) returns (stream User); }
message User { int32 id = 1; string name = 2; int32 age = 3; string gender = 4; }
import "google/protobuf/wrappers.proto": wrappers.proto is a standard protobuf file provided by Google. It defines wrapper message types for primitive values (like int32, bool, string).
UsernotExistUser= User.newBuilder() .setId(-1) .setName("NotExistUser") .setAge(20) .setGender("Male") .build(); userStreamObserver.onNext(notExistUser); // Tell the server we're done userStreamObserver.onCompleted();
Thread.sleep(3000); // Wait for async request to complete channel.shutdown(); } }
Appendixes
Custom protobuf configuration
/** * Customizing proto source directories */ sourceSets { main { proto { // In addition to the default 'src/main/proto' srcDir("src/main/protobuf") } } test { proto { // In addition to the default 'src/test/proto' srcDir("src/test/protobuf") } } }
protobuf { // Customize code generation tasks generateProtoTasks { all().forEach { it.builtins { java { // Optionally specify options for the builtin java codegen. // This yields "--java_out=example_option1=true,example_option2:/path/to/output" } } it.plugins { // Add grpc output without any option. grpc must have been defined in the protobuf.plugins block. // This yields "--grpc_out=/path/to/output" on the protoc commandline. id("grpc") { // Use subdirectory 'grpcjava' instead of the default 'grpc' outputSubDir = "grpcjava" } } } } }
References
[1] gRPC: Up and Running by Kasun Indrasiri and Danesh Kuruppu