Puy Web
Profile Blog
EN TH
Blog gRPC with Spring Boot 101
gRPC with Spring Boot 101
Coding Jul 10, 2019

gRPC with Spring Boot 101

gRPC is an RPC (Remote Procedure Call) framework originally developed by Google in 2015 and is now a part of the Cloud Native Computing Foundation (CNCF). It utilizes the HTTP/2 protocol, which is significantly faster than HTTP/1.1, and uses Protocol Buffers (Protobuf) as its Interface Definition Language (IDL) to compile the code needed for development.

A major benefit of Protocol Buffers is that you only need to write the Protobuf definition once, and it can be compiled to connect with multiple programming languages. Currently, Protobuf supports C++, Java, Python, Objective-C, C#, JavaScript, Ruby, Go, PHP, and Dart. As you can see, it covers almost all popular modern languages.

Concept Diagram
https://grpc.io/docs/guides/

In addition to being faster than RESTful APIs, gRPC exchanges data in a binary format, unlike RESTful APIs which use text-based JSON. This binary serialization makes gRPC exceptionally fast. However, it does trade off some of the "loose coupling" benefits found in REST, as both the client and server must implement Protobuf and gRPC to communicate.

To get started with gRPC, you first write a Protobuf file to define the data exchange format. Then, you compile it into your target programming language for both the Server and Client sides.

This article will demonstrate how to experiment with gRPC using Spring Boot and Gradle for service-to-service communication.

Server Dependencies [build.gradle]

plugins {
    id 'org.springframework.boot' version '2.2.2.RELEASE'
    id 'io.spring.dependency-management' version '1.0.8.RELEASE'
    // grpc step 1
    id 'com.google.protobuf' version '0.8.8'
    id 'java'
}

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

repositories {
    mavenCentral()
}

// grpc step 2
def grpcVersion = '1.27.1'
def protobufVersion = '3.11.0'
def protocVersion = protobufVersion
def lombokVersion = '1.18.12'

dependencies {
    // grpc step 3
    compile "io.github.lognet:grpc-spring-boot-starter:3.5.1"

    implementation "org.springframework.boot:spring-boot-starter-web"
    testImplementation("org.springframework.boot:spring-boot-starter-test") {
        exclude group: "org.junit.vintage", module: "junit-vintage-engine"
    }

    implementation "io.grpc:grpc-netty-shaded:${grpcVersion}"
    implementation "io.grpc:grpc-protobuf:${grpcVersion}"
    implementation "io.grpc:grpc-stub:${grpcVersion}"

    compileOnly "org.projectlombok:lombok:${lombokVersion}"
    annotationProcessor "org.projectlombok:lombok:${lombokVersion}"

    testCompileOnly "org.projectlombok:lombok:${lombokVersion}"
    testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}"
}

test {
    useJUnitPlatform()
}

// grpc step 4
protobuf {
    protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" }
    plugins {
        grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" }
    }
    generateProtoTasks {
        all()*.plugins { grpc {} }
    }
}

// grpc step 5: Inform IDE about the generated code
sourceSets {
    main {
        java {
            srcDirs 'build/generated/source/proto/main/grpc'
            srcDirs 'build/generated/source/proto/main/java'
        }
    }
}

Explanation of the steps:

  • Step 1: Define the plugins. org.springframework.boot and io.spring.dependency-management are for Spring Boot. com.google.protobuf is used to utilize the protobuf compiler.

  • Step 2: Define the version numbers for gRPC and Protobuf.

  • Step 3: Define dependencies.

    • grpc-spring-boot-starter allows using gRPC with Spring Boot to run it as a service.

    • spring-boot-starter-web is the standard Spring Boot web starter.

    • We exclude junit-vintage-engine from testing as it is not needed.

    • The io.grpc dependencies are the core gRPC libraries.

  • Step 4: Create the protobuf compilation tasks.

  • Step 5: Define the generated file paths so the IDE can locate them. The default compilation output paths are build/generated/source/proto/main/grpc and build/generated/source/proto/main/java.

Protocol Buffer [HelloService.proto]

syntax = "proto3";
option java_multiple_files = true;
package org.example.grpc.server;

message HelloRequest {
    string firstName = 1;
    string lastName = 2;
}

message HelloResponse {
    string greeting = 1;
}

service HelloService {
    rpc hello(HelloRequest) returns (HelloResponse);
}
  • syntax = "proto3";: Specifies the Protobuf version.

  • option java_multiple_files = true;: Ensures that multiple Java files are generated. Without this, everything compiles into a single massive Java file.

  • package: Sets the package name for the generated code.

  • message HelloRequest / message HelloResponse: Defines the message structures, field names, and data types (similar to defining Model classes).

  • service HelloService: Defines the service and its RPC methods, including request and response types.

After setting up the .proto file, run the Gradle build or the generateProto task. The generated files will be placed in build/generated/source/proto/main/.

Spring Boot gRPC Server Implement

Create the Spring Boot Application class:

package org.example.grpc;

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

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

Create the gRPC Service implementation:

@GRpcService
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
    @Override
    public void hello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
        System.out.println(request);

        HelloResponse response = HelloResponse.newBuilder()
                .setGreeting("GRPC Hello: " + request.getFirstName() + " " + request.getLastName())
                .build();

        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}
  • @GRpcService: Comes from the grpc-spring-boot-starter dependency, allowing Spring Boot to run gRPC as a service.

  • extends HelloServiceGrpc.HelloServiceImplBase: Allows us to override the service methods defined in the Protobuf file.

  • hello(...): This is the method signature generated by compiling the Protobuf file.

  • HelloResponse.newBuilder()...build(): Objects are created using the builder pattern generated by the Protobuf compiler.

  • StreamObserver<HelloResponse> responseObserver: Manages the state of the gRPC call (next, complete, error).

    • responseObserver.onNext(response);: Sends the response back to the client.

    • responseObserver.onCompleted();: Signals the client that the transaction is finished.

Server Configuration [application.properties]

server.port=8081
grpc.port=6081

  • server.port: The standard Spring Boot web port.

  • grpc.port: The dedicated port for the gRPC service.

Setting up the Client

Client Dependencies & Protocol Buffer: You can use the exact same build.gradle dependencies and the same HelloService.proto file for the client side. This is a core advantage of gRPC—you share the exact same contract.

Spring Boot Client Application:

package grpc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Create the gRPC Client Component:

package grpc.app;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import org.example.grpc.server.HelloRequest;
import org.example.grpc.server.HelloResponse;
import org.example.grpc.server.HelloServiceGrpc;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class HelloServiceClient {
    private final ManagedChannel channel;
    private final HelloServiceGrpc.HelloServiceBlockingStub blockingStub;

    public HelloServiceClient(@Value("${hello.service.endpoint}") String endpoint) {
        // Create a communication channel to the server
        this.channel = ManagedChannelBuilder.forTarget(endpoint)
                .usePlaintext()
                .build();

        // Passing channels to code makes it easier to test and reuse
        this.blockingStub = HelloServiceGrpc.newBlockingStub(this.channel);
    }

    public HelloResponse hello(String firstName, String lastName) {
        HelloRequest request = HelloRequest.newBuilder()
                .setFirstName(firstName)
                .setLastName(lastName)
                .build();
        System.out.println(request);
        
        HelloResponse response;
        try {
            response = blockingStub.hello(request);
        } catch (StatusRuntimeException e) {
            e.printStackTrace();
            return null;
        }
        
        System.out.println(response);
        return response;
    }
}
  • ManagedChannel channel: Establishes the communication link to the gRPC server.

  • HelloServiceBlockingStub blockingStub: Used to invoke the methods defined in the Protobuf file synchronously.

  • ManagedChannelBuilder.forTarget(endpoint).usePlaintext().build(): Initializes the channel targeting the server endpoint (using plaintext for testing, without TLS).

  • blockingStub.hello(request): Calls the remote gRPC service method and awaits the response.

Example REST Controller to Trigger the gRPC Client:

package grpc;

import grpc.app.HelloServiceClient;
import grpc.app.HelloServiceFeignClient;
import org.example.grpc.server.HelloResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @Autowired
    private HelloServiceClient helloServiceClient;

    @GetMapping("/hello")
    public String hello(@RequestParam("f") String firstName, @RequestParam("l") String lastName) {
        HelloResponse response = helloServiceClient.hello(firstName, lastName);
        if (response == null) {
            return "NullPointerException";
        } else {
            return response.toString();
        }
    }
}

จากทั้งหมดที่ได้ลองพัฒนามา จะเห็นว่ามีทั้งความง่าย และความซับซ้อนที่ต้องทำความเข้าใจเพิ่มขึ้นอยู่ ก็ถือว่าเป็นทางเลือกใหม่ที่จะช่วยให้การทำงานร่วมกันระว่าง service ทำงานในไวขึ้น ซึ่งจากเทรนในช่วง 2 - 3 ปี ที่ผ่านมา ก็จะเห็นว่าเริ่มมีหลายๆ ประเทศใช้ gRPC กันมากขึ้น หรือแม้แต่ประเทศจีนที่มีการใช้งาน gRPC กันแทน restful เป็นส่วนใหญ่แล้ว

Share this article:

Related Articles

Fix -bash: ng: command not found
Coding
Jul 15, 2019

Fix -bash: ng: command not found

When running any ng command, you might encounter the following error message: "ng: command not found".

Read More
FlutterBuilder
Coding
Jul 03, 2019

FlutterBuilder

Inside the Widget build(BuildContext context) function, you will notice that when rendering a Widget, we cannot directly use async / await or .then() from a Future and wait for the data to arrive before displaying it.

Read More
Code Smell Long Method Part 1
Coding
Jun 20, 2019

Code Smell Long Method Part 1

Long Method is one of the Code Smells in the Bloaters category. Its defining characteristic is having far too many lines of code. As a rule of thumb, any function or method exceeding 10 lines should be reviewed to see if it might cause tangled code in the future.

Read More
Code Smells & Code Refactoring
Coding
Jun 15, 2019

Code Smells & Code Refactoring

Has anyone ever written code that ended up tangled like the messy ball of yarn on the left? Have you ever wondered why it gets so tangled, even when we often think we've designed it perfectly? So, what do we need to do to make our code look as neat and beautiful as the spool of thread on the right? In the upcoming articles, I'll be gradually writing more about this for you all to read, haha! ^^y

Read More