Spring Boot 4.1でgRPC入門:サーバー・クライアント実装をやさしく解説
Spring Boot 4.1では、gRPCサーバー・クライアントを作りやすくするための公式サポートが追加されました
これまでSpring BootでgRPCを使う場合、外部のgRPC用Starterを追加したり、NettyサーバーやStub生成の設定を自分で組み立てたりする必要がありました。
Spring Boot 4.1では、spring-boot-starter-grpc-server や spring-boot-starter-grpc-client が用意され、Spring Bootらしい自動構成でgRPCを扱いやすくなっています。
この記事では、Spring Boot 4.1を使って、シンプルなgRPCサーバーとクライアントを作る流れを紹介します。
対象読者は、次のような人です。
・Spring BootでgRPCを試してみたい人
・REST API以外の通信方式を学びたい人
・Spring Boot 4.1の新機能を追いたい人
・gRPCサーバーとクライアントの最小構成を知りたい人
・マイクロサービス間通信にgRPCを使うか検討している人
gRPCとは?REST APIと何が違うのか
gRPCは、Googleが中心となって開発したRPCフレームワークです。
REST APIでは、URLとHTTPメソッドを使い、JSONでデータをやり取りすることが多いです。
一方、gRPCでは .proto ファイルにサービス定義とメッセージ定義を書き、その定義からJavaコードを自動生成します。通信にはProtocol Buffersが使われます。
イメージとしては、次のような違いです。
REST API:
GET /users/1
JSONでレスポンスを受け取る
gRPC:
UserService.GetUser(UserRequest)
Protocol Buffersでレスポンスを受け取る
REST APIはブラウザやcurlで確認しやすく、外部公開APIに向いています。
一方でgRPCは、型安全で通信効率がよく、サービス間通信に向いています。特にマイクロサービス構成では、内部APIとしてgRPCを使う選択肢があります。
ざっくり使い分けるなら、以下のようなイメージです。
外部公開API:
REST API
社内・サービス間通信:
gRPC
大量データやストリーミング:
gRPC
ブラウザやフロントエンドから扱いやすいAPI:
REST API
この記事では、まず一番シンプルな「名前を送ると挨拶を返す」gRPCアプリを作ります。
gRPC Client
↓
HelloService.SayHello
↓
gRPC Server
リクエストで "Spring" を送ると、レスポンスとして "Hello, Spring!" を返す構成です。
プロジェクト構成とGradle設定
まず、プロジェクト構成は以下のようなイメージです。
src
├─ main
│ ├─ java
│ │ └─ com.example.demo
│ │ ├─ GrpcSampleApplication.java
│ │ ├─ HelloGrpcService.java
│ │ └─ HelloClientRunner.java
│ ├─ proto
│ │ └─ hello.proto
│ └─ resources
│ └─ application.yml
└─ test
└─ java
└─ com.example.demo
└─ HelloGrpcServiceTest.java
ポイントは、src/main/proto に .proto ファイルを置くことです。
.proto ファイルから HelloServiceGrpc、HelloRequest、HelloReply などのJavaクラスが生成されます。サーバー側もクライアント側も、この生成コードを使って実装します。
Gradleでは、Spring BootのgRPCサーバー・クライアント用Starterと、Protobufコード生成用のプラグインを追加します。
plugins {
id 'java'
id 'org.springframework.boot' version '4.1.0'
id 'io.spring.dependency-management' version '1.1.7'
id 'com.google.protobuf' version '0.10.0'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-grpc-server'
implementation 'org.springframework.boot:spring-boot-starter-grpc-client'
implementation 'io.grpc:grpc-stub'
implementation 'io.grpc:grpc-protobuf'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.boot:spring-boot-starter-grpc-server-test'
testImplementation 'org.springframework.boot:spring-boot-starter-grpc-client-test'
}
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc'
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java'
}
}
generateProtoTasks {
all()*.plugins {
grpc {}
}
}
}
ここで重要なのは、次の3つです。
・spring-boot-starter-grpc-server を追加する
・spring-boot-starter-grpc-client を追加する
・.proto からJavaコードを生成するために protobuf プラグインを使う
REST APIだけのSpring Bootアプリと比べると、.proto からコード生成する分、少し設定が増えます。
ただし、一度仕組みを作ってしまえば、API定義を .proto に集約できるので、サーバーとクライアントの型ずれを防ぎやすくなります。
proto定義とgRPCサーバー実装
次に、src/main/proto/hello.proto を作成します。
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.example.demo.grpc";
option java_outer_classname = "HelloProto";
service HelloService {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
この .proto では、HelloService というサービスを定義しています。
SayHello は、HelloRequest を受け取り、HelloReply を返すRPCメソッドです。
Javaっぽく見ると、次のようなイメージです。
HelloReply sayHello(HelloRequest request);
この .proto を元に、Gradleのビルド時にJavaコードが生成されます。
次に、サーバー側の処理を実装します。
package com.example.demo;
import com.example.demo.grpc.HelloReply;
import com.example.demo.grpc.HelloRequest;
import com.example.demo.grpc.HelloServiceGrpc;
import io.grpc.stub.StreamObserver;
import org.springframework.grpc.server.service.GrpcService;
@GrpcService
public class HelloGrpcService extends HelloServiceGrpc.HelloServiceImplBase {
@Override
public void sayHello(
HelloRequest request,
StreamObserver<HelloReply> responseObserver
) {
String name = request.getName();
HelloReply reply = HelloReply.newBuilder()
.setMessage("Hello, " + name + "!")
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
@GrpcService を付けることで、Spring BootがこのクラスをgRPCサービスとして検出します。
REST APIでいう @RestController に近い役割だと考えるとわかりやすいです。
REST API:
@RestController
gRPC:
@GrpcService
ただし、REST APIのようにURLを直接定義するのではなく、.proto に定義したサービス名とメソッド名で呼び出されます。
レスポンスは StreamObserver に渡します。
responseObserver.onNext(reply);
responseObserver.onCompleted();
最初は少し見慣れないですが、「レスポンスを1件返して完了する」と考えれば大丈夫です。
application.ymlとクライアント実装
次に、gRPCサーバーとクライアントの設定を application.yml に書きます。
spring:
application:
name: spring-boot-grpc-sample
grpc:
server:
port: 9090
client:
channel:
hello:
target: static://localhost:9090
negotiation-type: plaintext
NettyベースのgRPCサーバーでは、通常のWeb APIで使う server.port とは別に、spring.grpc.server.port を使います。
spring:
grpc:
server:
port: 9090
つまり、REST APIが 8080、gRPCが 9090 というように、別ポートで待ち受ける構成にできます。
クライアント側では、hello というチャネル名に対して接続先を指定しています。
spring:
grpc:
client:
channel:
hello:
target: static://localhost:9090
negotiation-type: plaintext
ローカル開発では plaintext で問題ありません。
本番環境ではTLSや認証を検討する必要があります。
次に、アプリケーションクラスです。
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.grpc.client.ImportGrpcClients;
@SpringBootApplication
@ImportGrpcClients(
target = "hello",
types = com.example.demo.grpc.HelloServiceGrpc.HelloServiceBlockingStub.class
)
public class GrpcSampleApplication {
public static void main(String[] args) {
SpringApplication.run(GrpcSampleApplication.class, args);
}
}
@ImportGrpcClients を使うことで、生成されたgRPCクライアントStubをSpring Beanとして扱えるようになります。
今回は target = "hello" を指定しています。
この hello は、先ほど application.yml に書いたチャネル名と対応します。
spring:
grpc:
client:
channel:
hello:
target: static://localhost:9090
次に、起動時にgRPCサーバーを呼び出すクライアント処理を作ります。
package com.example.demo;
import com.example.demo.grpc.HelloReply;
import com.example.demo.grpc.HelloRequest;
import com.example.demo.grpc.HelloServiceGrpc;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class HelloClientRunner implements ApplicationRunner {
private final HelloServiceGrpc.HelloServiceBlockingStub helloStub;
public HelloClientRunner(HelloServiceGrpc.HelloServiceBlockingStub helloStub) {
this.helloStub = helloStub;
}
@Override
public void run(ApplicationArguments args) {
HelloRequest request = HelloRequest.newBuilder()
.setName("Spring Boot 4.1")
.build();
HelloReply reply = helloStub.sayHello(request);
System.out.println("gRPC response: " + reply.getMessage());
}
}
HelloServiceBlockingStub をコンストラクタで受け取っています。
public HelloClientRunner(HelloServiceGrpc.HelloServiceBlockingStub helloStub) {
this.helloStub = helloStub;
}
Spring BootがStubをBeanとして登録してくれるため、通常のServiceやRepositoryと同じようにDIできます。
アプリケーションを起動します。
./gradlew bootRun
Windowsの場合は以下です。
gradlew.bat bootRun
起動に成功すると、コンソールに次のようなログが出ます。
gRPC response: Hello, Spring Boot 4.1!
これで、Spring Bootアプリの中でgRPCサーバーとクライアントが動作しました。
grpcurlで外部から呼び出す
gRPCの動作確認には grpcurl が便利です。
REST APIでいうcurlのgRPC版のようなものです。
grpcurl -plaintext \
-d '{"name":"Spring"}' \
localhost:9090 \
HelloService/SayHello
レスポンス例です。
{
"message": "Hello, Spring!"
}
もしサービス名が見つからない場合は、まずサービス一覧を確認します。
grpcurl -plaintext localhost:9090 list
.proto に package を指定している場合、呼び出し名が変わることがあります。
その場合は、grpcurl list で表示された名前を使って呼び出しましょう。
テストを書く
Spring Boot 4.1では、gRPCアプリケーションのテストもしやすくなっています。
@AutoConfigureTestGrpcTransport を使うと、通常のネットワークポートを使わず、テスト専用のin-processチャネルでgRPC通信をテストできます。
package com.example.demo;
import com.example.demo.grpc.HelloReply;
import com.example.demo.grpc.HelloRequest;
import com.example.demo.grpc.HelloServiceGrpc;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.grpc.test.autoconfigure.AutoConfigureTestGrpcTransport;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.grpc.client.ImportGrpcClients;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
@AutoConfigureTestGrpcTransport
@ImportGrpcClients(types = HelloServiceGrpc.HelloServiceBlockingStub.class)
class HelloGrpcServiceTest {
@Autowired
private HelloServiceGrpc.HelloServiceBlockingStub helloStub;
@Test
void sayHelloで挨拶メッセージを返す() {
HelloRequest request = HelloRequest.newBuilder()
.setName("Test")
.build();
HelloReply reply = helloStub.sayHello(request);
assertThat(reply.getMessage()).isEqualTo("Hello, Test!");
}
}
このテストでは、実際に 9090 ポートを開かずにgRPCサービスを検証できます。
REST ControllerのテストでMockMvcを使うように、gRPCでもSpring Bootのテストサポートに乗せて確認できるのが便利です。
よくあるエラーと確認ポイント
生成コードが見つからない
以下のようなエラーが出ることがあります。
cannot find symbol
HelloServiceGrpc
HelloRequest
HelloReply
これは、.proto からJavaコードが生成されていないときによく起きます。
確認ポイントは以下です。
・src/main/proto 配下に .proto を置いているか
・protobuf Gradleプラグインを設定しているか
・./gradlew generateProto を実行したか
・IDEが generated source を認識しているか
IntelliJ IDEAを使っている場合、生成されたソースディレクトリが自動で認識されないことがあります。
その場合は、Generated Sources Rootとして認識されているか確認してください。
9090ポートが使われている
以下のようなエラーが出た場合です。
Address already in use
gRPCサーバーのポートが他のプロセスと競合しています。
application.yml でポートを変更します。
spring:
grpc:
server:
port: 19090
client:
channel:
hello:
target: static://localhost:19090
negotiation-type: plaintext
サーバー側だけでなく、クライアント側の接続先も合わせて変更してください。
クライアントが接続できない
以下のようなエラーが出ることがあります。
UNAVAILABLE: io exception
この場合は、次を確認します。
・gRPCサーバーが起動しているか
・spring.grpc.server.port が正しいか
・client.channel の target が正しいか
・plaintext / TLS の設定が一致しているか
・localhost と 0.0.0.0 の使い分けが正しいか
ローカル検証では、まず以下のようにシンプルにして確認するとよいです。
spring:
grpc:
client:
channel:
hello:
target: static://localhost:9090
negotiation-type: plaintext
REST APIのポートとgRPCのポートを混同する
Spring BootのWeb APIで使う server.port と、gRPCサーバーで使う spring.grpc.server.port は別です。
server:
port: 8080
spring:
grpc:
server:
port: 9090
この場合、REST APIは 8080、gRPCは 9090 で待ち受けます。
RESTとgRPCを同じアプリで扱う場合、ここを混同しやすいので注意しましょう。
導入前に確認したいこと
Spring BootでgRPCを導入する前に、次の点を確認しておくとスムーズです。
・Spring Boot 4.1系を使っているか
・gRPCを外部公開APIに使うのか、内部通信に使うのか
・.proto ファイルの管理方針を決めているか
・サーバー側とクライアント側の生成コードをどう共有するか
・ローカルではplaintext、本番ではTLSを使う方針か
・認証・認可をどうするか
・REST APIとの使い分けをチームで合意しているか
・テストで @AutoConfigureTestGrpcTransport を使うか
gRPCは便利ですが、すべてをgRPCに置き換える必要はありません。
外部公開APIはREST、内部サービス間通信はgRPC、という使い分けから始めるのが現実的です。
まとめ
Spring Boot 4.1では、gRPCサーバー・クライアントを作るための公式サポートが追加され、Spring Bootらしい自動構成でgRPCを扱いやすくなりました。
この記事では、hello.proto を定義し、@GrpcService でサーバーを実装し、@ImportGrpcClients でクライアントStubを注入する流れを紹介しました。
重要なポイントは以下です。
・gRPCのAPI定義は .proto ファイルに書く
・Gradleでは protobuf プラグインでJavaコードを生成する
・サーバー側は @GrpcService を付けて実装する
・クライアント側は @ImportGrpcClients でStubをBeanとして取り込む
・Nettyベースでは gRPC は通常 9090 ポートで待ち受ける
・REST APIの server.port とは別に考える
・テストでは @AutoConfigureTestGrpcTransport が便利
REST APIは今後も外部公開APIとして重要です。
一方で、サービス間通信や低レイテンシな内部APIでは、gRPCが有力な選択肢になります。
まずはこの記事のようなシンプルな HelloService から試し、次に認証、ヘルスチェック、ストリーミング、Observabilityへ広げていくのがおすすめです。
是非フォローしてください
最新の情報をお伝えします
