Java で gRPC を実装する方法を見てみましょう。
gRPC (Google Remote Procedure Call): gRPC は、マイクロサービス間の高速通信を可能にするために Google によって開発されたオープンソース RPC アーキテクチャです。 gRPC を使用すると、開発者はさまざまな言語で記述されたサービスを統合できます。 gRPC は、構造化データをシリアル化するための高効率で高度にパックされたメッセージング形式である Protobuf メッセージング形式 (プロトコル バッファー) を使用します。
ユースケースによっては、gRPC API の方が REST API よりも効率的である場合があります。
gRPC 上にサーバーを書いてみましょう。まず、サービスとモデル (DTO) を記述するいくつかの
.proto
ファイルを作成する必要があります。単純なサーバーの場合は、ProfileService と ProfileDescriptor を使用します。
ProfileService は次のようになります。
syntax = "proto3";
package com.deft.grpc;
import "google/protobuf/empty.proto";
import "profile_descriptor.proto";
service ProfileService {
rpc GetCurrentProfile (google.protobuf.Empty) returns (ProfileDescriptor) {}
rpc clientStream (stream ProfileDescriptor) returns (google.protobuf.Empty) {}
rpc serverStream (google.protobuf.Empty) returns (stream ProfileDescriptor) {}
rpc biDirectionalStream (stream ProfileDescriptor) returns (stream ProfileDescriptor) {}
}
gRPC は、さまざまなクライアント/サーバー通信オプションをサポートしています。それらをすべて分解してみましょう。
- 通常のサーバー呼び出し – リクエスト/レスポンス。
- クライアントからサーバーへのストリーミング。
- サーバーからクライアントへのストリーミング。
- そしてもちろん、双方向ストリーム。
ProfileService サービスは、インポート セクションで指定された ProfileDescriptor を使用します。
syntax = "proto3";
package com.deft.grpc;
message ProfileDescriptor {
int64 profile_id = 1;
string name = 2;
}
- int64 は Java の Long です。プロフィールIDを所属させます。
- 文字列 – Java と同様、これは文字列変数です。
Gradle または Maven を使用してプロジェクトをビルドできます。私にとっては maven を使用する方が便利です。さらにMavenを使用したコードになります。 Gradle の場合、将来の世代の .proto は若干異なるため、ビルド ファイルを別の方法で構成する必要があるため、これは非常に重要です。単純な gRPC サーバーを作成するには、依存関係が 1 つだけ必要です。
<dependency>
<groupId>io.github.lognet</groupId>
<artifactId>grpc-spring-boot-starter</artifactId>
<version>4.5.4</version>
</dependency>
それは本当に信じられないことです。このスターターは私たちにとって非常に多くの仕事をしてくれます。
作成するプロジェクトは次のようになります。
Spring Boot アプリケーションを開始するには、GrpcServerApplication が必要です。 GrpcProfileService は、
.proto
サービスからのメソッドを実装します。 protoc を使用し、書かれた .proto ファイルからクラスを生成するには、protobuf-maven-plugin を pom.xml に追加します。ビルドセクションは次のようになります。
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
<outputDirectory>${basedir}/target/generated-sources/grpc-java</outputDirectory>
<protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.38.0:exe:${os.detected.classifier}</pluginArtifact>
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
- protoSourceRoot – .proto ファイルが配置されているディレクトリを指定します。
- OutputDirectory – ファイルが生成されるディレクトリを選択します。
- clearOutputDirectory – 生成されたファイルをクリアしないことを示すフラグ。
この段階で、プロジェクトを構築できます。次に、出力ディレクトリで指定したフォルダーに移動する必要があります。生成されたファイルはそこにあります。これで、 GrpcProfileService を 段階的に実装できるようになります。
クラス宣言は次のようになります。
@GRpcService
public class GrpcProfileService extends ProfileServiceGrpc.ProfileServiceImplBase
GRpcService アノテーション – クラスを grpc-service Bean としてマークします。
サービスを ProfileServiceGrpc 、 ProfileServiceImplBase から継承しているため、親クラスのメソッドをオーバーライドできます。オーバーライドする最初のメソッドは getCurrentProfile です。
@Override
public void getCurrentProfile(Empty request, StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> responseObserver) {
System.out.println("getCurrentProfile");
responseObserver.onNext(ProfileDescriptorOuterClass.ProfileDescriptor
.newBuilder()
.setProfileId(1)
.setName("test")
.build());
responseObserver.onCompleted();
}
クライアントに応答するには、渡された StreamObserver の onNext メソッドを呼び出す必要があります。応答を送信した後、サーバーが 完了 したことを示すシグナルをクライアントに送信します。 getCurrentProfile サーバーにリクエストを送信すると、応答は次のようになります。
{
"profile_id": "1",
"name": "test"
}
次に、サーバー ストリームを見てみましょう。このメッセージング手法では、クライアントがサーバーにリクエストを送信し、サーバーがメッセージのストリームでクライアントに応答します。たとえば、ループで 5 つのリクエストを送信します。送信が完了すると、サーバーはストリームが正常に完了したことを示すメッセージをクライアントに送信します。
オーバーライドされたサーバー ストリーム メソッドは次のようになります。
@Override
public void serverStream(Empty request, StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> responseObserver) {
for (int i = 0; i < 5; i++) {
responseObserver.onNext(ProfileDescriptorOuterClass.ProfileDescriptor
.newBuilder()
.setProfileId(i)
.build());
}
responseObserver.onCompleted();
}
したがって、クライアントは、応答番号と同じ ProfileId を持つ 5 つのメッセージを受信します。
{
"profile_id": "0",
"name": ""
}
{
"profile_id": "1",
"name": ""
}
…
{
"profile_id": "4",
"name": ""
}
クライアント ストリームはサーバー ストリームと非常によく似ています。ここでのみ、クライアントがメッセージのストリームを送信し、サーバーがそれらを処理します。サーバーはメッセージをすぐに処理することも、クライアントからのすべてのリクエストを待ってからメッセージを処理することもできます。
@Override
public StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> clientStream(StreamObserver<Empty> responseObserver) {
return new StreamObserver<>() {
@Override
public void onNext(ProfileDescriptorOuterClass.ProfileDescriptor profileDescriptor) {
log.info("ProfileDescriptor from client. Profile id: {}", profileDescriptor.getProfileId());
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
クライアント ストリームでは、サーバーがメッセージを受信するクライアントに StreamObserver を返す必要があります。ストリームでエラーが発生した場合、onError メソッドが呼び出されます。たとえば、異常終了した場合などです。
双方向ストリームを実装するには、サーバーとクライアントからのストリームの作成を組み合わせる必要があります。
@Override
public StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> biDirectionalStream(
StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> responseObserver) {
return new StreamObserver<>() {
int pointCount = 0;
@Override
public void onNext(ProfileDescriptorOuterClass.ProfileDescriptor profileDescriptor) {
log.info("biDirectionalStream, pointCount {}", pointCount);
responseObserver.onNext(ProfileDescriptorOuterClass.ProfileDescriptor
.newBuilder()
.setProfileId(pointCount++)
.build());
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
この例では、クライアントのメッセージに応答して、サーバーは pointCount を増加させたプロファイルを返します。
結論
gRPC を使用したクライアントとサーバー間のメッセージングの基本オプション (実装されたサーバー ストリーム、クライアント ストリーム、双方向ストリーム) について説明しました。
この記事はセルゲイ・ゴリツィンによって書かれました
