| <?xml version="1.0"?> |
| <!-- |
| |
| Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. |
| |
| This program and the accompanying materials are made available under the |
| terms of the Eclipse Public License v. 2.0, which is available at |
| http://www.eclipse.org/legal/epl-2.0. |
| |
| This Source Code may also be made available under the following Secondary |
| Licenses when the conditions for such availability set forth in the |
| Eclipse Public License v. 2.0 are satisfied: GNU General Public License, |
| version 2 with the GNU Classpath Exception, which is available at |
| https://www.gnu.org/software/classpath/license.html. |
| |
| SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 |
| |
| --> |
| |
| <!DOCTYPE chapter [ |
| <!ENTITY % ents SYSTEM "jersey.ent" > %ents; |
| <!ENTITY jersey.github.rx.example.link "<link xlink:href='&jersey.github.examples.uri;/rx-client-webapp'>Travel Agency (Orchestration Layer) Example using Reactive Jersey Client API</link>"> |
| <!ENTITY jersey.github.rx.java8.example.link "<link xlink:href='&jersey.github.examples.uri;/rx-client-java8-webapp'>Travel Agency (Orchestration Layer) Example using Reactive Jersey Client API (Java 8)</link>"> |
| ]> |
| |
| <chapter xmlns="http://docbook.org/ns/docbook" |
| version="5.0" |
| xml:lang="en" |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| xmlns:xi="http://www.w3.org/2001/XInclude" |
| xmlns:xlink="http://www.w3.org/1999/xlink" |
| xsi:schemaLocation="http://docbook.org/ns/docbook http://docbook.org/xml/5.0/xsd/docbook.xsd |
| http://www.w3.org/1999/xlink http://www.w3.org/1999/xlink.xsd" |
| xml:id="rx-client"> |
| |
| <title>Reactive JAX-RS Client API</title> |
| |
| <warning> |
| <para> |
| Jersey 2.26 (JAX-RS 2.1 implementation) dropped Jersey-proprietary API in favor of JAX-RS 2.1 Reactive Client API. |
| For Jersey 3.x this approach is still valid. |
| </para> |
| </warning> |
| |
| <para> |
| Reactive client extension is quite a generic API allowing end users to utilize the popular reactive programming model |
| when using JAX-RS Client. The API is designed to be extensible, so any existing reactive framework can integrate with |
| it and there is build in support for CompletionStage. Along with describing the API itself, this section also covers |
| existing extension modules and provides hints to implement a custom extension if needed. |
| </para> |
| |
| <para> |
| If you are not familiar with the JAX-RS Client API, it is recommended that you see <xref linkend="client"/> |
| where the basics of JAX-RS Client API along with some advanced techniques are described. |
| </para> |
| |
| |
| |
| <section> |
| <title>Motivation for Reactive Client Extension</title> |
| |
| <simplesect> |
| <title>The Problem</title> |
| |
| <para> |
| Imagine a travel agency whose information system consists of multiple basic services. These services might be built |
| using different technologies (JMS, EJB, WS, ...). For simplicity we presume that the services can be |
| consumed using REST interface via HTTP method calls (e.g. using a JAX-RS Client). We also presume that the basic |
| services we need to work with are: |
| |
| <itemizedlist> |
| <listitem> |
| <para> |
| <emphasis>Customers service</emphasis> – provides information about customers of the travel agency. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| <emphasis>Destinations service</emphasis> – provides a list of visited and recommended destinations |
| for an authenticated customer. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| <emphasis>Weather service</emphasis> – provides weather forecast for a given destination. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| <emphasis>Quoting service</emphasis> – provides price calculation for a customer to travel to |
| a recommended destination. |
| </para> |
| </listitem> |
| </itemizedlist> |
| </para> |
| <para> |
| The task is to create a publicly available feature that would, for an authenticated user, display a list of 10 last visited places and |
| also display a list of 10 new recommended destinations including weather forecast and price calculations for the |
| user. Notice that some of the requests (to retrieve data) depend on results of previous requests. E.g. getting |
| recommended destinations depends on obtaining information about the authenticated user first. Obtaining weather |
| forecast depends on destination information, etc. This relationship between some of the requests is an important part of the |
| problem and an area where you can take a real advantage of the reactive programming model. |
| </para> |
| <para> |
| One way how to obtain data is to make multiple HTTP method calls from the client (e.g. mobile device) to all |
| services involved and combine the retrieved data on the client. However, since the basic services are available |
| in the internal network only we'd rather create a public orchestration layer instead of exposing all internal services to the outside world. |
| The orchestration layer would expose only the desired operations of the basic services |
| to the public. To limit traffic and achieve lower latency we'd like to return all the necessary |
| information to the client in a single response. |
| </para> |
| <para> |
| The orchestration layer is illustrated in the <xref linkend="rx.client.motivation.problem" xrefstyle="select: label" />. |
| The layer accepts requests from the outside and is responsible of invoking multiple requests to the internal services. |
| When responses from the internal services are available in the orchestration layer they're combined into |
| a single response that is sent back to the client. |
| |
| <figure xml:id="rx.client.motivation.problem"> |
| <title>Travel Agency Orchestration Service</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/rx-client-problem.png" format="PNG" width="80%" scalefit="1" align="center"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| |
| The next sections describe various approaches (using JAX-RS Client) how the orchestration layer can be implemented. |
| </para> |
| </simplesect> |
| |
| <simplesect> |
| <title>A Naive Approach</title> |
| |
| <para> |
| The simplest way to implement the orchestration layer is to use synchronous approach. For this purpose we can use |
| JAX-RS Client Sync API (see <xref linkend="rx.client.motivation.naive" />). The implementation is simple to do, |
| easy to read and straightforward to debug. |
| |
| <example xml:id="rx.client.motivation.naive"> |
| <title>Excerpt from a synchronous approach while implementing the orchestration layer</title> |
| |
| <programlisting language="java" linenumbering="numbered">final WebTarget destination = ...; |
| final WebTarget forecast = ...; |
| |
| // Obtain recommended destinations. |
| List<Destination> recommended = Collections.emptyList(); |
| try { |
| recommended = destination.path("recommended").request() |
| // Identify the user. |
| .header("Rx-User", "Sync") |
| // Return a list of destinations. |
| .get(new GenericType<List<Destination>>() {}); |
| } catch (final Throwable throwable) { |
| errors.offer("Recommended: " + throwable.getMessage()); |
| } |
| |
| // Forecasts. (depend on recommended destinations) |
| final Map<String, Forecast> forecasts = new HashMap<>(); |
| for (final Destination dest : recommended) { |
| try { |
| forecasts.put(dest.getDestination(), |
| forecast.resolveTemplate("destination", dest.getDestination()).request().get(Forecast.class)); |
| } catch (final Throwable throwable) { |
| errors.offer("Forecast: " + throwable.getMessage()); |
| } |
| }</programlisting> |
| </example> |
| |
| The downside of this approach is its slowness. You need to sequentially process all the independent requests which |
| means that you're wasting resources. You are needlessly blocking threads, that could be otherwise used for some real work. |
| </para> |
| <para> |
| If you take a closer look at the example you can notice that at the moment when all the recommended destinations are |
| available for further processing we try to obtain forecasts for these destinations. Obtaining a weather forecast |
| can be done only for a single destination with a single request, so we need to make 10 requests to |
| the <emphasis>Forecast service</emphasis> to get all the destinations covered. In a synchronous way this means getting the forecasts |
| one-by-one. When one response with a forecast arrives we can send another request to obtain another one. This takes |
| time. The whole process of constructing a response for the client can be seen in |
| <xref linkend="rx.client.motivation.graph.sync" xrefstyle="select: label" />. |
| </para> |
| <para> |
| Let's try to quantify this with assigning an approximate time to every request we make to the internal services. |
| This way we can easily compute the time needed to complete a response for the client. For example, obtaining |
| |
| <itemizedlist> |
| <listitem> |
| <para><emphasis>Customer details</emphasis> takes 150 ms</para> |
| </listitem> |
| <listitem> |
| <para><emphasis>Recommended destinations</emphasis> takes 250 ms</para> |
| </listitem> |
| <listitem> |
| <para><emphasis>Price calculation for a customer and destination</emphasis> takes 170 ms (each)</para> |
| </listitem> |
| <listitem> |
| <para><emphasis>Weather forecast for a destination</emphasis> takes 330 ms (each)</para> |
| </listitem> |
| </itemizedlist> |
| |
| When summed up, 5400 ms is approximately needed to construct a response for the client. |
| |
| <figure xml:id="rx.client.motivation.graph.sync"> |
| <title>Time consumed to create a response for the client – synchronous way</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/rx-client-sync-approach.png" format="PNG" width="80%" scalefit="1" align="center"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| |
| Synchronous approach is better to use for lower number of requests (where the accumulated time doesn't matter that |
| much) or for a single request that depends on the result of previous operations. |
| </para> |
| </simplesect> |
| |
| <simplesect> |
| <title>Optimized Approach</title> |
| |
| <para> |
| The amount of time needed by the synchronous approach can be lowered by invoking independent requests in parallel. |
| We're going to use JAX-RS Client Async API to illustrate this approach. The implementation in this case is slightly |
| more difficult to get right because of the nested callbacks and the need to wait at some points for the moment |
| when all partial responses are ready to be processed. The implementation is also a little bit harder to debug and maintain. |
| The nested calls are causing a lot of complexity here. An example of concrete Java code following the asynchronous approach |
| can be seen in <xref linkend="rx.client.motivation.optimized" />. |
| |
| <example xml:id="rx.client.motivation.optimized"> |
| <title>Excerpt from an asynchronous approach while implementing the orchestration layer</title> |
| |
| <programlisting language="java" linenumbering="numbered">final WebTarget destination = ...; |
| final WebTarget forecast = ...; |
| |
| // Obtain recommended destinations. (does not depend on visited ones) |
| destination.path("recommended").request() |
| // Identify the user. |
| .header("Rx-User", "Async") |
| // Async invoker. |
| .async() |
| // Return a list of destinations. |
| .get(new InvocationCallback<List<Destination>>() { |
| @Override |
| public void completed(final List<Destination> recommended) { |
| final CountDownLatch innerLatch = new CountDownLatch(recommended.size()); |
| |
| // Forecasts. (depend on recommended destinations) |
| final Map<String, Forecast> forecasts = Collections.synchronizedMap(new HashMap<>()); |
| for (final Destination dest : recommended) { |
| forecast.resolveTemplate("destination", dest.getDestination()).request() |
| .async() |
| .get(new InvocationCallback<Forecast>() { |
| @Override |
| public void completed(final Forecast forecast) { |
| forecasts.put(dest.getDestination(), forecast); |
| innerLatch.countDown(); |
| } |
| |
| @Override |
| public void failed(final Throwable throwable) { |
| errors.offer("Forecast: " + throwable.getMessage()); |
| innerLatch.countDown(); |
| } |
| }); |
| } |
| |
| // Have to wait here for dependent requests ... |
| try { |
| if (!innerLatch.await(10, TimeUnit.SECONDS)) { |
| errors.offer("Inner: Waiting for requests to complete has timed out."); |
| } |
| } catch (final InterruptedException e) { |
| errors.offer("Inner: Waiting for requests to complete has been interrupted."); |
| } |
| |
| // Continue with processing. |
| } |
| |
| @Override |
| public void failed(final Throwable throwable) { |
| errors.offer("Recommended: " + throwable.getMessage()); |
| } |
| });</programlisting> |
| </example> |
| </para> |
| <para> |
| The example is a bit more complicated from the first glance. We provided an &jaxrs.client.InvocationCallback; to async |
| <literal>get</literal> method. One of the callback methods (<literal>completed</literal> or <literal>failed</literal>) |
| is called when the request finishes. This is a pretty convenient way to handle async invocations when no nested |
| calls are present. Since we have some nested calls (obtaining weather forecasts) we needed to introduce |
| a &jdk6.CountDownLatch; synchronization primitive as we use asynchronous approach in obtaining the weather |
| forecasts as well. The latch is decreased every time a request, to the <emphasis>Forecasts service</emphasis>, |
| completes successfully or fails. This indicates that the request actually finished and it is a signal for us that |
| we can continue with processing (otherwise we wouldn't have all required data to construct the response for the |
| client). This additional synchronization is something that was not present when taking the synchronous approach, |
| but it is needed here. |
| </para> |
| <para> |
| Also the error processing can not be written as it could be in an ideal case. The error handling is scattered in |
| too many places within the code, that it is quite difficult to create a comprehensive response for the client. |
| </para> |
| <para> |
| On the other hand taking asynchronous approach leads to code that is as fast as it gets. |
| The resources are used optimally (no waiting threads) to achieve |
| quick response time. The whole process of constructing the response for the client can be seen in |
| <xref linkend="rx.client.motivation.graph.async" xrefstyle="select: label" />. It only took 730 ms instead of |
| 5400 ms which we encountered in the previous approach. |
| |
| <figure xml:id="rx.client.motivation.graph.async"> |
| <title>Time consumed to create a response for the client – asynchronous way</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/rx-client-async-approach.png" format="PNG" width="80%" scalefit="1" align="center"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| |
| As you can guess, this approach, even with all it's benefits, is the one that is really hard to implement, debug |
| and maintain. It's a safe bet when you have many independent calls to make but it gets uglier with an increasing |
| number of nested calls. |
| </para> |
| </simplesect> |
| |
| <simplesect> |
| <title>Reactive Approach</title> |
| |
| <para> |
| Reactive approach is a way out of the so-called <emphasis>Callback Hell</emphasis> which you can encounter when |
| dealing with Java's <literal>Future</literal>s or invocation callbacks. Reactive approach is based on a data-flow |
| concept and the execution model propagate changes through the flow. An example of a single item in the data-flow |
| chain can be a JAX-RS Client HTTP method call. When the JAX-RS request finishes then the next item (or the user code) |
| in the data-flow chain is notified about the continuation, completion or error in the chain. You're more describing |
| what should be done next than how the next action in the chain should be triggered. The other important part here |
| is that the data-flows are composable. You can compose/transform multiple flows into the resulting one and apply |
| more operations on the result. |
| </para> |
| <para> |
| An example of this approach can be seen in <xref linkend="rx.client.motivation.reactive" />. The APIs would be |
| described in more detail in the next sections. |
| |
| <example xml:id="rx.client.motivation.reactive"> |
| <title>Excerpt from a reactive approach while implementing the orchestration layer</title> |
| |
| <programlisting language="java" linenumbering="numbered">final WebTarget destination = ...; |
| final WebTarget forecast = ...; |
| |
| // Recommended places. |
| CompletionStage<List<Destination>> recommended = |
| destination.path("recommended") |
| .request() |
| // Identify the user. |
| .header("Rx-User", "CompletionStage") |
| // Reactive invoker. |
| .rx() |
| // Return a list of destinations. |
| .get(new GenericType<List<Destination>>() {}) |
| .exceptionally(throwable -> { |
| errors.offer("Recommended: " + throwable.getMessage()); |
| return Collections.emptyList(); |
| }); |
| |
| // get Forecast for recommended destinations. |
| return recommended.thenCompose(destinations -> { |
| |
| List<CompletionStage<Recommendation>> recommendations = destinations.stream().map(destination -> { |
| // For each destination, obtain a weather forecast ... |
| final CompletionStage<Forecast> forecastResult = |
| forecast.resolveTemplate("destination", destination.getDestination()) |
| .request().rx().get(Forecast.class) |
| .exceptionally(throwable -> { |
| errors.offer("Forecast: " + throwable.getMessage()); |
| return new Forecast(destination.getDestination(), "N/A"); |
| }); |
| |
| //noinspection unchecked |
| return CompletableFuture.completedFuture(new Recommendation(destination)) |
| // Set forecast for recommended destination. |
| .thenCombine(forecastResult, Recommendation::forecast) |
| }).collect(Collectors.toList()); |
| |
| // Transform List<CompletionStage<Recommendation>> to CompletionStage<List<Recommendation>> |
| return sequence(recommendations); |
| });</programlisting> |
| |
| </example> |
| </para> |
| <para> |
| As you can see the code achieves the same work as the previous two examples. It's more readable than the pure |
| asynchronous approach even though it's equally fast. It's as easy to read and implement as the synchronous approach. |
| The error processing is also better handled in this way than in the asynchronous approach. |
| </para> |
| <para> |
| When dealing with a large amount of requests (that depend on each other) and when you need to compose/combine the |
| results of these requests, the reactive programming model is the right technique to use. |
| </para> |
| </simplesect> |
| </section> |
| |
| <section> |
| <title>Usage and Extension Modules</title> |
| |
| <para> |
| Reactive Client API is part of the JAX-RS specification since version 2.1. |
| </para> |
| |
| <para> |
| When you compare synchronous invocation of HTTP calls ( |
| <xref linkend="rx.client.sync" />) |
| |
| <example xml:id="rx.client.sync"> |
| <title>Synchronous invocation of HTTP requests</title> |
| |
| <programlisting language="java" linenumbering="numbered">Response response = ClientBuilder.newClient() |
| .target("http://example.com/resource") |
| .request() |
| .get();</programlisting> |
| </example> |
| |
| with asynchronous invocation (<xref linkend="rx.client.async" />) |
| |
| <example xml:id="rx.client.async"> |
| <title>Asynchronous invocation of HTTP requests</title> |
| |
| <programlisting language="java" linenumbering="numbered">Future<Response> response = ClientBuilder.newClient() |
| .target("http://example.com/resource") |
| .request() |
| .async() |
| .get();</programlisting> |
| </example> |
| |
| it is apparent how to pretty conveniently modify the way how a request is invoked (from sync to async) only by calling |
| <literal>async</literal> method on an &jaxrs.client.Invocation.Builder;. |
| </para> |
| <para> |
| Naturally, it'd be nice to copy the same pattern to allow invoking requests in a reactive way. Just instead of |
| <literal>async</literal> you'd call <literal>rx</literal> on an extension of &lit.jaxrs.client.Invocation.Builder;, |
| like in <xref linkend="rx.client.reactive" />. |
| |
| <example xml:id="rx.client.reactive"> |
| <title>Reactive invocation of HTTP requests</title> |
| |
| <programlisting language="java" linenumbering="numbered">CompletionStage<Response> response = ClientBuilder.newClient() |
| .target("http://example.com/resource") |
| .request() |
| .rx() |
| .get();</programlisting> |
| </example> |
| </para> |
| <para> |
| The first reactive interface in the invocation chain is &jaxrs.client.RxInvoker; which is very similar to |
| &jaxrs.client.SyncInvoker; and &jaxrs.client.AsyncInvoker;. It contains all methods present in the two latter |
| JAX-RS interfaces but the &lit.jaxrs.client.RxInvoker; interface is more generic, so that it can be extended |
| and used in particular implementations taking advantage of various reactive libraries. Extending this new interface |
| in a particular implementation also preserves type safety which means that you're not loosing type information when a HTTP |
| method call returns an object that you want to process further. |
| </para> |
| <para> |
| The method "rx()" in the example above is perfect example of that principle. It returns &jaxrs.client.CompletionStageRxInvoker;, |
| which extends &jaxrs.client.RxInvoker;. |
| </para> |
| <para> |
| As a user of the Reactive Client API you only need to keep in mind that you won't be working with |
| &lit.jaxrs.client.RxInvoker; directly. You'd rather be working with an extension of this interface created for |
| a particular implementation and you don't need to be bothered much with why are things designed the way they are. |
| |
| <note> |
| <para> |
| To see how the &lit.jaxrs.client.RxInvoker; should be extended, refer to |
| <xref linkend="rx.client.spi" />. |
| </para> |
| </note> |
| |
| The important thing to notice here is that an extension of &lit.jaxrs.client.RxInvoker; holds the type |
| information and the Reactive Client needs to know about this type to properly propagate it among the method |
| calls you'll be making. This is the reason why other interfaces (described bellow) are parametrized with this type. |
| </para> |
| <para> |
| In order to extend the API to be used with other reactive frameworks, &jaxrs.client.RxInvokerProvider; needs to be |
| registered into the Client runtime: |
| </para> |
| <programlisting language="java" linenumbering="numbered">Client client = ClientBuilder.newClient(); |
| client.register(RxFlowableInvokerProvider.class); |
| |
| Flowable<String> responseFlowable = |
| client.target("http://eclipse-ee4j.github.io/jersey") |
| .request() |
| .rx(RxFlowableInvoker.class) |
| .get(String.class); |
| |
| String responseString = responseFlowable.blockingFirst();</programlisting> |
| |
| <simplesect> |
| <title>Dependencies</title> |
| |
| <para> |
| JAX-RS mandates support for CompletionStage, which doesn't required any other dependency and can be |
| used out of the box. |
| </para> |
| <para> |
| To add support for a particular library, see the <xref linkend="rx.client.supported" />. |
| </para> |
| |
| <note> |
| <para> |
| If you're not using Maven (or other dependency management tool) make sure to add also all the transitive |
| dependencies of Jersey client module and any other extensions (when used) on the class-path. |
| </para> |
| </note> |
| </simplesect> |
| </section> |
| |
| <section xml:id="rx.client.supported"> |
| <title>Supported Reactive Libraries</title> |
| |
| <para> |
| There are already some available reactive (or reactive-like) libraries out there and Jersey brings support for some of |
| them out of the box. Jersey currently supports: |
| |
| <itemizedlist> |
| <listitem> |
| <para><xref linkend="rx-client.rxjava" endterm="rx-client.rxjava.title" /></para> |
| </listitem> |
| <listitem> |
| <para><xref linkend="rx-client.rxjava2" endterm="rx-client.rxjava2.title" /></para> |
| </listitem> |
| <listitem> |
| <para><xref linkend="rx-client.guava" endterm="rx-client.guava.title" /></para> |
| </listitem> |
| </itemizedlist> |
| </para> |
| |
| <section xml:id="rx-client.rxjava"> |
| <title>RxJava – Observable</title> |
| <titleabbrev xml:id="rx-client.rxjava.title">RxJava (Observable)</titleabbrev> |
| |
| <para> |
| &rxjava.link;, contributed by Netflix, is probably the most advanced reactive library for Java at the moment. It's |
| used for composing asynchronous and event-based programs by using observable sequences. It uses the |
| <link xlink:href="&wikipedia.uri;Observer_pattern">observer pattern</link> to support these sequences of data/events |
| via its &rxjava.Observable; entry point class which implements the Reactive Pattern. &lit.rxjava.Observable; is |
| actually the parameter type in the RxJava's extension of &jaxrs.client.RxInvoker;, called |
| &jersey.ext.rx.client.rxjava.RxObservableInvoker;. This means that the return type of HTTP method calls is |
| &lit.rxjava.Observable; in this case (accordingly parametrized). |
| </para> |
| <para> |
| Requests are by default invoked at the moment when a subscriber is subscribed to an observable (it's a cold |
| &lit.rxjava.Observable;). If not said otherwise a separate thread (JAX-RS Async Client requests) is used to |
| obtain data. This behavior can be overridden by providing an &jdk6.ExecutorService; when a reactive |
| &lit.jaxrs.client.Client; is created. |
| </para> |
| |
| <simplesect> |
| <title>Usage</title> |
| |
| <para> |
| The extensibility is built-in JAX-RS Client API, so there are no special dependencies on Jersey Client |
| API other than the extension itself. |
| |
| <example xml:id="rx.client.rxjava.rx"> |
| <title>Creating JAX-RS Client with RxJava reactive extension</title> |
| |
| <programlisting language="java" linenumbering="numbered">// New Client |
| Client client = ClientBuilder.newClient(); |
| client.register(RxObservableInvokerProvider.class);</programlisting> |
| </example> |
| </para> |
| <para> |
| An example of obtaining &lit.rxjava.Observable; with JAX-RS &lit.jaxrs.core.Response; from a remote service |
| can be seen in <xref linkend="rx.client.rxjava.usage" />. |
| |
| <example xml:id="rx.client.rxjava.usage"> |
| <title>Obtaining Observable<Response> from Jersey/RxJava Client</title> |
| |
| <programlisting language="java" linenumbering="numbered">Observable<Response> observable = RxObservable.newClient() |
| .target("http://example.com/resource") |
| .request() |
| .rx(RxObservableInvoker.class) |
| .get();</programlisting> |
| </example> |
| </para> |
| </simplesect> |
| |
| <simplesect> |
| <title>Dependencies</title> |
| |
| <para> |
| The RxJava support is available as an extension module in Jersey. For Maven users, |
| simply add the following dependency to your &lit.pom.xml;: |
| |
| <programlisting language="xml" linenumbering="unnumbered"><dependency> |
| <groupId>org.glassfish.jersey.ext.rx</groupId> |
| <artifactId>jersey-rx-client-rxjava</artifactId> |
| <version>&version;</version> |
| </dependency></programlisting> |
| |
| After this step you can use the extended client right away. The dependency transitively adds the following |
| dependencies to your class-path as well: <literal>io.reactivex:rxjava</literal>. |
| </para> |
| |
| <note> |
| <para> |
| If you're not using Maven (or other dependency management tool) make sure to add also all the transitive |
| dependencies of this extension module (see &jersey.ext.rx-client.rxjava.deps.link;) on the class-path. |
| </para> |
| </note> |
| </simplesect> |
| </section> |
| |
| <section xml:id="rx-client.rxjava2"> |
| <title>RxJava – Flowable</title> |
| <titleabbrev xml:id="rx-client.rxjava2.title">RxJava (Flowable)</titleabbrev> |
| |
| <para> |
| &rxjava.link;, contributed by Netflix, is probably the most advanced reactive library for Java at the moment. It's |
| used for composing asynchronous and event-based programs by using observable sequences. It uses the |
| <link xlink:href="&wikipedia.uri;Observer_pattern">observer pattern</link> to support these sequences of data/events |
| via its &rxjava2.Flowable; entry point class which implements the Reactive Pattern. &lit.rxjava2.Flowable; is |
| actually the parameter type in the RxJava's extension of &jaxrs.client.RxInvoker;, called |
| &jersey.ext.rx.client.rxjava2.RxFlowableInvoker;. This means that the return type of HTTP method calls is |
| &lit.rxjava2.Flowable; in this case (accordingly parametrized). |
| </para> |
| <para> |
| Requests are by default invoked at the moment when a subscriber is subscribed to a flowable (it's a cold |
| &lit.rxjava2.Flowable;). If not said otherwise a separate thread (JAX-RS Async Client requests) is used to |
| obtain data. This behavior can be overridden by providing an &jdk6.ExecutorService; when a reactive |
| &lit.jaxrs.client.Client; is created. |
| </para> |
| |
| <simplesect> |
| <title>Usage</title> |
| |
| <para> |
| The extensibility is built-in JAX-RS Client API, so there are no special dependencies on Jersey Client |
| API other than the extension itself. |
| |
| <example xml:id="rx.client.rxjava2.rx"> |
| <title>Creating JAX-RS Client with RxJava2 reactive extension</title> |
| |
| <programlisting language="java" linenumbering="numbered">// New Client |
| Client client = ClientBuilder.newClient(); |
| client.register(RxFlowableInvokerProvider.class);</programlisting> |
| </example> |
| </para> |
| <para> |
| An example of obtaining &lit.rxjava2.Flowable; with JAX-RS &lit.jaxrs.core.Response; from a remote service |
| can be seen in <xref linkend="rx.client.rxjava.usage" />. |
| |
| <example xml:id="rx.client.rxjava2.usage"> |
| <title>Obtaining Flowable<Response> from Jersey/RxJava Client</title> |
| |
| <programlisting language="java" linenumbering="numbered">Flowable<Response> observable = RxObservable.newClient() |
| .target("http://example.com/resource") |
| .request() |
| .rx(RxFlowableInvoker.class) |
| .get(); |
| </programlisting> |
| </example> |
| </para> |
| </simplesect> |
| |
| <simplesect> |
| <title>Dependencies</title> |
| |
| <para> |
| The RxJava support is available as an extension module in Jersey. For Maven users, |
| simply add the following dependency to your &lit.pom.xml;: |
| |
| <programlisting language="xml" linenumbering="unnumbered"><dependency> |
| <groupId>org.glassfish.jersey.ext.rx</groupId> |
| <artifactId>jersey-rx-client-rxjava2</artifactId> |
| <version>&version;</version> |
| </dependency></programlisting> |
| |
| After this step you can use the extended client right away. The dependency transitively adds the following |
| dependencies to your class-path as well: <literal>io.reactivex:rxjava2</literal>. |
| </para> |
| |
| <note> |
| <para> |
| If you're not using Maven (or other dependency management tool) make sure to add also all the transitive |
| dependencies of this extension module (see &jersey.ext.rx-client.rxjava2.deps.link;) on the class-path. |
| </para> |
| </note> |
| </simplesect> |
| </section> |
| |
| <section xml:id="rx-client.guava"> |
| <title>Guava – ListenableFuture and Futures</title> |
| <titleabbrev xml:id="rx-client.guava.title">Guava (ListenableFuture and Futures)</titleabbrev> |
| |
| <para> |
| &guava.link;, contributed by Google, also contains a type, &guava.ListenableFuture;, which can be decorated with |
| listeners that are notified when the future completes. The &lit.guava.ListenableFuture; can be combined with |
| &guava.Futures; to achieve asynchronous/event-based completion aware processing. &lit.guava.ListenableFuture; |
| is the parameter type in the Guava's extension of &lit.jaxrs.client.RxInvoker;, called |
| &jersey.ext.rx.client.guava.RxListenableFutureInvoker;. This means that the return type of HTTP method calls is |
| &lit.guava.ListenableFuture; in this case (accordingly parametrized). |
| </para> |
| <para> |
| Requests are by default invoked immediately. If not said otherwise the &jdk8.Executors.newCachedThreadPool; pool |
| is used to obtain a thread which processed the request. This behavior can be overridden by providing a |
| &jdk6.ExecutorService; when a &lit.jaxrs.client.Client; is created. |
| </para> |
| |
| <simplesect> |
| <title>Usage</title> |
| |
| <para> |
| The extensibility is built-in JAX-RS Client API, so there are no special dependencies on Jersey Client |
| API other than the extension itself. |
| |
| <example xml:id="rx.client.guava.rx"> |
| <title>Creating Jersey/Guava Client</title> |
| |
| <programlisting language="java" linenumbering="numbered">// New Client |
| Client client = ClientBuilder.newClient(); |
| client.register(RxListenableFutureInvokerProvider.class);</programlisting> |
| </example> |
| </para> |
| <para> |
| An example of obtaining &lit.guava.ListenableFuture; with JAX-RS &lit.jaxrs.core.Response; from a remote |
| service can be seen in <xref linkend="rx.client.guava.usage" />. |
| |
| <example xml:id="rx.client.guava.usage"> |
| <title>Obtaining ListenableFuture<Response> from Jersey/Guava Client</title> |
| |
| <programlisting language="java" linenumbering="numbered"> |
| ListenableFuture<Response> response = client.target("http://eclipse-ee4j.github.io/jersey") |
| .request() |
| .rx(RxListenableFutureInvoker.class) |
| .get(); |
| </programlisting> |
| </example> |
| </para> |
| </simplesect> |
| |
| <simplesect> |
| <title>Dependencies</title> |
| |
| <para> |
| The Reactive Jersey Client with Guava support is available as an extension module in Jersey. For Maven users, |
| simply add the following dependency to your &lit.pom.xml;: |
| |
| <programlisting language="xml" linenumbering="unnumbered"><dependency> |
| <groupId>org.glassfish.jersey.ext.rx</groupId> |
| <artifactId>jersey-rx-client-guava</artifactId> |
| <version>&version;</version> |
| </dependency></programlisting> |
| |
| After this step you can use the extended client right away. The dependency transitively adds the following |
| dependencies to your class-path as well: <literal>com.google.guava:guava</literal>. |
| </para> |
| |
| <note> |
| <para> |
| If you're not using Maven (or other dependency management tool) make sure to add also all the transitive |
| dependencies of this extension module (see &jersey.ext.rx-client.guava.deps.link;) on the class-path. |
| </para> |
| </note> |
| </simplesect> |
| </section> |
| |
| </section> |
| |
| <section xml:id="rx.client.spi"> |
| <title>Implementing Support for Custom Reactive Libraries (SPI)</title> |
| |
| <para> |
| In case you want to bring support for some other library providing Reactive Programming Model into your application |
| you can extend functionality of Reactive JAX-RS Client by implementing &jaxrs.client.RxInvokerProvider;, registering |
| that implementation into the client runtime and then using rx(Class<T>) in your code. |
| </para> |
| |
| <simplesect> |
| <title>Implement RxInvoker and RxInvokerProvider interfaces</title> |
| |
| <para> |
| The first step when implementing support for another reactive library is to implement &jaxrs.client.RxInvoker;. |
| JAX-RS API itself contains one implementation, which will be used as an example: &jaxrs.client.CompletionStageRxInvoker;. |
| |
| <example xml:id="rx.client.rxinvoker"> |
| <title>Extending RxIvoker</title> |
| |
| <programlisting language="java" linenumbering="numbered">public interface CompletionStageRxInvoker extends RxInvoker<CompletionStage> { |
| @Override |
| public CompletionStage<Response> get(); |
| |
| @Override |
| public <T> CompletionStage<T> get(Class<T> responseType); |
| |
| // ... |
| }</programlisting> |
| </example> |
| </para> |
| <para> |
| The important fact to notice is that the generic parameter of &jaxrs.client.RxInvoker; is &jdk8.CompletionStage; |
| and also that the return type is overriden to be always &jdk8.CompletionStage; with some generic param (&jaxrs.core.Response;; |
| or T). |
| </para> |
| <para> |
| After having the extended RxInvoker interface, the implementor has to provide &jaxrs.client.RxInvokerProvider;, |
| which will be registered as an provider to a client instance. |
| </para> |
| <example xml:id="rx.client.extend.rxinvoker"> |
| <title>Extending RxInvokerProvider</title> |
| |
| <programlisting language="java" linenumbering="numbered">public static class CompletionStageRxInvokerProvider implements RxInvokerProvider<CompletionStageRxInvoker> { |
| @Override |
| public boolean isProviderFor(Class<?> clazz) { |
| return CompletionStage.class.equals(clazz); |
| } |
| |
| @Override |
| public CompletionStageRxInvoker getRxInvoker(SyncInvoker syncInvoker, ExecutorService executorService) { |
| return new CompletionStageRxInvoker() { |
| // ... |
| }; |
| } |
| }</programlisting></example> |
| </simplesect> |
| <simplesect> |
| <title>Example of using custom RxInvokerProvider</title> |
| |
| <para> |
| Considering the work above was done and the implementation of custom &lit.jaxrs.client.RxInvoker; and |
| &lit.jaxrs.client.RxInvokerProvider; is available, the client code using those extensions will be: |
| </para> |
| <programlisting language="java" linenumbering="numbered">Client client = ClientBuilder.newClient(); |
| // register custom RxInvokerProvider |
| client.register(CompletionStageRxInvokerProvider.class); |
| |
| CompletionStage<Response> response = |
| client.target("http://eclipse-ee4j.github.io/jersey") |
| .request() |
| .rx(CompletionStageRxInvoker.class) |
| // Now we have an instance of CompletionStageRxInvoker returned from our registered RxInvokerProvider, |
| // which is CompletionStageRxInvokerProvider in this particular scenario. |
| .get();</programlisting> |
| |
| </simplesect> |
| </section> |
| </chapter> |