From 272f4d0512d984d44a133c1746cd97880a40f896 Mon Sep 17 00:00:00 2001 From: Santiago Pericas-Geertsen Date: Fri, 16 Aug 2024 10:22:49 -0400 Subject: [PATCH 1/2] Implements support for client gRPC channel injections. Does not use CDI qualifiers for forward compatibility with our declarative API (annotations will be reused). New test that shows field and constructor injection. --- .../main/java/io/helidon/grpc/api/Grpc.java | 4 +- .../grpc/client/ChannelProducer.java | 13 +---- .../grpc/client/ChannelInjectionTest.java | 50 +++++++++++++++++++ 3 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 microprofile/grpc/client/src/test/java/io/helidon/microprofile/grpc/client/ChannelInjectionTest.java diff --git a/grpc/api/src/main/java/io/helidon/grpc/api/Grpc.java b/grpc/api/src/main/java/io/helidon/grpc/api/Grpc.java index 4f5a50c2e70..3a4f3585cfe 100644 --- a/grpc/api/src/main/java/io/helidon/grpc/api/Grpc.java +++ b/grpc/api/src/main/java/io/helidon/grpc/api/Grpc.java @@ -275,7 +275,7 @@ public interface Grpc { } /** - * An annotation to indicate the response type of a gRPC method. + * An annotation to indicate the response type of gRPC method. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @@ -293,7 +293,7 @@ public interface Grpc { /** * An annotation that can be used to specify the name of a configured gRPC channel. */ - @Target({ElementType.TYPE, ElementType.METHOD}) + @Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @interface GrpcChannel { diff --git a/microprofile/grpc/client/src/main/java/io/helidon/microprofile/grpc/client/ChannelProducer.java b/microprofile/grpc/client/src/main/java/io/helidon/microprofile/grpc/client/ChannelProducer.java index 9a1cb1f3c5f..521965b2ed8 100644 --- a/microprofile/grpc/client/src/main/java/io/helidon/microprofile/grpc/client/ChannelProducer.java +++ b/microprofile/grpc/client/src/main/java/io/helidon/microprofile/grpc/client/ChannelProducer.java @@ -57,9 +57,8 @@ public class ChannelProducer { * @return a gRPC {@link io.grpc.Channel} */ @Produces - @Grpc.GrpcChannel(GrpcChannelsProvider.DEFAULT_CHANNEL_NAME) public Channel get(InjectionPoint injectionPoint) { - Grpc.GrpcChannel qualifier = injectionPoint.getQualifiers() + Grpc.GrpcChannel qualifier = injectionPoint.getAnnotated().getAnnotations() .stream() .filter(q -> q.annotationType().equals(Grpc.GrpcChannel.class)) .map(q -> (Grpc.GrpcChannel) q) @@ -70,16 +69,6 @@ public Channel get(InjectionPoint injectionPoint) { return findChannel(name); } - /** - * Produces the default gRPC {@link io.grpc.Channel}. - * - * @return the default gRPC {@link io.grpc.Channel} - */ - @Produces - public Channel getDefaultChannel() { - return findChannel(GrpcChannelsProvider.DEFAULT_CHANNEL_NAME); - } - /** * Obtain the named {@link io.grpc.Channel}. * diff --git a/microprofile/grpc/client/src/test/java/io/helidon/microprofile/grpc/client/ChannelInjectionTest.java b/microprofile/grpc/client/src/test/java/io/helidon/microprofile/grpc/client/ChannelInjectionTest.java new file mode 100644 index 00000000000..3a427d2f42d --- /dev/null +++ b/microprofile/grpc/client/src/test/java/io/helidon/microprofile/grpc/client/ChannelInjectionTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.microprofile.grpc.client; + +import io.helidon.grpc.api.Grpc; +import io.helidon.microprofile.testing.junit5.HelidonTest; + +import io.grpc.Channel; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@HelidonTest +class ChannelInjectionTest { + + @Inject + @Grpc.GrpcChannel("echo-channel") + private Channel echoChannel1; + + private final Channel echoChannel2; + + @Inject + ChannelInjectionTest(@Grpc.GrpcChannel("echo-channel") Channel echoChannel2) { + this.echoChannel2 = echoChannel2; + } + + @Test + void testInjection() { + assertThat(echoChannel1, notNullValue()); + assertThat(echoChannel2, notNullValue()); + assertEquals(echoChannel1.getClass(), echoChannel2.getClass()); + assertEquals(echoChannel1.authority(), echoChannel2.authority()); + } +} From 91b08efbc35ecdf559283c5f3c1349ffb1580be9 Mon Sep 17 00:00:00 2001 From: Santiago Pericas-Geertsen Date: Fri, 16 Aug 2024 11:31:57 -0400 Subject: [PATCH 2/2] Updates documentation with a new section. Signed-off-by: Santiago Pericas-Geertsen --- docs/src/main/asciidoc/mp/grpc/client.adoc | 26 ++++++++++++++++--- .../io/helidon/docs/mp/grpc/GrpcSnippets.java | 9 +++++++ .../grpc/client/ChannelInjectionTest.java | 6 ++--- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/docs/src/main/asciidoc/mp/grpc/client.adoc b/docs/src/main/asciidoc/mp/grpc/client.adoc index 2ce1e87b7e2..bf2b196de1d 100644 --- a/docs/src/main/asciidoc/mp/grpc/client.adoc +++ b/docs/src/main/asciidoc/mp/grpc/client.adoc @@ -35,6 +35,7 @@ include::{rootdir}/includes/mp.adoc[] - <> ** <> ** <> +** <> - <> == Overview @@ -133,7 +134,7 @@ include::{sourcedir}/mp/grpc/GrpcSnippets.java[tag=snippet_5, indent=0] service when it differs from the interface name, as it is the case in this example. <2> The `@Grpc.GrpcChannel` annotation is the qualifier that supplies the channel name. This is the same name as used in the channel configuration in the examples provided in -the <>. +the <>. There is no need to write any code to implement the client. The Helidon MP gRPC API will create a dynamic proxy for the interface using the information from the annotations and @@ -162,14 +163,33 @@ include::{sourcedir}/mp/grpc/GrpcSnippets.java[tag=snippet_7, indent=0] <1> The `@Inject` annotation tells CDI to inject the client implementation. <2> The `@Grpc.GrpcProxy` annotation is used by the CDI container to match the injection point to -the gRPC MP APIs provider. +the gRPC MP API provider. When the CDI container instantiates `MyAppBean`, it will inject a dynamic proxy into the `stringServiceClient` field, and then provide the necessary logic for the proxy methods to convert a method call into a gRPC call. In the example above, there is no need to use a channel directly. The correct channel is added to -the dynamic client proxy internally by the Helidon MP gRPC APIs. +the dynamic client proxy internally by the Helidon MP gRPC API. + +=== Injecting Channels + +Channels can also be directly injected into application bean instances. +The Helidon gRPC client API has CDI producers to inject `io.grpc.Channel` instances. + +For example, a class might have an injectable `io.grpc.Channel` field as follows: + +[source,java] +---- +include::{sourcedir}/mp/grpc/GrpcSnippets.java[tag=snippet_8, indent=4] +---- + +<1> The `@Inject` annotation tells CDI to inject the channel. +<2> The `@Grpc.GrpcChannel` annotation supplies the channel name. +This is the same name as used in the channel configuration in the examples provided in +the <>. + +An injected channel can be used, for example, when directly instantiating `protoc` generated stubs. == Examples diff --git a/docs/src/main/java/io/helidon/docs/mp/grpc/GrpcSnippets.java b/docs/src/main/java/io/helidon/docs/mp/grpc/GrpcSnippets.java index 54f15767343..6e83cb4fa25 100644 --- a/docs/src/main/java/io/helidon/docs/mp/grpc/GrpcSnippets.java +++ b/docs/src/main/java/io/helidon/docs/mp/grpc/GrpcSnippets.java @@ -136,4 +136,13 @@ public class MyAppBean { } // end::snippet_7[] } + + class Snippet8 { + + // tag::snippet_8[] + @Inject // <1> + @Grpc.GrpcChannel("string-channel") // <2> + private Channel channel; + // end::snippet_8[] + } } diff --git a/microprofile/grpc/client/src/test/java/io/helidon/microprofile/grpc/client/ChannelInjectionTest.java b/microprofile/grpc/client/src/test/java/io/helidon/microprofile/grpc/client/ChannelInjectionTest.java index 3a427d2f42d..5c17ff64123 100644 --- a/microprofile/grpc/client/src/test/java/io/helidon/microprofile/grpc/client/ChannelInjectionTest.java +++ b/microprofile/grpc/client/src/test/java/io/helidon/microprofile/grpc/client/ChannelInjectionTest.java @@ -22,9 +22,9 @@ import jakarta.inject.Inject; import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; @HelidonTest class ChannelInjectionTest { @@ -44,7 +44,7 @@ class ChannelInjectionTest { void testInjection() { assertThat(echoChannel1, notNullValue()); assertThat(echoChannel2, notNullValue()); - assertEquals(echoChannel1.getClass(), echoChannel2.getClass()); - assertEquals(echoChannel1.authority(), echoChannel2.authority()); + assertThat(echoChannel1.getClass(), equalTo(echoChannel2.getClass())); + assertThat(echoChannel1.authority(), equalTo(echoChannel2.authority())); } }