gRPC サービスの使用
gRPCクライアントは、アプリケーションコードにインジェクトすることができます。
gRPC サービスを使用するには、gRPC クラスを生成する必要があります。 proto ファイルを src/main/proto に置き、 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 javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.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 javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.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 javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.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サービスに対して、以下の属性を設定することができます。
ビルド時に固定される設定プロパティ - その他の設定プロパティはランタイムでオーバーライド可能です。
型 |
デフォルト |
|
---|---|---|
Use new Vert.x gRPC client support. By default, we still use previous Java gRPC support. Environment variable: |
boolean |
|
Explicitly enable use of XDS. Environment variable: |
boolean |
|
Use secure credentials. Environment variable: |
boolean |
|
Optional explicit target. Environment variable: |
string |
|
The gRPC service port. Environment variable: |
int |
|
The host name / IP on which the service is exposed. Environment variable: |
string |
required |
The classpath path or file path to a server certificate or certificate chain in PEM format. Environment variable: |
path |
|
The classpath path or file path to the corresponding certificate private key file in PEM format. Environment variable: |
path |
|
An optional trust store which holds the certificate information of the certificates to trust The trust store can be either on classpath or in an external file. Environment variable: |
path |
|
Use a name resolver. Defaults to dns. If set to "stork", host will be treated as SmallRye Stork service name Environment variable: |
string |
|
Whether Environment variable: |
boolean |
|
The duration after which a keep alive ping is sent. Environment variable: |
||
The flow control window in bytes. Default is 1MiB. Environment variable: |
int |
|
The duration without ongoing RPCs before going to idle mode. Environment variable: |
||
The amount of time the sender of a keep alive ping waits for an acknowledgement. Environment variable: |
||
Whether keep-alive will be performed when there are no outstanding RPC on a connection. Environment variable: |
boolean |
|
The max number of hedged attempts. Environment variable: |
int |
|
The max number of retry attempts. Retry must be explicitly enabled. Environment variable: |
int |
|
The maximum number of channel trace events to keep in the tracer for each channel or sub-channel. Environment variable: |
int |
|
The maximum message size allowed for a single gRPC frame (in bytes). Default is 4 MiB. Environment variable: |
int |
|
The maximum size of metadata allowed to be received (in bytes). Default is 8192B. Environment variable: |
int |
|
The negotiation type for the HTTP/2 connection. Accepted values are: Environment variable: |
string |
|
Overrides the authority used with TLS and HTTP virtual hosting. Environment variable: |
string |
|
The per RPC buffer limit in bytes used for retry. Environment variable: |
長 |
|
Whether retry is enabled. Note that retry is disabled by default. Environment variable: |
boolean |
|
The retry buffer size in bytes. Environment variable: |
長 |
|
Use a custom user-agent. Environment variable: |
string |
|
Use a custom load balancing policy. Accepted values are: Environment variable: |
string |
|
The compression to use for each call. The accepted values are Environment variable: |
string |
|
The deadline used for each call.
The format uses the standard Environment variable: |
期間フォーマットについて
期間のフォーマットは標準の 数値で始まる期間の値を指定することもできます。この場合、値が数値のみで構成されている場合、コンバーターは値を秒として扱います。そうでない場合は、 |
client-name
は、 @GrpcClient
で設定された名称、または明示的に定義されていない場合は、インジェクションポイントから派生した名称です。
以下の例では、クライアント名として _hello_を使用しています。 @GrpcClient
アノテーションで使用した名前に置き換えることを忘れないでください。
When you enable quarkus.grpc.clients."client-name".use-quarkus-grpc-client , you are then using the new Vert.x gRPC channel implementation, so not all configuration properties can still be applied. And currently there is no Stork support yet.
|
When you enable quarkus.grpc.clients."client-name".xds.enabled , it’s the xDS that should handle most of the configuration above.
|
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
SSL/TLSを設定すると、 plain-text は自動的に無効になります。
|
相互認証付き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
Client Stub Deadlines
If you need to configure a deadline for a gRPC stub, i.e. to specify a duration of time after which the stub will always return the status error DEADLINE_EXCEEDED
. You can specify the deadline via the quarkus.grpc.clients."service-name".deadline
configuration property, e.g.:
quarkus.grpc.clients.hello.host=localhost
quarkus.grpc.clients.hello.deadline=2s (1)
1 | Set the deadline for all injected stubs. |
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 javax.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
は、あらゆる種類のクライアントに対応しています。
クライアントインターセプター
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クライアントに適用されます。 |
ClientInterceptor JavaDoc をチェックして、インターセプターを適切に実装してください。 |
@RegisterClientInterceptor
の例import 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 を登録します。 |
複数のクライアント・インターセプターがある場合、 javax.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クライアントのメトリクス
メトリクス収集の有効化
アプリケーションが 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 で確認できます。
Custom exception handling
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.
e.g.
@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モードの動作は、以下のプロパティで設定することができます。
ビルド時に固定される設定プロパティ - その他の設定プロパティはランタイムでオーバーライド可能です。
型 |
デフォルト |
|
---|---|---|
Start gRPC server in dev mode even if no gRPC services are implemented. By default set to Environment variable: |
boolean |
|
Inject mock clients
In your @QuarkuTest
, you can use @InjectMock
to inject the Mutiny client of a gRPC service:
@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. |