gRPC とは
.proto
による IDL(Interface Description Language)からRPC用のソースを生成- REST と比べ API 仕様が規定しやすい
- Go、Java、Node、Python、Ruby、C#、 C++ など多くの言語に対応し、RPCは特定の言語に依存しない
- Protocol Buffers によるAPIコールで通信効率が良い
- HTTP/2による双方向ストリーミングに対応
など。マイクロサービスでの利用に吉。
プロジェクトの作成
Gradle でプロジェクトを作成します。
init
タスクでプロジェクトの雛形を作成しましょう。
$ mkdir example-grpc $ cd example-grpc $ gradle init --type java-application
build.gradle を以下のように変更します。
plugins { id 'java' id 'application' id 'com.google.protobuf' version '0.8.8' } repositories { jcenter() } mainClassName = 'App' sourceCompatibility = targetCompatibility = 1.8 dependencies { implementation 'io.grpc:grpc-netty-shaded:1.23.0' implementation 'io.grpc:grpc-protobuf:1.23.0' implementation 'io.grpc:grpc-stub:1.23.0' implementation 'javax.annotation:javax.annotation-api:1.3.2' } protobuf { protoc { artifact = "com.google.protobuf:protoc:3.9.0" } plugins { grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.23.0' } } generateProtoTasks { all()*.plugins { grpc {} } } }
Gradle プラグイン com.google.protobuf
を使います。
このプラグインにて、インターフェース定義ファイル .proto
からRPC用のコードが自動生成されます。
プラグインの設定は protobuf
ブロックにて行います。
Java9 以降の場合は javax.annotation-api
の依存が必要になります。
.proto ファイルの作成
RPC のインターフェースを定義します。
helloworld.proto
を作成します。
$ mkdir -p src/main/proto/ $ touch src/main/proto/helloworld.proto
helloworld.proto
を以下のように定義します。
syntax = "proto3"; option java_multiple_files = true; option java_package = "example.grpc.helloworld"; option java_outer_classname = "HelloWorldProto"; package helloworld; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }
Greeter というサービスで SayHello というインターフェースを定義しました。
generateProto
タスクで .proto
ファイルをコンパイルしてコード生成します。
$ ./gradlew generateProto
build/generated/source/proto
以下にコードが吐かれます。
IDE で生成されたコードをソースセットに加えるには build.gradle
に以下を追加します。
sourceSets { main { java { srcDirs "$buildDir/generated/source/proto/main/grpc" srcDirs "$buildDir/generated/source/proto/main/java" } } }
このようなコードが生成されました。
java_multiple_files
、option java_package
、java_outer_classname
のオプションを書かない場合は以下のようになります。
サーバコードの実装
GreeterImplBase
というクラスを継承して sayHello
メソッドを定義します。
package example; import example.grpc.helloworld.GreeterGrpc; import example.grpc.helloworld.HelloReply; import example.grpc.helloworld.HelloRequest; import io.grpc.stub.StreamObserver; public class GreeterImpl extends GreeterGrpc.GreeterImplBase { @Override public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) { HelloReply reply = HelloReply.newBuilder() .setMessage("Hello " + req.getName()).build(); responseObserver.onNext(reply); responseObserver.onCompleted(); } }
HelloRequest
というリクエストを取り、レスポンスは StreamObserver
経由でレスポンスを返却します。
サーバは ServerBuilder
を使って実装します。
import example.GreeterImpl; import io.grpc.Server; import io.grpc.ServerBuilder; public class App { public static void main(String[] args) throws Exception { Server server = ServerBuilder.forPort(50051) .addService(new GreeterImpl()) .build() .start(); Runtime.getRuntime().addShutdownHook(new Thread(server::shutdown)); server.awaitTermination(); } }
先程作成した GreeterImpl
をサービスとして登録します。
クライアントコードの実装
クライアントはテストコードから実行することにします。
JUnit の依存を build.gradle
に追加しておきます。
dependencies { // ... testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2' } test { useJUnitPlatform() testLogging.showStandardStreams = true }
クライアントコードは、こちらも生成された GreeterBlockingStub
を使い、以下のような実装になります。
import example.grpc.helloworld.GreeterGrpc; import example.grpc.helloworld.HelloReply; import example.grpc.helloworld.HelloRequest; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public class AppTest { @Test void testApp() { ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051) .usePlaintext() .build(); GreeterGrpc.GreeterBlockingStub blockingStub = GreeterGrpc.newBlockingStub(channel); HelloRequest request = HelloRequest.newBuilder().setName("Thom").build(); HelloReply response = blockingStub.sayHello(request); System.out.println(response.getMessage()); assertEquals(response.getMessage(), "Hello Thom"); } }
実行
application
プラグインを入れているので、サーバは以下で実行します。
$ ./gradlew run
サーバが起動したら、テストを実行します。
$ ./gradlew test AppTest > testApp() STANDARD_OUT Hello Thom
API の呼び出しができました。
最後に、今回使ったbuild.gradle
は以下のようになりました。
plugins { id 'java' id 'application' id 'com.google.protobuf' version '0.8.8' } repositories { jcenter() } mainClassName = 'App' sourceCompatibility = targetCompatibility = 1.8 dependencies { implementation 'io.grpc:grpc-netty-shaded:1.23.0' implementation 'io.grpc:grpc-protobuf:1.23.0' implementation 'io.grpc:grpc-stub:1.23.0' implementation 'javax.annotation:javax.annotation-api:1.3.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2' } test { useJUnitPlatform() testLogging.showStandardStreams = true } protobuf { protoc { artifact = "com.google.protobuf:protoc:3.9.0" } plugins { grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.23.0' } } generateProtoTasks { all()*.plugins { grpc {} } } } // for IDE sourceSets { main { java { srcDirs "$buildDir/generated/source/proto/main/grpc" srcDirs "$buildDir/generated/source/proto/main/java" } } }
- 作者:Magnus Larsson
- 出版社/メーカー: Packt Publishing
- 発売日: 2019/09/20
- メディア: ペーパーバック