| <?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> |