Introduction about gRPC and implement with Spring boot

Introduction about gRPC and implement with Spring boot

Roman Le

A high performance, open source universal RPC framework

Introduction

gRPC is a modern open source high performance Remote Procedure Call (RPC) framework, was initially created by Google, first public release at August 2016.

It helps to eliminate boilerplate code and connect polyglot services in and across data centers.

Overview

  1. Define .proto file.
  2. Generate server and client code.
  3. Create server application, implement the generated service and spawn the gRPC server, make request with BloomRPC.
  4. Create client application, make RPC call using generated stubs.

Define .proto file

gRPC use Protocol Buffer (a.k.a. Protobuf) for strict schema definition. It's where store your data and function contracts in the form of a proto file.

Let's create a HelloService.proto file for our sample HelloService.

// Which syntax this file uses.
syntax = "proto3";

// Specify the package we want to use for our generated Java classes.
package com.example.grpc;

// Everything will be generated in individual files.
// By default, the compiler generates all the Java code in a single Java file.
option java_multiple_files = true;

// Request payload
message HelloRequest {
  string name = 1;
}

// Response payload
message HelloResponse {
  string greeting = 1;
}

// Service contract
service HelloService {
  rpc hello(HelloRequest) returns (HelloResponse);
}

In request/response payload, each attribute that goes into the message is defined, along with its type.

A unique number needs to be assigned to each attribute, called the tag.

Finally, the hello() operation accepts a unary request, and returns a unary response.

Dependencies (Gradle)

buildscript {
    ext {
        protobufVersion = '3.19.1'
        protobufPluginVersion = '0.8.18'
        grpcVersion = '1.42.1'
    }
}

plugins {
    id 'java'
    id 'com.google.protobuf' version "${protobufPluginVersion}"
}

group = 'org.example'
version = '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    testImplementation platform('org.junit:junit-bom:5.9.1')
    testImplementation 'org.junit.jupiter:junit-jupiter'
    implementation 'net.devh:grpc-spring-boot-starter:2.14.0.RELEASE'
    compileOnly 'jakarta.annotation:jakarta.annotation-api:1.3.5'
}

test {
    useJUnitPlatform()
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:${protobufVersion}"
    }
    generatedFilesBaseDir = "$projectDir/src/generated"
    clean {
        delete generatedFilesBaseDir
    }
    plugins {
        grpc {
            artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
        }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {}
        }
    }
}

Generate server and client code

Let's build introduction-grpc project, the protoc-gen-grpc-java lib will generate the code into /$projectDir/src/generated (configurated in the gradle file)

Create server application

Create new module named server-side inside the introduction-grpc project.

Update the server-side gradle file

plugins {
    id 'java'
}

group = 'org.example'
version = '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation project(path: ':') // Reference to parent project 
    implementation 'net.devh:grpc-server-spring-boot-starter:2.14.0.RELEASE' // gRPC server dependence
}

test {
    useJUnitPlatform()
}

Implement the generated service

Create the HelloService.java implement HelloServiceGrpc.HelloServiceImplBase

package com.example.server.services;

import com.example.grpc.HelloRequest;
import com.example.grpc.HelloResponse;
import com.example.grpc.HelloServiceGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;

@GrpcService
public class HelloService extends HelloServiceGrpc.HelloServiceImplBase{
    @Override
    public void hello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
        HelloResponse reply = HelloResponse.newBuilder()
                .setGreeting("Hello ==> " + request.getName())
                .build();
        responseObserver.onNext(reply);
        responseObserver.onCompleted();
    }
}

In the generate server code, we have generated the HelloServiceGrpc contain code base for server implement.

The hello function doesn't return HelloResponse. Instead, it takes the second argument as StreamObserver<HelloResponse>, which is a response observer, a callback for the server to call with its response.

gRPC uses builders for creating objects. It use HelloResponse.newBuilder() and set the greeting text to build a HelloResponse object. Then set this object to the responseObserver's onNext() method to send it to the client.

Finally, we'll need to call onCompleted() to specify that we’ve finished dealing with the RPC. Otherwise, the connection will be hung, and the client will just wait for more information to come in.

Spawn the gRPC server

We'll use spring boot for spawn the gRPC server.

package com.example.server;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServerApplication.class, args);
    }
}

Default, server will start at port 9090

Make request with BloomRPC

Install BloomRPC from this

Import the HelloService.proto file

Make the request

We can see the response in the right side.

Create client application

Create new module named client-side inside the introduction-grpc project.

Update the client-side gradle file

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.1.0'
    id 'io.spring.dependency-management' version '1.1.0'
}

group = 'org.example'
version = '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation project(path: ':') // Reference to parent project
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    implementation 'net.devh:grpc-client-spring-boot-starter:2.14.0.RELEASE'
}

test {
    useJUnitPlatform()
}

application.yml

server:
  port: 8080
spring:
  application:
    name: grpc-client

grpc:
  client:
    grpc-client:
      address: 'static://127.0.0.1:9090'
      enableKeepAlive: true
      keepAliveWithoutCalls: true
      negotiationType: plaintext

We need a GrpcConfig configuration to prevent NullPointerException when using @GrpcClient

NullPointerException when using @GrpcClient · Issue #823 · yidongnan/grpc-spring-boot-starter
I’m trying to develop a SpringBoot based app as a client to gRPC services. For that, I’m trying the Python HelloWorld server example, from the main gRPC website. It works without SpringBoot, with b...
package com.example.client.configs;

import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.context.annotation.Configuration;

@Configuration
@ImportAutoConfiguration({
        net.devh.boot.grpc.client.autoconfigure.GrpcClientAutoConfiguration.class,
        net.devh.boot.grpc.client.autoconfigure.GrpcClientMetricAutoConfiguration.class,
        net.devh.boot.grpc.client.autoconfigure.GrpcClientHealthAutoConfiguration.class,
        net.devh.boot.grpc.client.autoconfigure.GrpcClientSecurityAutoConfiguration.class,
        net.devh.boot.grpc.client.autoconfigure.GrpcClientTraceAutoConfiguration.class,
        net.devh.boot.grpc.client.autoconfigure.GrpcDiscoveryClientAutoConfiguration.class,

        net.devh.boot.grpc.common.autoconfigure.GrpcCommonCodecAutoConfiguration.class,
        net.devh.boot.grpc.common.autoconfigure.GrpcCommonTraceAutoConfiguration.class,
})

public class GrpcConfig {
}

Implement HelloService.java

package com.example.client.services;


import com.example.grpc.HelloRequest;
import com.example.grpc.HelloServiceGrpc.HelloServiceBlockingStub;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.stereotype.Service;

@Service
public class HelloService {
    @GrpcClient("grpc-client")
    private HelloServiceBlockingStub helloServiceStub;

    public String receiveGreeting(String name) {
        HelloRequest request = HelloRequest.newBuilder()
                .setName(name)
                .build();
        return helloServiceStub.hello(request).getGreeting();
    }
}

You can see we specific the grpc-client value in annotation @GrpcClient. That mean we will use grpc-client connection in the application.yml, which point to server address 127.0.0.1:9090

We'll need to inject HelloServiceBlockingStub, which we'll use to make the actual remote call to hello()

The stub is the primary way for clients to interact with the server.

We'll pass the HelloRequest. We can use the auto-generated setters to set the name attributes of the HelloRequest object.

The server returns the HelloResponse object.

Alos we need to create a resource (api) to trigger gRPC request.

Spawn client.

Make a request to resource (api)

Conclusion

This post is quickly introduce the gRPC and how to implement with Spring boot.

There are a lot of things interest about gRPC.

The code is available in this.

References:

Happy coding.