Spring BootでgRPCを試してみる

gRPCとRESTの比較をしてみたかったので、まずはgRPCを触ってみました。

Srping Bootだと、grpc-spring-boot-starterを使うと簡単にgRPCのサーバが実装できます。すばらしい。

build.gradle

build.gradle は下記のような感じです。Spring BootのStarterで作成したものに、gRPCに必要なものを付け加えています。 参考にしたのは、grpc-spring-boot-starter のサンプルです。

buildscript {
    ext { springBootVersion = '2.1.2.RELEASE' }
    repositories { mavenCentral() }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath('com.google.protobuf:protobuf-gradle-plugin:0.8.8')
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'com.google.protobuf'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories { mavenCentral() }

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'io.github.lognet:grpc-spring-boot-starter:3.1.0'
    compileOnly 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.assertj:assertj-core:3.11.1'
}

protobuf {
    protoc { artifact = 'com.google.protobuf:protoc:3.5.1' }
    plugins {
        grpc { artifact = "io.grpc:protoc-gen-grpc-java:1.18.0" }
    }

    generateProtoTasks {
        ofSourceSet('main').each { task ->
            task.builtins {
                java{ outputSubDir = 'protogen' }
            }
            task.plugins {
                grpc { outputSubDir = 'protogen' }
            }
        }
    }
    generatedFilesBaseDir = "$projectDir/src/"
}

sourceSets {
    main {
        java { srcDir 'src/main/protogen' }
    }
}

task cleanProtoGen{
    doFirst{
           delete("$projectDir/src/main/protogen")
    }
}

clean.dependsOn cleanProtoGen

proto

proto の定義は、とりあえずgRPCの公式サイトにあるサンプルと同じで。

syntax = "proto3";

option java_package = "com.example.grpc";

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

Gradle の generateProto タスクを実行すると、上記定義を元にJavaのコードが生成されます。

@GRpcService の実装

あとは@GRpcServiceを付与したサービスクラスを実装するだけです。

package com.example.grpc;

import org.lognet.springboot.grpc.GRpcService;

import com.example.grpc.GreeterOuterClass.HelloReply;
import com.example.grpc.GreeterOuterClass.HelloRequest;

import io.grpc.stub.StreamObserver;

@GRpcService
public class GreeterService extends GreeterGrpc.GreeterImplBase {

    @Override
    public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {

        HelloReply reply = HelloReply.newBuilder()
                .setMessage("Hello " + request.getName())
                .build();
        responseObserver.onNext(reply);
        responseObserver.onCompleted();
    }
}

クライアントのコード

クライアントのコードは下記のような感じで。(Spring Bootのテストコードとして書いてます)

package com.example.grpc;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.lognet.springboot.grpc.context.LocalRunningGrpcPort;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.example.grpc.GreeterOuterClass.HelloReply;
import com.example.grpc.GreeterOuterClass.HelloRequest;

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

@RunWith(SpringRunner.class)
@SpringBootTest
public class GreeterServiceTest {

    @LocalRunningGrpcPort
    private int runningPort;

    @Test
    public void sayHello() {

        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", runningPort)
                .usePlaintext()
                .build();

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

        String name = "Taro";

        HelloRequest request = HelloRequest.newBuilder()
                .setName(name)
                .build();

        HelloReply reply = stub.sayHello(request);

        assertThat(reply.getMessage()).isEqualTo("Hello " + name);
    }
}

おわりに

ちょっと試してみる分には、すごく簡単に実行できました。

コード全体は、下記のプロジェクトになります。

Channelは使いまわしできるのかとか、並列で呼び出す場合にはどのように使うのか、、など、まだまだわからないところがあるので、今後もう少し調べてみようと思います。

これ読むといいよ!!とかありましたら、ぜひ教えてください。