Implementing a gRPC Service
CDI Beanとして公開されたgRPCサービスの実装は、quarkus-grpcによって自動的に登録、提供されます。
Implementing a gRPC service requires the gRPC classes to be generated.
Place your proto files in src/main/proto and run mvn compile.
|
生成されたコード
Quarkusは、 proto ファイルで宣言されたサービスに対して、いくつかの実装クラスを生成します。
-
Mutiny APIを使用した サービスインターフェース
-
クラス名は
${JAVA_PACKAGE}.${NAME_OF_THE_SERVICE}
-
-
gRPC APIを利用した 実装のベース のクラス
-
クラス名は以下のように構成されています。
${JAVA_PACKAGE}.${NAME_OF_THE_SERVICE}Grpc.${NAME_OF_THE_SERVICE}ImplBase
-
例えば、以下の proto ファイルスニペットを使用した場合は、
option java_package = "hello"; (1)
service Greeter { (2)
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
| 1 | hello は、生成されたクラスのjavaパッケージです。 |
| 2 | Greeter はサービス名です。 |
サービス・インターフェースは hello.Greeter であり、実装ベースは hello.GreeterGrpc.GreeterImplBase という抽象的な静的ネストクラスです。
| You’ll need to implement the service interface or extend the base class with your service implementation bean as described in the following sections. |
Mutiny APIによるサービスの実装
Mutiny APIを使ってgRPCサービスを実装するには、サービスインターフェースを実装したクラスを作成します。そして、サービスインターフェースで定義されているメソッドを実装します。サービスメソッドを実装したくない場合は、メソッドボディから java.lang.UnsupportedOperationException を投げてください(例外は適切な gRPCの例外に自動的に変換されます)。最後に、サービスを実装し、 @GrpcService アノテーションを追加します。
import io.quarkus.grpc.GrpcService;
import hello.Greeter;
@GrpcService (1)
public class HelloService implements Greeter { (2)
@Override
public Uni<HelloReply> sayHello(HelloRequest request) {
return Uni.createFrom().item(() ->
HelloReply.newBuilder().setMessage("Hello " + request.getName()).build()
);
}
}
| 1 | gRPCサービス実装Beanは、 @GrpcService アノテーションを付加されなければならず、他のCDI修飾子を宣言してはいけません。すべてのgRPCサービスは、 javax.inject.Singleton スコープを持ちます。また、サービスコール中は常にリクエストコンテキストが有効になります。 |
| 2 | hello.Greeter は、生成されたサービスインターフェースです。 |
The service implementation bean can also extend the Mutiny implementation base, where the class name is structured as follows: Mutiny${NAME_OF_THE_SERVICE}Grpc.${NAME_OF_THE_SERVICE}ImplBase.
|
デフォルトのgRPC APIでのサービス実装
デフォルトのgRPC APIを使ってgRPCサービスを実装するには、デフォルトの実装ベースを拡張したクラスを作成します。次に、サービス・インターフェースで定義されているメソッドを上書きします。最後に、サービスを実装し、 @GrpcService アノテーションを追加します。
import io.quarkus.grpc.GrpcService;
@GrpcService
public class HelloService extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
String name = request.getName();
String message = "Hello " + name;
responseObserver.onNext(HelloReply.newBuilder().setMessage(message).build());
responseObserver.onCompleted();
}
}
ブロッキングサービスの実装
デフォルトでは、gRPCサービスからのすべてのメソッドはイベントループ上で実行されます。そのため、ブロックしては いけません 。サービスロジックをブロックする必要がある場合は、メソッドに io.smallrye.common.annotation.Blocking アノテーションを追加します。
@Override
@Blocking
public Uni<HelloReply> sayHelloBlocking(HelloRequest request) {
// Do something blocking before returning the Uni
}
ストリームの処理
gRPCでは、ストリームを受信して返すことができます。
service Streaming {
rpc Source(Empty) returns (stream Item) {} // Returns a stream
rpc Sink(stream Item) returns (Empty) {} // Reads a stream
rpc Pipe(stream Item) returns (stream Item) {} // Reads a streams and return a streams
}
Mutinyを使うと、以下のように実装できます。
import io.quarkus.grpc.GrpcService;
@GrpcService
public class StreamingService implements Streaming {
@Override
public Multi<Item> source(Empty request) {
// Just returns a stream emitting an item every 2ms and stopping after 10 items.
return Multi.createFrom().ticks().every(Duration.ofMillis(2))
.select().first(10)
.map(l -> Item.newBuilder().setValue(Long.toString(l)).build());
}
@Override
public Uni<Empty> sink(Multi<Item> request) {
// Reads the incoming streams, consume all the items.
return request
.map(Item::getValue)
.map(Long::parseLong)
.collect().last()
.map(l -> Empty.newBuilder().build());
}
@Override
public Multi<Item> pipe(Multi<Item> request) {
// Reads the incoming stream, compute a sum and return the cumulative results
// in the outbound stream.
return request
.map(Item::getValue)
.map(Long::parseLong)
.onItem().scan(() -> 0L, Long::sum)
.onItem().transform(l -> Item.newBuilder().setValue(Long.toString(l)).build());
}
}
ヘルスチェック
実装されたサービスでは、Quarkus gRPCは以下の形式でヘルスチェックを公開しています。
syntax = "proto3";
package grpc.health.v1;
message HealthCheckRequest {
string service = 1;
}
message HealthCheckResponse {
enum ServingStatus {
UNKNOWN = 0;
SERVING = 1;
NOT_SERVING = 2;
}
ServingStatus status = 1;
}
service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}
クライアントは、特定のサービスのヘルス状態を取得するために完全修飾されたサービス名を指定したり、gRPCサーバーの一般的な状態を取得するためにサービス名の指定を省略することができます。
詳細については、 gRPCのドキュメント を確認してください。
さらに、Quarkus SmallRye Healthがアプリケーションに追加された場合、gRPCサービスの状態に関するレディネスチェックがMicroProfile Healthエンドポイントレスポンスに追加されます( /q/health )。
リフレクションサービス
Quarkus gRPC Serverは、 リフレクションサービス を実装しています。このサービスを使用すると、 grpcurl や grpcox などのツールがサービスと対話できるようになります。
リフレクションサービスは、 開発 モードではデフォルトで有効になっています。テストモードやプロダクションモードでは、 quarkus.grpc.server.enable-reflection-service を true に設定して明示的に有効にする必要があります。
スケーリング
デフォルトでは、quarkus-grpcは単一のイベントループ上で動作する単一のgRPCサーバーを起動します。
サーバーをスケールさせたい場合は、 quarkus.grpc.server.instances を設定することで、サーバーのインスタンス数をセットできます。
サーバー構成
ビルド時に固定される設定プロパティ - それ以外の設定プロパティは実行時に上書き可能
タイプ |
デフォルト |
|
|---|---|---|
Do we use separate HTTP server to serve gRPC requests. Set this to false if you want to use new Vert.x gRPC support, which uses existing Vert.x HTTP server. Environment variable: Show more |
boolean |
|
Explicitly enable use of XDS. Environment variable: Show more |
boolean |
|
Use secure credentials. Environment variable: Show more |
boolean |
|
The gRPC Server port. Environment variable: Show more |
int |
|
The gRPC Server port used for tests. Environment variable: Show more |
int |
|
The gRPC server host. Environment variable: Show more |
string |
|
The gRPC handshake timeout. Environment variable: Show more |
||
The max inbound message size in bytes. Environment variable: Show more |
int |
|
The max inbound metadata size in bytes Environment variable: Show more |
int |
|
The classpath path or file path to a server certificate or certificate chain in PEM format. Environment variable: Show more |
path |
|
The classpath path or file path to the corresponding certificate private key file in PEM format. Environment variable: Show more |
path |
|
An optional key store which holds the certificate information instead of specifying separate files. The key store can be either on classpath or an external file. Environment variable: Show more |
path |
|
An optional parameter to specify the type of the key store file. If not given, the type is automatically detected based on the file name. Environment variable: Show more |
string |
|
A parameter to specify the password of the key store file. If not given, the default ("password") is used. Environment variable: Show more |
string |
|
An optional trust store which holds the certificate information of the certificates to trust The trust store can be either on classpath or an external file. Environment variable: Show more |
path |
|
An optional parameter to specify type of the trust store file. If not given, the type is automatically detected based on the file name. Environment variable: Show more |
string |
|
A parameter to specify the password of the trust store file. Environment variable: Show more |
string |
|
The cipher suites to use. If none is given, a reasonable default is selected. Environment variable: Show more |
文字列のリスト |
|
Sets the ordered list of enabled SSL/TLS protocols.
If not set, it defaults to Environment variable: Show more |
文字列のリスト |
|
Configures the engine to require/request client authentication. NONE, REQUEST, REQUIRED Environment variable: Show more |
|
|
Disables SSL, and uses plain text instead. If disabled, configure the ssl configuration. Environment variable: Show more |
boolean |
|
Whether ALPN should be used. Environment variable: Show more |
boolean |
|
The path to the certificate file. Environment variable: Show more |
string |
|
The path to the private key file. Environment variable: Show more |
string |
|
Enables the gRPC Reflection Service. By default, the reflection service is only exposed in Environment variable: Show more |
boolean |
|
Number of gRPC server verticle instances. This is useful for scaling easily across multiple cores. The number should not exceed the amount of event loops. Environment variable: Show more |
int |
|
Sets a custom keep-alive duration. This configures the time before sending a Environment variable: Show more |
||
gRPC compression, e.g. "gzip" Environment variable: Show more |
string |
|
期間フォーマットについて
期間のフォーマットは標準の 数値で始まる期間の値を指定することもできます。この場合、値が数値のみで構成されている場合、コンバーターは値を秒として扱います。そうでない場合は、 |
When you disable quarkus.grpc.server.use-separate-server, you are then using the new Vert.x gRPC server implementation
which uses the existing HTTP server. Which means that the server port is now 8080 (or the port configured with quarkus.http.port).
Also, most of the other configuration properties are no longer applied, since it’s the HTTP server that should already be properly configured.
|
When you enable quarkus.grpc.server.xds.enabled, it’s the xDS that should handle most of the configuration above.
|
構成の例
サーバーインターセプター
gRPCサーバーのインターセプターでは、サービスが呼び出される前に、認証などのロジックを実行することができるようになります。
io.grpc.ServerInterceptor を実装した @ApplicationScoped Beanを作成することで、gRPC サーバーのインターセプターを実装できます。
@ApplicationScoped
// add @GlobalInterceptor for interceptors meant to be invoked for every service
public class MyInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall,
Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {
// ...
}
}
| Check the ServerInterceptor JavaDoc to properly implement your interceptor. |
公開されているすべてのサービスにインターセプターを適用するには、 @io.quarkus.grpc.GlobalInterceptor でアノテーションを付けます。インターセプターを単一のサービスに適用するには、 @io.quarkus.grpc.RegisterInterceptor でそのサービスに登録します。
import io.quarkus.grpc.GrpcService;
import io.quarkus.grpc.RegisterInterceptor;
@GrpcService
@RegisterInterceptor(MyInterceptor.class)
public class StreamingService implements Streaming {
// ...
}
複数のサーバーインターセプターがある場合、 javax.enterprise.inject.spi.Prioritized インターフェースを実装することで、それらを順番に並べることができます。すべてのグローバルインターセプターは、サービス固有のインターセプターの前に起動されることに注意してください。
@ApplicationScoped
public class MyInterceptor implements ServerInterceptor, Prioritized {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall,
Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {
// ...
}
@Override
public int getPriority() {
return 10;
}
}
最高の優先度を持つインターセプターが最初に呼び出されます。インターセプターが Prioritized インターフェイスを実装していない場合に使用されるデフォルトの優先度は 0 です。
サービスのテスト
gRPCサービスをテストする最も簡単な方法は、 gRPCサービスの使用 で説明したように、gRPCクライアントを使用することです。
なお、TLS を使用していない公開サービスのテストにクライアントを使用する場合は、設定を行う必要はありませんのでご注意ください。例えば、上記で定義した HelloService をテストするには、次のようなテストを作成します。
public class HelloServiceTest implements Greeter {
@GrpcClient
Greeter client;
@Test
void shouldReturnHello() {
CompletableFuture<String> message = new CompletableFuture<>();
client.sayHello(HelloRequest.newBuilder().setName("Quarkus").build())
.subscribe().with(reply -> message.complete(reply.getMessage()));
assertThat(message.get(5, TimeUnit.SECONDS)).isEqualTo("Hello Quarkus");
}
}
手動でサービスを試す
Devモードでは、Quarkus Dev UIでgRPCサービスを試すことができます。 http://localhost:8080/q/dev にアクセスし、gRPCタイルの下にある Services をクリックするだけです。
Dev UIにアクセスするには、アプリケーションが ”通常の” HTTPポートを公開する必要があることに注意してください。アプリケーションがHTTPエンドポイントを公開していない場合は、 quarkus-vertx-http に依存する専用のプロファイルを作成することができます。
<profiles>
<profile>
<id>development</id>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http</artifactId>
</dependency>
</dependencies>
</profiles>
</profile>
これがあれば、次のようにしてDevモードを実行できます: mvn quarkus:dev -Pdevelopment
Gradleを使用している場合は、 quarkusDev タスクの依存関係を追加するだけです。
dependencies {
quarkusDev 'io.quarkus:quarkus-vertx-http'
}
gRPCサーバーのメトリクス
メトリクス収集の有効化
アプリケーションが quarkus-micrometer エクステンションを使用している場合、gRPCサーバーメトリクスが自動的に有効になります。Micrometerは、アプリケーションが実装するすべてのgRPCサービスのメトリクスを収集します。
例えば、メトリクスをPrometheusにエクスポートすると、以下のように取得できます。
# HELP grpc_server_responses_sent_messages_total The total number of responses sent
# TYPE grpc_server_responses_sent_messages_total counter
grpc_server_responses_sent_messages_total{method="SayHello",methodType="UNARY",service="helloworld.Greeter",} 6.0
# HELP grpc_server_processing_duration_seconds The total time taken for the server to complete the call
# TYPE grpc_server_processing_duration_seconds summary
grpc_server_processing_duration_seconds_count{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 6.0
grpc_server_processing_duration_seconds_sum{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 0.016216771
# HELP grpc_server_processing_duration_seconds_max The total time taken for the server to complete the call
# TYPE grpc_server_processing_duration_seconds_max gauge
grpc_server_processing_duration_seconds_max{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 0.007985236
# HELP grpc_server_requests_received_messages_total The total number of requests received
# TYPE grpc_server_requests_received_messages_total counter
grpc_server_requests_received_messages_total{method="SayHello",methodType="UNARY",service="helloworld.Greeter",} 6.0
サービス名、メソッド、タイプは tags で確認できます。