gRPCサービスの実装
gRPC service implementations exposed as CDI beans are automatically registered and served by quarkus-grpc.
gRPC サービスを実装するには、gRPC クラスを生成する必要があります。 proto ファイルを src/main/proto に置き、 mvn compile を実行します。
|
Generated Code
Quarkus generates a few implementation classes for services declared in the proto
file:
-
A service interface using the Mutiny API
-
the class name is
${JAVA_PACKAGE}.${NAME_OF_THE_SERVICE}
-
-
An implementation base class using the gRPC API
-
the class name is structured as follows:
${JAVA_PACKAGE}.${NAME_OF_THE_SERVICE}Grpc.${NAME_OF_THE_SERVICE}ImplBase
-
For example, if you use the following proto
file snippet:
option java_package = "hello"; (1)
service Greeter { (2)
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
1 | hello is the java package for the generated classes. |
2 | Greeter is the service name. |
Then the service interface is hello.Greeter
and the implementation base is the abstract static nested class: 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. |
Implementing a Service with the Mutiny API
To implement a gRPC service using the Mutiny API, create a class that implements the service interface. Then, implement the methods defined in the service interface. If you don’t want to implement a service method just throw an java.lang.UnsupportedOperationException
from the method body (the exception will be automatically converted to the appropriate gRPC exception). Finally, implement the service and add the @GrpcService
annotation:
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 | A gRPC service implementation bean must be annotated with the @GrpcService annotation and should not declare any other CDI qualifier. All gRPC services have the javax.inject.Singleton scope. Additionally, the request context is always active during a service call. |
2 | hello.Greeter is the generated service interface. |
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 .
|
Implementing a Service with the default gRPC API
To implement a gRPC service using the default gRPC API, create a class that extends the default implementation base. Then, override the methods defined in the service interface. Finally, implement the service and add the @GrpcService
annotation:
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();
}
}
Blocking Service Implementation
デフォルトでは、gRPCサービスからのすべてのメソッドはイベントループ上で実行されます。そのため、ブロックしては いけません 。サービスロジックをブロックする必要がある場合は、メソッドに io.smallrye.common.annotation.Blocking
アノテーションを追加します。
@Override
@Blocking
public Uni<HelloReply> sayHelloBlocking(HelloRequest request) {
// Do something blocking before returning the Uni
}
Handling Streams
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());
}
}
Health Check
実装されたサービスでは、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
を設定することで、サーバーのインスタンス数をセットできます。
Server Configuration
ビルド時に固定される設定プロパティ - それ以外の設定プロパティは実行時に上書き可能
タイプ |
デフォルト |
|
---|---|---|
The gRPC Server port. Environment variable: |
int |
|
The gRPC Server port used for tests. Environment variable: |
int |
|
The gRPC server host. Environment variable: |
string |
|
The gRPC handshake timeout. Environment variable: |
||
The max inbound message size in bytes. Environment variable: |
int |
|
The max inbound metadata size in bytes Environment variable: |
int |
|
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 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: |
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: |
string |
|
A parameter to specify the password of the key store file. If not given, the default ("password") is used. Environment variable: |
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: |
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: |
string |
|
A parameter to specify the password of the trust store file. Environment variable: |
string |
|
The cipher suites to use. If none is given, a reasonable default is selected. Environment variable: |
list of string |
|
The list of protocols to explicitly enable. Environment variable: |
list of string |
|
Configures the engine to require/request client authentication. NONE, REQUEST, REQUIRED Environment variable: |
|
|
Disables SSL, and uses plain text instead. If disabled, configure the ssl configuration. Environment variable: |
boolean |
|
Whether ALPN should be used. Environment variable: |
boolean |
|
The path to the certificate file. Environment variable: |
string |
|
The path to the private key file. Environment variable: |
string |
|
Enables the gRPC Reflection Service. By default, the reflection service is only exposed in Environment variable: |
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: |
int |
|
Sets a custom keep-alive duration. This configures the time before sending a Environment variable: |
||
gRPC compression, e.g. "gzip" Environment variable: |
string |
期間フォーマットについて
期間のフォーマットは標準の 数値で始まる期間の値を指定することもできます。この場合、値が数値のみで構成されている場合、コンバーターは値を秒として扱います。そうでない場合は、 |
Example of Configuration
サーバーインターセプター
gRPC server interceptors let you perform logic, such as authentication, before your service is invoked.
You can implement a gRPC server interceptor by creating an @ApplicationScoped
bean implementing io.grpc.ServerInterceptor
:
@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) {
// ...
}
}
ServerInterceptor JavaDoc をチェックして、インターセプターを適切に実装してください。 |
To apply an interceptor to all exposed services, annotate it with @io.quarkus.grpc.GlobalInterceptor
. To apply an interceptor to a single service, register it on the service with @io.quarkus.grpc.RegisterInterceptor
:
import io.quarkus.grpc.GrpcService;
import io.quarkus.grpc.RegisterInterceptor;
@GrpcService
@RegisterInterceptor(MyInterceptor.class)
public class StreamingService implements Streaming {
// ...
}
When you have multiple server interceptors, you can order them by implementing the javax.enterprise.inject.spi.Prioritized
interface. Please note that all the global interceptors are invoked before the service-specific interceptors.
@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
です。
Testing your services
The easiest way to test a gRPC service is to use a gRPC client as described in Consuming a gRPC Service.
Please note that in the case of using a client to test an exposed service that does not use TLS, there is no need to provide any configuration. E.g. to test the HelloService
defined above, one could create the following test:
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");
}
}
Trying out your services manually
In the dev mode, you can try out your gRPC services in the Quarkus Dev UI. Just go to http://localhost:8080/q/dev and click on Services under the gRPC tile.
Please note that your application needs to expose the "normal" HTTP port for the Dev UI to be accessible. If your application does not expose any HTTP endpoints, you can create a dedicated profile with a dependency on quarkus-vertx-http
:
<profiles>
<profile>
<id>development</id>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http</artifactId>
</dependency>
</dependencies>
</profiles>
</profile>
Having it, you can run the dev mode with: mvn quarkus:dev -Pdevelopment
.
If you use Gradle, you can simply add a dependency for the quarkusDev
task:
dependencies {
quarkusDev 'io.quarkus:quarkus-vertx-http'
}
gRPC Server metrics
Enabling metrics collection
gRPC server metrics are automatically enabled when the application also uses the quarkus-micrometer
extension. Micrometer collects the metrics of all the gRPC services implemented by the application.
As an example, if you export the metrics to Prometheus, you will get:
# 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
The service name, method and type can be found in the tags.