gRPCサービスの実装
CDI Beanとして公開されたgRPCサービスの実装は、quarkus-grpcによって自動的に登録、提供されます。
gRPC サービスを実装するには、gRPC クラスを生成する必要があります。 proto ファイルを src/main/proto に置き、 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
という抽象的な静的ネストクラスです。
以下のセクションで説明するように、 サービスインターフェース を実装するか、 ベースクラス をサービス実装Beanで拡張する必要があります。 |
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サービスは、 jakarta.inject.Singleton のスコープを持ちます。さらに、サービス呼び出しの間、リクエストコンテキストが有効になります。 |
2 | hello.Greeter は、生成されたサービスインターフェースです。 |
サービス実装Beanは、Mutinyの実装ベースを拡張することもでき、その場合、クラス名は以下のように構成されます。 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は、リフレクションサービス v1 と v1alpha の両方を公開しています。
|
スケーリング
デフォルトでは、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 |
|
Explicitly enable use of in-process. Environment variable: Show more |
boolean |
|
Set in-process name. Environment variable: Show more |
string |
|
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 |
list of string |
|
Sets the ordered list of enabled SSL/TLS protocols. If not set, it defaults to Note that setting an empty list, and enabling SSL/TLS is invalid. You must at least have one protocol. Environment variable: Show more |
list of string |
|
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 |
||
Sets a custom permit-keep-alive duration. This configures the most aggressive keep-alive time clients are permitted to configure. The server will try to detect clients exceeding this rate and when detected will forcefully close the connection. Environment variable: Show more |
||
Sets whether to allow clients to send keep-alive HTTP/2 PINGs even if there are no outstanding RPCs on the connection. Environment variable: Show more |
boolean |
|
gRPC compression, e.g. "gzip" Environment variable: Show more |
string |
期間フォーマットについて
To write duration values, use the standard You can also use a simplified format, starting with a number:
In other cases, the simplified format is translated to the
|
quarkus.grpc.server.use-separate-server を無効にすると、既存の HTTP サーバーを使用する新しい Vert.x gRPC サーバー実装が使用されます。つまり、サーバーのポートが 8080 (または quarkus.http.port で設定されたポート) になることを意味します。また、すでに適切に設定されているはずの HTTP サーバーであるため、他のほとんどの設定プロパティは適用されなくなります。
|
quarkus.grpc.server.xds.enabled を有効にすると、上記の設定のほとんどを処理するのはxDSになります。
|
構成の例
サーバーインターセプター
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) {
// ...
}
}
It’s also possible to annotate a producer method as a global interceptor:
import io.quarkus.grpc.GlobalInterceptor;
import jakarta.enterprise.inject.Produces;
public class MyProducer {
@GlobalInterceptor
@Produces
public MyInterceptor myInterceptor() {
return new MyInterceptor();
}
}
ServerInterceptor JavaDoc をチェックして、インターセプターを適切に実装してください。 |
公開されているすべてのサービスにインターセプターを適用するには、 @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 {
// ...
}
複数のサーバーインターセプターがある場合、 jakarta.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");
}
}
手動でサービスを試す
開発モードでは、Quarkus Dev UIでgRPCサービスを試すことができます。 http://localhost:8080/q/dev-v1 に遷移し、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 で確認できます。
メトリクス収集の無効化
quarkus-micrometer
を使用しているときに gRPC サーバーメトリクスを無効にするには、アプリケーションの設定に以下のプロパティを追加します。
quarkus.micrometer.binder.grpc-server.enabled=false
Use virtual threads
To use virtual threads in your gRPC service implementation, check the dedicated guide.