-
Notifications
You must be signed in to change notification settings - Fork 136
First commit of gRPC Bridge example #127
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
Merged
Merged
Changes from 1 commit
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| # gRPC Bridge Example | ||
|
|
||
| gRPC ([https://grpc.io/](https://grpc.io/)), like Jakarta RESTFul Web Services | ||
| ([https://jakarta.ee/specifications/restful-ws/](https://jakarta.ee/specifications/restful-ws/)), | ||
| is a framework for client-server invocations. The semantics, | ||
| however, are quite different, and, normally, they are two distinct and non-communicating worlds. However, | ||
| the RESTEasy [gRPC Bridge](https://github.com/resteasy/Resteasy) project provides a mechanism for creating | ||
| a bridge between the two worlds. In particular, gRPC Bridge can take an existing Jakarta RESTful Web Services | ||
| (Jakarta REST, for short) project (the **target** project) and extend it to a **bridge** project which contains | ||
|
|
||
| 1. the contents of the target project, and | ||
| 2. generated classes that constitute a layer that dispatches a gRPC client invocation to a Jakarta REST | ||
| service. | ||
|
|
||
| Note that 1 implies that the bridge project can still function as a Jakarta REST project. | ||
|
|
||
| In this example, we will walk though the process of building a bridge project, using this project as | ||
| the target project. There are three classes: | ||
|
|
||
| 1. `Greeting`: a simple message type | ||
| 2. `GeneralGreeting`: a message type derived from `Greeting` | ||
| 3. `Greeter`: a Jakarta REST resource class with two resource methods | ||
|
|
||
| **Note.** | ||
| We assume a basic understanding of Jakarta REST, gRPC, and [protobuf](https://protobuf.dev/), the data | ||
| foundation of gRPC. | ||
|
|
||
| ## Introduction | ||
|
|
||
| A gRPC application begins with a .proto file, a language independent description of message types and | ||
| procedure calls, which can be compiled into a number of supported programming languages. In our context, | ||
| however, we are assuming the prior existence of a Jakarta REST application. The first step, then, in | ||
| creating a gRPC bridge application is to parse a set of resource classes and generate a matching .proto file. | ||
| When the .proto file is compiled into Java, the result is a set of classes representing the original | ||
| classes. For each original class, we call the compiled class its **javabuf** representation. The gRPC | ||
| infrastructure supports translating javabuf classes to and from a wire representation, so a gRPC client | ||
| can send a javabuf instance, and a reconstituted javabuf instance will appear on the server side. Normally, | ||
| the javabuf instance would be dispatched to a gRPC service, but, in our context, we want to dispatch it | ||
| to a Jakarta REST service. As we will see below, the gRPC bridge project supplies the mechanism for | ||
| translating a javabuf instance to its corresponding original Java class and dispatching it appropriately. | ||
|
|
||
| ## Building the bridge project | ||
|
|
||
| 1. Create a directory for the bridge project. We will refer to it as BUILD_HOME. | ||
|
|
||
| 2. Use the archetype org.jboss.resteasy:gRPCtoJakartaREST-archetype:${archetype-version} to create the | ||
| skeleton of the bridge project: | ||
|
|
||
| <pre><code> | ||
| mvn archetype:generate -B \ | ||
| -DarchetypeGroupId=org.jboss.resteasy \ | ||
| -DarchetypeArtifactId=gRPCtoJakartaREST-archetype \ | ||
| -DarchetypeVersion=${archetype-version} \ | ||
| -DgroupId=dev.resteasy.examples \ | ||
| -DartifactId=grpcToRest.example \ | ||
| -Dversion=0.0.1-SNAPSHOT \ | ||
| -Dgenerate-prefix=Greet \ | ||
| -Dgenerate-package=org.greet \ | ||
| -Dresteasy-version=6.2.3.Final | ||
| </pre></code> | ||
|
|
||
| The parameters groupId, artifactId, and version describe the target project. | ||
| The result will be a new maven project, dev.resteasy.examples:grpcToRest.example.grpc:0.0.1-SNAPSHOT in | ||
| a directory named grpcToRest.example (the value of artifactId). Note that the groupId and version are copied | ||
| from the target project, and ".grpc" is added to the artifactId. The parameters generate-prefix and generate-package are | ||
| applied to several classes that will be generated. | ||
|
|
||
| The resulting skeleton bridge project has contents: | ||
|
|
||
| <pre><code> | ||
| grpcToRest/pom.xml | ||
| grpcToRest/src/main/resources/buildjar | ||
| grpcToRest/src/main/resources/deployjar | ||
| grpcToRest/src/main/webapp/META-INF/beans.xml | ||
| grpcToRest/src/main/webapp/WEB-INF/web.xml | ||
| grpcToRest/src/test/java/org/jboss/resteasy/grpc/server/Greet_Server.java | ||
| </code></pre> | ||
|
|
||
| 3. Build the bridge project: | ||
|
|
||
| <pre><code> | ||
| mvn clean install | ||
| </code></pre> | ||
|
|
||
| A number of things happen: | ||
|
|
||
| * The Java classes are copied from the target project: | ||
|
|
||
| src/main/java/dev | ||
| src/main/java/dev/resteasy | ||
| src/main/java/dev/resteasy/greet | ||
| src/main/java/dev/resteasy/greet/Greeting.java | ||
| src/main/java/dev/resteasy/greet/Greeter.java | ||
| src/main/java/dev/resteasy/greet/GeneralGreeting.java | ||
|
|
||
|
|
||
| * A .proto file is created to describe the gRPC message types and procedure calls: | ||
|
|
||
| src/main/proto/Greet.proto | ||
|
|
||
| Let's take a look at Greet.proto. The two message types are transformed into | ||
|
|
||
| message dev_resteasy_greet___Greeting { | ||
| string s = 1; | ||
| } | ||
|
|
||
| message dev_resteasy_greet___GeneralGreeting { | ||
| string salute = 2; | ||
| dev_resteasy_greet___Greeting greeting___super = 3; | ||
| } | ||
|
|
||
| There are two things to note: | ||
|
|
||
| 1. protobuf doesn't have a concept of packages, so we represent package names with '_' characters. | ||
| 2. protobuf doesn't have a concept of inheritance, so we use a specially named member (ending in `___super`) to represent a parent class. | ||
|
|
||
| Going back to `Greet.proto`, let's also look at the method calls derived from `Greeter`. First, note that there are | ||
| three methods in `Greeter` but only two rpc entries in `Greet.proto`. That's because, lacking annotations, | ||
| `getGeneralGreeting()` is not a resource method. Also, note the presence of two types, `GeneralEntityMessage` and | ||
| `GeneralReturnMessage`, which do not come from the original classes: | ||
|
|
||
| <pre><code> | ||
| rpc greet (GeneralEntityMessage) returns (GeneralReturnMessage); | ||
| rpc generalGreet (GeneralEntityMessage) returns (GeneralReturnMessage); | ||
| </code></pre> | ||
|
|
||
| These general purpose types are needed to bridge part of the gap between the gRPC and Jakarta REST semantics. | ||
| Whereas gRPC method calls support only a single parameter, Jakarta REST, in addition to an entity parameter, | ||
| also has path parameters, query parameters, etc., and those are accomodated in `GeneralEntityMessage`. | ||
|
|
||
| * Five Java classes are generated, which constitute the intermediate layer between gRPC and the target | ||
| project: | ||
|
|
||
| <pre><code> | ||
| target/generated-sources/protobuf/java/org/greet/Greet_proto.java | ||
| target/generated-sources/protobuf/grpc-java/org/greet/GreetServiceGrpc.java | ||
| target/generated-sources/protobuf/grpc-java/org/greet/GreetServiceGrpcImpl.java | ||
| target/generated-sources/protobuf/grpc-java/org/greet/GreetJavabufTranslator.java | ||
| target/generated-sources/protobuf/grpc-java/org/greet/GreetMessageBodyReaderWriter.java | ||
| </code></pre> | ||
|
|
||
| 1. `Greet_proto`: This is the compiled version of Greet.proto. The javabuf classes are defined here. | ||
| 2. `GreetJavabufTranslator`: This tranlates back and forth between javabuf classes and their corresponding original classes. | ||
| 3. `GreetMessageBodyReaderWriter`: This implements the Jakarta REST `MessageBodyReader` and `MessageBodyWriter` interfaces. | ||
| 4. `GreetServiceGrpc`: This class, generated by the compiler, has a stub method for each procedure call in `Greet.proto`. | ||
| 5. `GreetServiceGrpcImpl`: Each method in `GreetServiceGrpc` is overridden here with code that creates a servlet environment and dispatches | ||
| invocation to a Jakarta REST resource method. | ||
|
|
||
| * A WAR ready to be deployed is generated, in this case called grpcToRest.example.grpc-0.0.1-SNAPSHOT.war. | ||
|
|
||
| ## Using the bridge project | ||
|
|
||
| The easiest way of using the bridge project is to deploy the WAR to a version of WildFly supplied with the gRPC subsystem and | ||
| RESTEasy's grpc-bridge-runtime, both of which are available as WildFly feature packs. A suitable WildFly can be built as follows: | ||
|
|
||
| [THIS needs to be changed] | ||
| <pre><code> | ||
| galleon.sh install wildfly-preview:current --dir=wildfly; | ||
| galleon.sh install org.jboss.resteasy:galleon-preview-feature-pack:6.2.2.Final-SNAPSHOT --dir=wildfly --ignore-not-excluded-layers=true; | ||
| galleon.sh install org.wildfly.extras.grpc:wildfly-grpc-feature-pack:0.0.5-SNAPSHOT --layers=grpc --dir=wildfly | ||
| </code></pre> | ||
|
|
||
| Once WildFly is available, the WAR can be deployed, and invocations may be made from the client. However, there is one preliminary | ||
| step that is necessary. `GreetServiceGrpcImpl` creates a servlet environment in which a Jakarta REST resource method can run, and part | ||
| of that process involves supplying a `jakarta.servlet.ServletContext`. Making a native Jakarta REST call on the | ||
| `org.jboss.resteasy.grpc.server.Greet_Server` supplied in the bridge project will accomplish that. For example, | ||
|
|
||
| <pre><code> | ||
| Client client = ClientBuilder.newClient(); | ||
| client.target("http: //localhost:8080/grpcToRest.grpc-0.0.1-SNAPSHOT/grpcToJakartaRest/grpcserver/context").request().get(); | ||
| </code></pre> | ||
|
|
||
| Now, let's consider the client side. The client side code is targeted at the gRPC runtime: | ||
|
|
||
| <pre><code> | ||
| @Test | ||
| public void testGeneralGreeting() { | ||
| GeneralEntityMessage.Builder builder = GeneralEntityMessage.newBuilder(); | ||
| GeneralEntityMessage gem = builder.setURL("http: //localhost:8080/salute/Bill?salute=Heyyy").build(); | ||
| try { | ||
| GeneralReturnMessage grm = blockingStub.generalGreet(gem); | ||
| dev_resteasy_greet___GeneralGreeting greeting = grm.getDevResteasyGreetGeneralGreetingField(); | ||
| Assert.assertEquals("Heyyy", greeting.getSalute()); | ||
| Assert.assertEquals("Bill", greeting.getGreetingSuper().getS()); | ||
| } catch (StatusRuntimeException e) { | ||
| // | ||
| } | ||
| } | ||
| </code></pre> | ||
|
|
||
| Note that the URL host and port are ignored by `Greeter.generalGreet()`, so we just use "localhost:8080" as | ||
| a placeholder. | ||
|
|
||
| This example comes with a test you can run, GreetingTest.java, but it's in src/main/resources because it depends on classes | ||
| that will be created in grpcToRest.example.grpc. To run it, copy it to the src/test/java directory in grpcToRest.example.grpc, | ||
| and add two dependencies to the pom.xml: | ||
|
|
||
| <pre><code> | ||
| <dependency> | ||
| <groupId>junit</groupId> | ||
| <artifactId>junit</artifactId> | ||
| <version>4.13.2</version> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>org.jboss.resteasy</groupId> | ||
| <artifactId>resteasy-client</artifactId> | ||
| <version>6.2.3.Final</version> | ||
| </dependency> | ||
| </code></pre> | ||
|
|
||
| ## Exploring further | ||
|
|
||
| 1. The "gRPC Bridge" chapter in the RESTEasy User Guide [https://resteasy.dev/docs/](https://resteasy.dev/docs/) has more detailed information. | ||
| 2. The `org.jboss.resteasy.test.grpc.GrpcToJakartaRESTTest` in the grpc-bridge project has a lot of code demonstrating a variety | ||
| of situations. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| <modelVersion>4.0.0</modelVersion> | ||
| <parent> | ||
| <groupId>dev.resteasy.tools</groupId> | ||
| <artifactId>resteasy-parent</artifactId> | ||
| <version>2.0.3.Final</version> | ||
| <relativePath/> | ||
| </parent> | ||
|
|
||
| <groupId>dev.resteasy.examples</groupId> | ||
| <artifactId>grpcToRest.example</artifactId> | ||
| <version>0.0.1-SNAPSHOT</version> | ||
| <packaging>war</packaging> | ||
| <name>gRPC to Jakarta REST example</name> | ||
| <description>Shows how to build a gRPC to Jakarta REST bridge example</description> | ||
| <licenses> | ||
| <license> | ||
| <name>Apache License 2.0</name> | ||
| <url>https://repository.jboss.org/licenses/apache-2.0.txt</url> | ||
| <distribution>repo</distribution> | ||
| </license> | ||
| </licenses> | ||
| <scm> | ||
| <connection>scm:git:git://github.com/resteasy/resteasy-examples.git</connection> | ||
| <developerConnection>scm:git:git@github.com:resteasy/resteasy-examples.git</developerConnection> | ||
| <url>https://github.com/resteasy/resteasy-examples/tree/main/</url> | ||
| </scm> | ||
|
|
||
| <properties> | ||
| <version.jakarta.ee>10.0.0</version.jakarta.ee> | ||
| <version.org.jboss.resteasy>6.2.3.Final</version.org.jboss.resteasy> | ||
| <version.junit>4.13.2</version.junit> | ||
| </properties> | ||
|
|
||
| <dependencyManagement> | ||
| <dependencies> | ||
| <dependency> | ||
| <groupId>jakarta.platform</groupId> | ||
| <artifactId>jakarta.jakartaee-bom</artifactId> | ||
| <version>${version.jakarta.ee}</version> | ||
| <scope>import</scope> | ||
| <type>pom</type> | ||
| </dependency> | ||
| </dependencies> | ||
| </dependencyManagement> | ||
|
|
||
| <dependencies> | ||
| <dependency> | ||
| <groupId>jakarta.ws.rs</groupId> | ||
| <artifactId>jakarta.ws.rs-api</artifactId> | ||
| </dependency> | ||
| </dependencies> | ||
|
|
||
| <build> | ||
| <finalName>${project.artifactId}-${version}</finalName> | ||
| <plugins> | ||
| <plugin> | ||
| <artifactId>maven-war-plugin</artifactId> | ||
| <configuration> | ||
| <failOnMissingWebXml>false</failOnMissingWebXml> | ||
| </configuration> | ||
| </plugin> | ||
| <plugin> | ||
| <artifactId>maven-compiler-plugin</artifactId> | ||
| <configuration> | ||
| <release>11</release> | ||
| </configuration> | ||
| </plugin> | ||
|
ronsigal marked this conversation as resolved.
Outdated
|
||
| <plugin> | ||
| <groupId>net.revelc.code.formatter</groupId> | ||
| <artifactId>formatter-maven-plugin</artifactId> | ||
| </plugin> | ||
| <plugin> | ||
| <groupId>net.revelc.code</groupId> | ||
| <artifactId>impsort-maven-plugin</artifactId> | ||
| </plugin> | ||
| </plugins> | ||
| </build> | ||
| </project> | ||
25 changes: 25 additions & 0 deletions
25
grpc-bridge-example/src/main/java/dev/resteasy/greet/GeneralGreeting.java
|
ronsigal marked this conversation as resolved.
Outdated
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package dev.resteasy.greet; | ||
|
ronsigal marked this conversation as resolved.
Outdated
|
||
|
|
||
| public class GeneralGreeting extends Greeting { | ||
| private String salute; | ||
|
|
||
| public GeneralGreeting() { | ||
| } | ||
|
|
||
| public GeneralGreeting(String salute, String s) { | ||
| super(s); | ||
| this.salute = salute; | ||
| } | ||
|
|
||
| public String getSalute() { | ||
| return salute; | ||
| } | ||
|
|
||
| public void setSalute(String salute) { | ||
| this.salute = salute; | ||
| } | ||
|
|
||
| public String toString() { | ||
| return salute + ", " + getS(); | ||
| } | ||
| } | ||
30 changes: 30 additions & 0 deletions
30
grpc-bridge-example/src/main/java/dev/resteasy/greet/Greeter.java
|
ronsigal marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package dev.resteasy.greet; | ||
|
|
||
| import jakarta.ws.rs.GET; | ||
| import jakarta.ws.rs.Path; | ||
| import jakarta.ws.rs.PathParam; | ||
| import jakarta.ws.rs.Produces; | ||
| import jakarta.ws.rs.QueryParam; | ||
| import jakarta.ws.rs.core.MediaType; | ||
|
|
||
| @Path("") | ||
| public class Greeter { | ||
|
|
||
| @GET | ||
| @Path("greet/{s}") | ||
| @Produces(MediaType.APPLICATION_JSON) | ||
| public Greeting greet(@PathParam("s") String s) { | ||
| return new Greeting("hello, " + s); | ||
| } | ||
|
|
||
| @GET | ||
| @Path("salute/{s}") | ||
| @Produces(MediaType.APPLICATION_JSON) | ||
| public GeneralGreeting generalGreet(@QueryParam("salute") String salute, @PathParam("s") String s) { | ||
| return getGeneralGreeting(salute, s); | ||
| } | ||
|
|
||
| private GeneralGreeting getGeneralGreeting(String salute, String name) { | ||
| return new GeneralGreeting(salute, name); | ||
| } | ||
| } |
24 changes: 24 additions & 0 deletions
24
grpc-bridge-example/src/main/java/dev/resteasy/greet/Greeting.java
|
ronsigal marked this conversation as resolved.
Outdated
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package dev.resteasy.greet; | ||
|
|
||
| public class Greeting { | ||
| private String s; | ||
|
|
||
| public Greeting() { | ||
| } | ||
|
|
||
| public Greeting(String s) { | ||
| this.s = s; | ||
| } | ||
|
|
||
| public String getS() { | ||
| return s; | ||
| } | ||
|
|
||
| public void setS(String s) { | ||
| this.s = s; | ||
| } | ||
|
|
||
| public String toString() { | ||
| return "Hello, " + s; | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.