Consuming a gRPC service
gRPCクライアントは、アプリケーションコードにインジェクトすることができます。
Consuming gRPC services requires the gRPC classes to be generated.
Place your proto files in src/main/proto and run mvn compile.
|
スタブとインジェクション
gRPCの生成には、いくつかのスタブが用意されており、gRPCサービスを使用するためのさまざまな方法を提供しています。下記をインジェクトすることができます。
-
Mutiny APIを使用したサービスインターフェース
-
gRPC APIを使用したブロッキングスタブ
-
Mutinyをベースにしたリアクティブ・スタブ
-
他のタイプのスタブを作成することができるgRPC
io.grpc.Channel
import io.quarkus.grpc.GrpcClient;
import hello.Greeter;
import hello.GreeterGrpc.GreeterBlockingStub;
import hello.MutinyGreeterGrpc.MutinyGreeterStub;
class MyBean {
// A service interface using the Mutiny API
@GrpcClient("helloService") (1)
Greeter greeter;
// A reactive stub based on Mutiny
@GrpcClient("helloService")
MutinyGreeterGrpc.MutinyGreeterStub mutiny;
// A blocking stub using the gRPC API
@GrpcClient
GreeterGrpc.GreeterBlockingStub helloService; (2)
@GrpcClient("hello-service")
Channel channel;
}
| 1 | gRPCクライアントのインジェクションポイントには、 @GrpcClient の修飾子を付ける必要があります。この修飾子は、基礎となるgRPCクライアントの設定に使用される名前を指定するために使用できます。たとえば、この修飾子を hello-service に設定すると、サービスのホストの設定は、 quarkus.grpc.clients.hello-service.host を使用して行われるようになります。 |
| 2 | GrpcClient#value() で名前が指定されていない場合は、代わりにフィールド名が使用されます。例えば、この例では helloService です。 |
スタブクラス名は、 proto ファイルで使用されているサービス名に由来します。例えば、 Greeter をサービス名として使用している場合は以下のようになります。
option java_package = "hello";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
サービスインターフェース名は hello.Greeter 、Mutiny stub名は hello.MutinyGreeterGrpc.MutinyGreeterStub 、Blocking stub名は hello.GreeterGrpc.GreeterBlockingStub となります。
例
サービスインターフェース
import io.quarkus.grpc.GrpcClient;
import io.smallrye.mutiny.Uni;
import hello.Greeter;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/hello")
public class ExampleResource {
@GrpcClient (1)
Greeter hello;
@GET
@Path("/mutiny/{name}")
public Uni<String> helloMutiny(String name) {
return hello.sayHello(HelloRequest.newBuilder().setName(name).build())
.onItem().transform(HelloReply::getMessage);
}
}
| 1 | サービス名はインジェクションポイントに由来する - フィールド名を使用しています。 quarkus.grpc.clients.hello.host プロパティの設定が必要です。 |
ブロッキングスタブ
import io.quarkus.grpc.GrpcClient;
import hello.GreeterGrpc.GreeterBlockingStub;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/hello")
public class ExampleResource {
@GrpcClient("hello") (1)
GreeterGrpc.GreeterBlockingStub blockingHelloService;
@GET
@Path("/blocking/{name}")
public String helloBlocking(String name) {
return blockingHelloService.sayHello(HelloRequest.newBuilder().setName(name).build()).getMessage();
}
}
| 1 | quarkus.grpc.clients.hello.host プロパティを設定する必要があります。 |
ストリームの取り扱い
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のスタブを使用すると、以下のようにこれらとやりとりすることができます。
package io.quarkus.grpc.example.streaming;
import io.grpc.examples.streaming.Empty;
import io.grpc.examples.streaming.Item;
import io.grpc.examples.streaming.MutinyStreamingGrpc;
import io.quarkus.grpc.GrpcClient;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/streaming")
@Produces(MediaType.APPLICATION_JSON)
public class StreamingEndpoint {
@GrpcClient
MutinyStreamingGrpc.MutinyStreamingStub streaming;
@GET
public Multi<String> invokeSource() {
// Retrieve a stream
return streaming.source(Empty.newBuilder().build())
.onItem().transform(Item::getValue);
}
@GET
@Path("sink/{max}")
public Uni<Void> invokeSink(int max) {
// Send a stream and wait for completion
Multi<Item> inputs = Multi.createFrom().range(0, max)
.map(i -> Integer.toString(i))
.map(i -> Item.newBuilder().setValue(i).build());
return streaming.sink(inputs).onItem().ignore().andContinueWithNull();
}
@GET
@Path("/{max}")
public Multi<String> invokePipe(int max) {
// Send a stream and retrieve a stream
Multi<Item> inputs = Multi.createFrom().range(0, max)
.map(i -> Integer.toString(i))
.map(i -> Item.newBuilder().setValue(i).build());
return streaming.pipe(inputs).onItem().transform(Item::getValue);
}
}
クライアント設定
アプリケーションにインジェクトする各gRPCサービスに対して、以下の属性を設定することができます。
Global configuration
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Configuration property |
タイプ |
デフォルト |
|---|---|---|
If set to true, and a Stork load balancer is used, connections with all available service instances will be requested proactively. This means better load balancing at the cost of having multiple active connections. Environment variable: Show more |
boolean |
|
Per-client configuration
ビルド時に固定される設定プロパティ - それ以外の設定プロパティは実行時に上書き可能
Configuration property |
タイプ |
デフォルト |
|---|---|---|
Number of threads on a delayed gRPC ClientCall Environment variable: Show more |
int |
|
Deadline in milliseconds of delayed gRPC call Environment variable: Show more |
long |
|
Number of retries on a gRPC ClientCall Environment variable: Show more |
int |
|
Initial delay in seconds on refresh check Environment variable: Show more |
long |
|
Refresh period in seconds Environment variable: Show more |
long |
|
The gRPC service port. Environment variable: Show more |
int |
|
The gRPC service test port. If not set, uses 8081 for plain text and 8444 when TLS is used. Environment variable: Show more |
int |
|
The host name / IP on which the service is exposed. Environment variable: Show more |
string |
|
The path to a Unix domain socket. When set, the client connects to the gRPC server using a Unix domain socket instead of a TCP connection. The Unix domain sockets are not available on Windows. Environment variable: Show more |
string |
|
The name of the TLS configuration to use. If not set and the default TLS configuration is configured ( If no TLS configuration is set, and Important: This is only supported when using the Quarkus (Vert.x-based) gRPC client. Environment variable: Show more |
string |
|
Whether SSL/TLS is enabled. Environment variable: Show more |
boolean |
|
Enable trusting all certificates. Disabled by default. Environment variable: Show more |
boolean |
|
Comma-separated list of the trust certificate files (Pem format). Environment variable: Show more |
文字列のリスト |
|
Path of the key file (JKS format). Environment variable: Show more |
string |
|
Password of the key file. Environment variable: Show more |
string |
|
Path to the key file (PFX format). Environment variable: Show more |
string |
|
Password of the key. Environment variable: Show more |
string |
|
Comma-separated list of the path to the key files (Pem format). Environment variable: Show more |
文字列のリスト |
|
Comma-separated list of the path to the certificate files (Pem format). Environment variable: Show more |
文字列のリスト |
|
Path of the key file (JKS format). Environment variable: Show more |
string |
|
Password of the key file. Environment variable: Show more |
string |
|
Path to the key file (PFX format). Environment variable: Show more |
string |
|
Password of the key. Environment variable: Show more |
string |
|
Whether hostname should be verified in the SSL/TLS handshake. Environment variable: Show more |
boolean |
|
Use a name resolver. Defaults to dns. If set to "stork", host will be treated as SmallRye Stork service name Environment variable: Show more |
string |
|
Whether Environment variable: Show more |
boolean |
|
The duration without ongoing RPCs before going to idle mode. Environment variable: Show more |
||
The amount of time the sender of a keep alive ping waits for an acknowledgement. Environment variable: Show more |
||
Whether keep-alive will be performed when there are no outstanding RPC on a connection. Environment variable: Show more |
boolean |
|
The maximum message size allowed for a single gRPC frame (in bytes). Default is 4 MiB. Environment variable: Show more |
int |
|
The compression to use for each call. The accepted values are Environment variable: Show more |
string |
|
The deadline used for each call. Environment variable: Show more |
|
期間フォーマットについて
期間の値を書くには、標準の 数字で始まる簡略化した書式を使うこともできます:
その他の場合は、簡略化されたフォーマットが解析のために
|
client-name は、 @GrpcClient で設定された名称、または明示的に定義されていない場合は、インジェクションポイントから派生した名称です。
The following examples use hello as the client name.
Don’t forget to replace it with the name you used in the @GrpcClient annotation.
When you enable quarkus.grpc.clients."client-name".xds.enabled, it’s the xDS that should handle most of the configuration above.
|
Domain Socket
To connect a gRPC client to a server listening on a Unix domain socket, set the domain-socket property to the socket path:
quarkus.grpc.clients.hello.domain-socket=/var/run/grpc.sock
When domain-socket is set, the host and port properties are ignored. The client connects directly to the specified socket file.
Here is a full example with both the server and client configured to use the same domain socket:
# Server: listen on domain socket only
quarkus.http.domain-socket-enabled=true
quarkus.http.domain-socket=/var/run/grpc.sock
quarkus.http.host-enabled=false
# Client: connect via domain socket
quarkus.grpc.clients.hello.domain-socket=/var/run/grpc.sock
The Stork name resolver is incompatible with domain sockets. Setting both domain-socket and name-resolver=stork will result in a startup error.
|
| Unix domain sockets require JDK 16+ and are not available on Windows. |
カスタムチャンネルの構築
QuarkusがgRPCチャネルインスタンス(gRPCクライアントがより低いネットワークレベルでgRPCサービスと通信する方法)を構築する際、ユーザは独自のチャネル(ビルダー)カスタマイザを適用できます。
カスタマイザは priority によって適用され、数字が大きいほど後に適用されます。カスタマイザは、Quarkusがユーザーのクライアント設定を適用する前に適用されます。
2つの customize メソッドがあります。最初のメソッドは gRPC の ManagedChannelBuilder をパラメータとして使用し、Quarkus のレガシー gRPC サポートで使用します。
もう一方は GrpcClientOptions を使用し、新しい Vert.x gRPC サポートで使用します。ユーザーは、gRPCサポートタイプの使用法ごとに適切な customize メソッドを実装する必要があります。
カスタマイザーがgRPCタイプに依存しない場合は、両方を実装する必要があります。
public interface ChannelBuilderCustomizer<T extends ManagedChannelBuilder<T>> {
/**
* Customize a ManagedChannelBuilder instance.
*
* @param name gRPC client name
* @param config client's configuration
* @param builder Channel builder instance
* @return map of config properties to be used as default service config against the builder
*/
default Map<String, Object> customize(String name, GrpcClientConfiguration config, T builder) {
return Map.of();
}
/**
* Customize a GrpcClientOptions instance.
*
* @param name gRPC client name
* @param config client's configuration
* @param options GrpcClientOptions instance
*/
default void customize(String name, GrpcClientConfiguration config, GrpcClientOptions options) {
}
/**
* Priority by which the customizers are applied.
* Higher priority is applied later.
*
* @return the priority
*/
default int priority() {
return 0;
}
}
TLSを有効にする
TLS を有効にするには、以下の設定を使用します。構成内のすべてのパスは、クラスパス上のリソース (通常は src/main/resources またはそのサブフォルダーから) または外部ファイルのいずれかを指定することに注意してください。
quarkus.grpc.clients.hello.host=localhost
# either a path to a classpath resource or to a file:
quarkus.grpc.clients.hello.ssl.trust-store=tls/ca.pem
When SSL/TLS is configured, plain-text is automatically disabled.
|
相互認証付きTLS
相互認証付きのTLSを使用するには、以下の設定を使用します。
quarkus.grpc.clients.hello.host=localhost
quarkus.grpc.clients.hello.plain-text=false
# all the following may use either a path to a classpath resource or to a file:
quarkus.grpc.clients.hello.ssl.certificate=tls/client.pem
quarkus.grpc.clients.hello.ssl.key=tls/client.key
quarkus.grpc.clients.hello.ssl.trust-store=tls/ca.pem
クライアント・スタブ・デッドライン
gRPCスタブにデッドラインを設定する必要がある場合、つまり、以後、スタブが常にステータスエラー DEADLINE_EXCEEDED を返すようになる期間を指定する必要がある場合、デッドラインは、 quarkus.grpc.clients."service-name".deadline の設定プロパティで指定することができます。例:
quarkus.grpc.clients.hello.host=localhost
quarkus.grpc.clients.hello.deadline=2s (1)
| 1 | すべての注入されたスタブのデッドラインを設定します。 |
Do not use this feature to implement an RPC timeout.
To implement an RPC timeout, either use Mutiny call.ifNoItem().after(…) or Fault Tolerance @Timeout.
|
gRPCヘッダー
HTTPと同様に、gRPCの呼び出しはメッセージと一緒にヘッダを運ぶことができます。 ヘッダは、例えば、認証のために有用です。
gRPCコールにヘッダを設定するには、ヘッダを付加したクライアントを作成し、このクライアントで呼び出しを実行します。
import jakarta.enterprise.context.ApplicationScoped;
import examples.Greeter;
import examples.HelloReply;
import examples.HelloRequest;
import io.grpc.Metadata;
import io.quarkus.grpc.GrpcClient;
import io.quarkus.grpc.GrpcClientUtils;
import io.smallrye.mutiny.Uni;
@ApplicationScoped
public class MyService {
@GrpcClient
Greeter client;
public Uni<HelloReply> doTheCall() {
Metadata extraHeaders = new Metadata();
if (headers) {
extraHeaders.put("my-header", "my-interface-value");
}
Greeter alteredClient = GrpcClientUtils.attachHeaders(client, extraHeaders); (1)
return alteredClient.sayHello(HelloRequest.newBuilder().setName(name).build()); (2)
}
}
| 1 | extraHeaders を付加して呼び出しができるようにクライアントを変更します。 |
| 2 | 変更したクライアントで呼び出しを行います。オリジナルのクライアントは変更されないままです。 |
GrpcClientUtils works with all flavors of clients.
クライアントインターセプター
gRPCクライアントインターセプターは、 io.grpc.ClientInterceptor インターフェースも実装する CDI Bean で実装できます。インジェクトされたクライアントに @io.quarkus.grpc.RegisterClientInterceptor のアノテーションを付加することで、特定のクライアントインスタンスに対して指定されたインターセプターを登録できます。 @RegisterClientInterceptor のアノテーションは繰り返し使用できます。また、注入されたクライアントにインターセプターを適用したい場合は、インターセプターBeanに @io.quarkus.grpc.GlobalInterceptor のアノテーションを付けます。
import io.quarkus.grpc.GlobalInterceptor;
import io.grpc.ClientInterceptor;
@GlobalInterceptor (1)
@ApplicationScoped
public class MyInterceptor implements ClientInterceptor {
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
CallOptions callOptions, Channel next) {
// ...
}
}
| 1 | このインターセプターは、インジェクトされたすべてのgRPCクライアントに適用されます。 |
プロデューサー・メソッドをグローバル・インターセプターとしてアノテーションすることも可能です:
import io.quarkus.grpc.GlobalInterceptor;
import jakarta.enterprise.inject.Produces;
public class MyProducer {
@GlobalInterceptor
@Produces
public MyInterceptor myInterceptor() {
return new MyInterceptor();
}
}
| Check the ClientInterceptor JavaDoc to properly implement your interceptor. |
@RegisterClientInterceptor Exampleimport io.quarkus.grpc.GrpcClient;
import io.quarkus.grpc.RegisterClientInterceptor;
import hello.Greeter;
@ApplicationScoped
class MyBean {
@RegisterClientInterceptor(MySpecialInterceptor.class) (1)
@GrpcClient("helloService")
Greeter greeter;
}
| 1 | この特定のクライアントのために MySpecialInterceptor を登録します。 |
複数のクライアントインターセプターがある場合、 jakarta.enterprise.inject.spi.Prioritized インターフェースを実装することで、順番に並べることができます:
@ApplicationScoped
public class MyInterceptor implements ClientInterceptor, Prioritized {
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
CallOptions callOptions, Channel next) {
// ...
}
@Override
public int getPriority() {
return 10;
}
}
最高の優先度を持つインターセプターが最初に呼び出されます。インターセプターが Prioritized インターフェイスを実装していない場合に使用されるデフォルトの優先度は 0 です。
gRPCクライアントのメトリクス
メトリクス収集の有効化
gRPC クライアント・メトリクスは、アプリケーションもエクステンションを使用する場合に自動的に有効になります。 quarkus-micrometer エクステンションを使用している場合、gRPC クライアント・メトリクスは自動的に有効になります。micrometerは、アプリケーションが使用するすべてのgRPCクライアントのメトリクスを収集します。
例えば、メトリクスをPrometheusにエクスポートすると、以下のように取得できます。
# HELP grpc_client_responses_received_messages_total The total number of responses received
# TYPE grpc_client_responses_received_messages_total counter
grpc_client_responses_received_messages_total{method="SayHello",methodType="UNARY",service="helloworld.Greeter",} 6.0
# HELP grpc_client_requests_sent_messages_total The total number of requests sent
# TYPE grpc_client_requests_sent_messages_total counter
grpc_client_requests_sent_messages_total{method="SayHello",methodType="UNARY",service="helloworld.Greeter",} 6.0
# HELP grpc_client_processing_duration_seconds The total time taken for the client to complete the call, including network delay
# TYPE grpc_client_processing_duration_seconds summary
grpc_client_processing_duration_seconds_count{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 6.0
grpc_client_processing_duration_seconds_sum{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 0.167411625
# HELP grpc_client_processing_duration_seconds_max The total time taken for the client to complete the call, including network delay
# TYPE grpc_client_processing_duration_seconds_max gauge
grpc_client_processing_duration_seconds_max{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 0.136478028
サービス名、メソッド、タイプは tags で確認できます。
By default, processing duration is exported as a Prometheus summary (_count, _sum, _max).
To publish aggregatable histogram buckets (for example for histogram_quantile), enable:
quarkus.micrometer.binder.grpc-client.histogram=true
This uses Micrometer’s default percentile histogram buckets (about 1ms to 30s).
Optionally add extra SLO boundaries and/or clamp the published bucket range to reduce cardinality:
quarkus.micrometer.binder.grpc-client.slos=5ms,10ms,25ms,50ms,100ms,1s
quarkus.micrometer.binder.grpc-client.minimum-expected-value=1ms
quarkus.micrometer.binder.grpc-client.maximum-expected-value=10s
Histograms are disabled by default because they increase memory usage and metric cardinality.
カスタム例外処理
If any of the gRPC services or server interceptors throw an (custom) exception, you can add your own ExceptionHandlerProvider as a CDI bean in your application, to provide a custom handling of those exceptions.
例
@ApplicationScoped
public class HelloExceptionHandlerProvider implements ExceptionHandlerProvider {
@Override
public <ReqT, RespT> ExceptionHandler<ReqT, RespT> createHandler(ServerCall.Listener<ReqT> listener,
ServerCall<ReqT, RespT> serverCall, Metadata metadata) {
return new HelloExceptionHandler<>(listener, serverCall, metadata);
}
@Override
public Throwable transform(Throwable t) {
if (t instanceof HelloException he) {
return new StatusRuntimeException(Status.ABORTED.withDescription(he.getName()));
} else {
return ExceptionHandlerProvider.toStatusException(t, true);
}
}
private static class HelloExceptionHandler<A, B> extends ExceptionHandler<A, B> {
public HelloExceptionHandler(ServerCall.Listener<A> listener, ServerCall<A, B> call, Metadata metadata) {
super(listener, call, metadata);
}
@Override
protected void handleException(Throwable t, ServerCall<A, B> call, Metadata metadata) {
StatusRuntimeException sre = (StatusRuntimeException) ExceptionHandlerProvider.toStatusException(t, true);
Metadata trailers = sre.getTrailers() != null ? sre.getTrailers() : metadata;
call.close(sre.getStatus(), trailers);
}
}
}
Devモード
デフォルトでは、devモードでアプリケーションを起動すると、サービスが設定されていない場合でもgRPCサーバが起動します。 gRPCエクステンションのDevモードの動作は、以下のプロパティで設定することができます。
ビルド時に固定される設定プロパティ - それ以外の設定プロパティは実行時に上書き可能
Configuration property |
タイプ |
デフォルト |
|---|---|---|
Start gRPC server in dev mode even if no gRPC services are implemented. By default set to Environment variable: Show more |
boolean |
|
モッククライアントの注入
@QuarkusTest では、 @InjectMock を使用して、gRPC サービスの Mutiny クライアントを注入できます:
@QuarkusTest
public class GrpcMockTest {
@InjectMock
@GrpcClient("hello")
Greeter greeter;
@Test
void test1() {
HelloRequest request = HelloRequest.newBuilder().setName("neo").build();
Mockito.when(greeter.sayHello(Mockito.any(HelloRequest.class)))
.thenReturn(Uni.createFrom().item(HelloReply.newBuilder().setMessage("hello neo").build()));
Assertions.assertEquals(greeter.sayHello(request).await().indefinitely().getMessage(), "hello neo");
}
}
| Only the Mutiny client can be mocked, channels, and other stubs cannot be mocked. |