diff --git a/docs/src/main/asciidoc/mp/grpc/client.adoc b/docs/src/main/asciidoc/mp/grpc/client.adoc new file mode 100644 index 00000000000..2ce1e87b7e2 --- /dev/null +++ b/docs/src/main/asciidoc/mp/grpc/client.adoc @@ -0,0 +1,177 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2019, 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. + +/////////////////////////////////////////////////////////////////////////////// + += gRPC MP Client +:description: Building Helidon gRPC MicroProfile Clients +:keywords: helidon, java, grpc, microprofile, micro-profile, mp +:feature-name: gRPC MicroProfile Clients +:rootdir: {docdir}/../.. +:microprofile-bundle: false + +include::{rootdir}/includes/mp.adoc[] + +== Contents + +- <> +- <> +- <> +- <> +** <> +- <> +** <> +** <> +- <> + +== Overview +Building Java-based gRPC clients using the Helidon MP gRPC API is very simple and removes a lot of +the boilerplate code typically associated with more traditional approaches of writing gRPC clients. +At its simplest, a gRPC Java client can be written using nothing more than a suitably annotated +Java interface. + +include::{rootdir}/includes/dependencies.adoc[] + +[source,xml] +---- + + io.helidon.microprofile.grpc + helidon-microprofile-grpc-client + +---- + +== API + +The following annotations are used to work with Helidon MP gRPC clients: + +* `@Grpc.GrpcChannel` - an annotation used to inject a gRPC channel. +* `@Grpc.GrpcProxy` - an annotation used to mark an injection point for a gRPC service client proxy. +* `@Grpc.GrpcService` - an annotation used to specify the name of a gRPC service to connect to. + +== Configuration + +For a gRPC client to connect to a server, it requires a channel. Channels are configured in the +`grpc` section of the Helidon application configuration. The examples below use an `application.yaml` +file but there are many other ways to to configure Helidon. See +xref:{rootdir}/mp/config/introduction.adoc[Configuration in Helidon] for more information. + +[source,yaml] +---- +grpc: + client: + channels: # <1> + - name: "string-channel" # <2> + host: localhost # <3> + port: 8080 # <4> +---- +<1> Channels are configured in the `channels` section under `grpc.client`. +<2> The name of the channel as referred to in the application code. +<3> The host name for the channel (defaults to localhost). +<4> The port number for the channel (defaults to 1408). + +While most client applications only connect to a single server, it is possible to configure multiple +(an array of) named channels if the client needs to connect to multiple servers. + +=== Configuring TLS + +gRPC runs on top of HTTP/2 which prefers secure TLS connections. Most gRPC channels will also +include a section to configure TLS. Here is a sample of that configuration for the `string-channel`: + +[source,yaml] +---- +grpc: + client: + channels: + - name: "string-channel" + port: 8080 + tls: + trust: + keystore: + passphrase: "password" + trust-store: true + resource: + resource-path: "client.p12" + private-key: + keystore: + passphrase: "password" + resource: + resource-path: "client.p12" +---- + +TLS in the gRPC MP client section is configured in the same way as in other Helidon +components such as the webserver. For more information see +xref:{rootdir}/se/webserver.adoc#_configuring_tls[Configuring TLS]. + +== Usage + +=== Defining a Client Interface + +The next step is to produce an interface with the service methods that the client requires. +For example, suppose we have a simple service that has a unary method to convert a string +to uppercase. To write a client for this service, all that is required is an interface +as shown next: + +[source,java] +---- +include::{sourcedir}/mp/grpc/GrpcSnippets.java[tag=snippet_5, indent=0] +---- + +<1> The `@Grpc.GrpcService` annotation is necessary to provide the name of the gRPC +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 <>. + +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 +method signatures. + +The interface in the example above uses the same method signature as the server, but this +does not need to be the case. For example, it can use a `StreamObserver` as a +second parameter to return the result: + +[source,java] +---- +include::{sourcedir}/mp/grpc/GrpcSnippets.java[tag=snippet_6, indent=0] +---- + +=== Injecting Client Proxies +Now that there is a client interface and a channel configuration, we can then use these +in the client application. +We can declare a field of the same type as the client service interface in the application +class that requires the client. The field is then annotated so that CDI will inject the +client proxy into the field. + +[source,java] +---- +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. + +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. + +== Examples + +Please refer to the link:{helidon-github-examples-url}/microprofile/grpc[Helidon gRPC MP Example]. + diff --git a/docs/src/main/asciidoc/mp/grpc/server.adoc b/docs/src/main/asciidoc/mp/grpc/server.adoc index 9c8e7cdca16..de2bc0520a7 100644 --- a/docs/src/main/asciidoc/mp/grpc/server.adoc +++ b/docs/src/main/asciidoc/mp/grpc/server.adoc @@ -16,7 +16,7 @@ /////////////////////////////////////////////////////////////////////////////// -= gRPC MicroProfile Server += gRPC MP Server :description: Helidon gRPC MicroProfile Server-Side Services :keywords: helidon, java, grpc, microprofile, micro-profile, mp :feature-name: gRPC MicroProfile Server @@ -186,4 +186,4 @@ protocol. == Examples -Please refer to the link:{helidon-github-examples-url}[Helidon Examples] repository. +Please refer to the link:{helidon-github-examples-url}/microprofile/grpc[Helidon gRPC MP Example]. diff --git a/docs/src/main/asciidoc/sitegen.yaml b/docs/src/main/asciidoc/sitegen.yaml index 104a788e5bc..35f50b37b33 100644 --- a/docs/src/main/asciidoc/sitegen.yaml +++ b/docs/src/main/asciidoc/sitegen.yaml @@ -141,6 +141,7 @@ backend: value: "swap_horiz" sources: - "server.adoc" + - "client.adoc" - type: "PAGE" title: "Health Checks" source: "health.adoc" 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 6253e54b027..54f15767343 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 @@ -16,6 +16,10 @@ package io.helidon.docs.mp.grpc; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import io.grpc.Channel; +import io.grpc.stub.StreamObserver; import io.helidon.grpc.api.Grpc; import io.helidon.webserver.grpc.GrpcService; @@ -88,4 +92,48 @@ public void configure(GrpcMpContext context) { // <1> } // end::snippet_4[] } + + class Snippet5 { + + // tag::snippet_5[] + @ApplicationScoped + @Grpc.GrpcService("StringService") // <1> + @Grpc.GrpcChannel("string-channel") // <2> + interface StringServiceClient { + + @Grpc.Unary + String upper(String s); + } + // end::snippet_5[] + } + + class Snippet6 { + + // tag::snippet_6[] + @ApplicationScoped + @Grpc.GrpcService("StringService") + @Grpc.GrpcChannel("string-channel") + interface StringServiceClient { + + @Grpc.Unary + void upper(String s, StreamObserver response); + } + // end::snippet_6[] + } + + class Snippet7 { + + interface StringServiceClient { + } + + // tag::snippet_7[] + @ApplicationScoped + public class MyAppBean { + + @Inject // <1> + @Grpc.GrpcProxy // <2> + private StringServiceClient stringServiceClient; + } + // end::snippet_7[] + } }