Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gRPC - rework annotations #16908

Merged
merged 1 commit into from
Apr 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 12 additions & 14 deletions docs/src/main/asciidoc/grpc-getting-started.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ To override it, set the `quarkus.generate-code.grpc.scan-for-imports` property i

Now that we have the generated classes let's implement our _hello_ service.

With Quarkus, implementing a service requires to _extend_ the generated service base implementation and expose it as a `@Singleton` CDI bean.
With Quarkus, implementing a service requires to _extend_ the generated service base implementation and expose it as as a CDI bean. We encourage you to annotate the service class with the `@io.quarkus.grpc.GrpcService` CDI stereotype.

IMPORTANT: Don't use `@ApplicationScoped` as the gRPC service implementation cannot be proxied.
IMPORTANT: If a gRPC service bean had other scope annotation than `@Singleton` the build would fail.

=== Implementing a service

Expand All @@ -160,10 +160,9 @@ import io.grpc.stub.StreamObserver;
import io.quarkus.example.GreeterGrpc;
import io.quarkus.example.HelloReply;
import io.quarkus.example.HelloRequest;
import io.quarkus.grpc.GrpcService;

import javax.inject.Singleton;

@Singleton // <1>
@GrpcService // <1>
public class HelloService extends GreeterGrpc.GreeterImplBase { // <2>

@Override
Expand Down Expand Up @@ -193,11 +192,11 @@ package org.acme;
import io.quarkus.example.HelloReply;
import io.quarkus.example.HelloRequest;
import io.quarkus.example.MutinyGreeterGrpc;
import io.smallrye.mutiny.Uni;
import io.quarkus.grpc.GrpcService;

import javax.inject.Singleton;
import io.smallrye.mutiny.Uni;

@Singleton
@GrpcService
public class ReactiveHelloService extends MutinyGreeterGrpc.GreeterImplBase {

@Override
Expand Down Expand Up @@ -242,7 +241,7 @@ package org.acme;

import io.quarkus.example.GreeterGrpc;
import io.quarkus.example.HelloRequest;
import io.quarkus.grpc.runtime.annotations.GrpcService;
import io.quarkus.grpc.GrpcClient;

import javax.inject.Inject;
import javax.ws.rs.GET;
Expand All @@ -254,9 +253,8 @@ import javax.ws.rs.core.MediaType;
@Path("/hello")
public class ExampleResource {

@Inject
@GrpcService("hello") // <1>
GreeterGrpc.GreeterBlockingStub client; // <2>
@GrpcClient // <1>
GreeterGrpc.GreeterBlockingStub hello; // <2>

@GET
@Produces(MediaType.TEXT_PLAIN)
Expand All @@ -272,7 +270,7 @@ public class ExampleResource {
}
----

1. Inject the service and configure its name. This name is used in the application configuration.
1. Inject the service and configure its name. The service name is used in the application configuration. If not specified then the field name is used instead: `hello` in this particular case.
2. Use the _blocking_ stub (also a generated class).
3. Invoke the service.

Expand All @@ -284,7 +282,7 @@ In the `src/main/resources/application.properties` file, add the following prope
quarkus.grpc.clients.hello.host=localhost
----

- `hello` is the name of the service used in the `@GrpcService` annotation.
- `hello` is the name of the service used in the `@GrpcClient` annotation.
- `host` configures the service host (here it's localhost).

Then, open http://localhost:8080/hello/quarkus in a browser, and you should get `Hello quarkus`!
Expand Down
28 changes: 17 additions & 11 deletions docs/src/main/asciidoc/grpc-service-consumption.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,19 @@ In addition, it also can inject the gRPC `io.grpc.Channel`, that lets you create

[source, java]
----
@Inject @GrpcService("hello-service")
import io.quarkus.grpc.GrpcClient;

@GrpcClient("helloService") // <1>
MutinyGreeterGrpc.MutinyGreeterStub mutiny;

@Inject @GrpcService("hello-service")
GreeterGrpc.GreeterBlockingStub blocking;
@GrpcClient
GreeterGrpc.GreeterBlockingStub helloService; // <2>

@Inject @GrpcService("hello-service")
@GrpcClient("hello-service")
Channel channel;
----
1. Note that in Quarkus, you can skip the `@Inject` annotation completely if the injected point declares at least one qualifier.
2. If the service name is not specified via `GrpcClient#value()` then the field name is used instead: `helloService` in this particular case.

The stub class names are computed from the service name.
For example, if you use `Greeter` as service name as in:
Expand All @@ -47,7 +51,7 @@ service Greeter {
The Mutiny stub name is: `MutinyGreeterGrpc.MutinyGreeterStub`
The blocking stub name is: `GreeterGrpc.GreeterBlockingStub`

Client injection must be qualified using `@GrpcService`.
Client injection must be qualified using `@GrpcClient`.
This annotation indicates the configuration prefix used to configure the service.
For example, if you set it to `hello-service`, configuring the host of the service is done using `hello-service.host`.

Expand All @@ -57,8 +61,8 @@ For example, if you set it to `hello-service`, configuring the host of the servi

[source, java]
----
@Inject @GrpcService("hello") GreeterGrpc.GreeterBlockingStub blockingHelloService;
@Inject @GrpcService("hello") MutinyGreeterGrpc.MutinyGreeterStub mutinyHelloService;
@GrpcClient("hello") GreeterGrpc.GreeterBlockingStub blockingHelloService;
@GrpcClient("hello") MutinyGreeterGrpc.MutinyGreeterStub mutinyHelloService;

@GET
@Path("/blocking/{name}")
Expand Down Expand Up @@ -98,7 +102,8 @@ 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.runtime.annotations.GrpcService;
import io.quarkus.grpc.GrpcClient;

import io.smallrye.mutiny.Multi;

import javax.inject.Inject;
Expand All @@ -112,7 +117,8 @@ import javax.ws.rs.core.MediaType;
@Produces(MediaType.APPLICATION_JSON)
public class StreamingEndpoint {

@Inject @GrpcService("streaming") MutinyStreamingGrpc.MutinyStreamingStub client;
@GrpcClient
MutinyStreamingGrpc.MutinyStreamingStub streaming;

@GET
public Multi<String> invokeSource() {
Expand Down Expand Up @@ -151,12 +157,12 @@ For each gRPC service you inject in your application, you can configure the foll

include::{generated-dir}/config/quarkus-grpc-config-group-config-grpc-client-configuration.adoc[opts=optional, leveloffset=+1]

The `service-name` is the name set in the `@GrpcService`.
The `service-name` is the name set in the `@GrpcClient` or derived from the injection point if not explicitly defined.

== Example of configuration

The 2 following examples uses _hello_ as service name.
Don't forget to replace it with the name you used in in the `@GrpcService` annotation.
Don't forget to replace it with the name you used in in the `@GrpcClient` annotation.

=== Enabling TLS

Expand Down
14 changes: 8 additions & 6 deletions docs/src/main/asciidoc/grpc-service-implementation.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ When extending them, you need to override the service methods defined in the ser

To implement a gRPC service using the default gRPC API, create a class extending the default implementation base.
Then, override the methods defined in the service interface.
Finally, implement the service as a CDI bean using the `@Singleton` annotation:
Finally, implement the service as a CDI bean using the `@GrpcService` annotation:

[source, java]
----
import javax.inject.Singleton;
import io.quarkus.grpc.GrpcService;

@Singleton
@GrpcService
public class HelloService extends GreeterGrpc.GreeterImplBase {

@Override
Expand All @@ -69,9 +69,9 @@ Finally, implement the service as a CDI bean using the `@Singleton` annotation:

[source, java]
----
import javax.inject.Singleton;
import io.quarkus.grpc.GrpcService;

@Singleton
@GrpcService
public class ReactiveHelloService extends MutinyGreeterGrpc.GreeterImplBase {

@Override
Expand Down Expand Up @@ -115,7 +115,9 @@ Using Mutiny, you can implement these as follows:

[source, java]
----
@Singleton
import io.quarkus.grpc.GrpcService;

@GrpcService
public class StreamingService extends MutinyStreamingGrpc.StreamingImplBase {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget.Kind;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassType;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodParameterInfo;
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;

import io.grpc.Channel;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanDiscoveryFinishedBuildItem;
import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem;
import io.quarkus.arc.processor.BeanConfigurator;
import io.quarkus.arc.processor.BuildExtension;
import io.quarkus.arc.processor.InjectionPointInfo;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
Expand All @@ -36,8 +39,8 @@
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.grpc.GrpcClient;
import io.quarkus.grpc.runtime.GrpcClientInterceptorContainer;
import io.quarkus.grpc.runtime.annotations.GrpcService;
import io.quarkus.grpc.runtime.supports.Channels;
import io.quarkus.grpc.runtime.supports.GrpcClientConfigProvider;
import io.quarkus.grpc.runtime.supports.IOThreadClientInterceptor;
Expand All @@ -53,39 +56,60 @@ public class GrpcClientProcessor {

@BuildStep
void registerBeans(BuildProducer<AdditionalBeanBuildItem> beans) {
beans.produce(AdditionalBeanBuildItem.unremovableOf(GrpcService.class));
beans.produce(AdditionalBeanBuildItem.unremovableOf(GrpcClientConfigProvider.class));
beans.produce(AdditionalBeanBuildItem.unremovableOf(GrpcClientInterceptorContainer.class));
beans.produce(AdditionalBeanBuildItem.unremovableOf(IOThreadClientInterceptor.class));
// @GrpcClient is a CDI qualifier
beans.produce(new AdditionalBeanBuildItem(GrpcClient.class));
beans.produce(AdditionalBeanBuildItem.builder().setUnremovable().addBeanClasses(GrpcClientConfigProvider.class,
GrpcClientInterceptorContainer.class, IOThreadClientInterceptor.class).build());
}

@BuildStep
void discoverInjectedGrpcServices(
BeanRegistrationPhaseBuildItem phase,
void discoverInjectedGrpcServices(BeanDiscoveryFinishedBuildItem beanDiscovery,
BuildProducer<GrpcServiceBuildItem> services,
BuildProducer<FeatureBuildItem> features) {

Map<String, GrpcServiceBuildItem> items = new HashMap<>();

for (InjectionPointInfo injectionPoint : phase.getContext()
.get(BuildExtension.Key.INJECTION_POINTS)) {
AnnotationInstance instance = injectionPoint.getRequiredQualifier(GrpcDotNames.GRPC_SERVICE);
if (instance == null) {
for (InjectionPointInfo injectionPoint : beanDiscovery.getInjectionPoints()) {
AnnotationInstance clientAnnotation = injectionPoint.getRequiredQualifier(GrpcDotNames.GRPC_CLIENT);
if (clientAnnotation == null) {
continue;
}

String name = instance.value().asString();
if (name.trim().isEmpty()) {
String serviceName;
AnnotationValue serviceNameValue = clientAnnotation.value();
if (serviceNameValue == null || serviceNameValue.asString().equals(GrpcClient.ELEMENT_NAME)) {
// Determine the service name from the annotated element
if (clientAnnotation.target().kind() == Kind.FIELD) {
serviceName = clientAnnotation.target().asField().name();
} else if (clientAnnotation.target().kind() == Kind.METHOD_PARAMETER) {
MethodParameterInfo param = clientAnnotation.target().asMethodParameter();
serviceName = param.method().parameterName(param.position());
if (serviceName == null) {
throw new DeploymentException("Unable to determine the service name from the parameter at position "
+ param.position()
+ " in method "
+ param.method().declaringClass().name() + "#" + param.method().name()
+ "() - compile the class with debug info enabled (-g) or parameter names recorded (-parameters), or use GrpcClient#value() to specify the service name");
}
} else {
// This should never happen because @GrpcClient has @Target({ FIELD, PARAMETER })
throw new IllegalStateException(clientAnnotation + " may not be declared at " + clientAnnotation.target());
}
} else {
serviceName = serviceNameValue.asString();
}

if (serviceName.trim().isEmpty()) {
throw new DeploymentException(
"Invalid @GrpcService `" + injectionPoint.getTargetInfo() + "` - missing configuration key");
"Invalid @GrpcClient `" + injectionPoint.getTargetInfo() + "` - service name cannot be empty");
Copy link
Member

@michalszynkiewicz michalszynkiewicz Apr 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blank may be a better word

}

GrpcServiceBuildItem item;
if (items.containsKey(name)) {
item = items.get(name);
if (items.containsKey(serviceName)) {
item = items.get(serviceName);
} else {
item = new GrpcServiceBuildItem(name);
items.put(name, item);
item = new GrpcServiceBuildItem(serviceName);
items.put(serviceName, item);
}

Type injectionType = injectionPoint.getRequiredType();
Expand Down Expand Up @@ -136,7 +160,7 @@ public void generateGrpcServicesProducers(List<GrpcServiceBuildItem> services,
BeanConfigurator<Object> channelProducer = phase.getContext()
.configure(GrpcDotNames.CHANNEL)
.types(Channel.class)
.addQualifier().annotation(GrpcDotNames.GRPC_SERVICE).addValue("value", svc.getServiceName()).done()
.addQualifier().annotation(GrpcDotNames.GRPC_CLIENT).addValue("value", svc.getServiceName()).done()
.scope(Singleton.class)
.unremovable()
.creator(new Consumer<MethodCreator>() {
Expand All @@ -155,7 +179,7 @@ public void accept(MethodCreator mc) {
BeanConfigurator<Object> stubProducer = phase.getContext()
.configure(stubClassName)
.types(stubClass)
.addQualifier().annotation(GrpcDotNames.GRPC_SERVICE).addValue("value", svcName).done()
.addQualifier().annotation(GrpcDotNames.GRPC_CLIENT).addValue("value", svcName).done()
.scope(Singleton.class)
.creator(new Consumer<MethodCreator>() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
import io.grpc.BindableService;
import io.grpc.Channel;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.grpc.runtime.annotations.GrpcService;
import io.quarkus.grpc.GrpcClient;
import io.quarkus.grpc.GrpcService;
import io.quarkus.grpc.runtime.supports.Channels;
import io.smallrye.common.annotation.Blocking;

public class GrpcDotNames {

static final DotName BINDABLE_SERVICE = DotName.createSimple(BindableService.class.getName());
static final DotName CHANNEL = DotName.createSimple(Channel.class.getName());
static final DotName GRPC_CLIENT = DotName.createSimple(GrpcClient.class.getName());
static final DotName GRPC_SERVICE = DotName.createSimple(GrpcService.class.getName());

static final DotName BLOCKING = DotName.createSimple(Blocking.class.getName());
Expand Down
Loading