When JAX-RS 2.1 specification was released back in 2017, one of its true novelties was the introduction of the reactive API extensions. The industry has acknowledged the importance of the modern programming paradigms and specification essentially mandated the first-class support of the asynchronous and reactive programming for the Client API.
But what about the server side? It was not left out, the JAX-RS 2.1 asynchronous processing model has been enriched with Java 8's CompletionStage support, certainly a step in a right direction. Any existing REST web APIs built on top of the JAX-RS 2.1 implementation (like Apache CXF for example) could benefit from such enhancements right away.
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @Service @Path("/people") public class PeopleRestService { @GET @Produces(MediaType.APPLICATION_JSON) public CompletionStage<List<Person>> getPeople() { return CompletableFuture .supplyAsync(() -> Arrays.asList(new Person("a@b.com", "Tom", "Knocker"))); } }
Udoubtedly, CompletionStage and CompletableFuture are powerful tools but not without own quirks and limitations. The Reactive Streams specification and a number of its implementations offer a considerably better glimpse on how asynchronous and reactive programming should look like on JVM. With that, the logical question pops up: could your JAX-RS web services and APIs take advantage of the modern reactive libraries? And if the answer is positive, what does it take?
If your bets are on Apache CXF, you are certainly well positioned. The latest Apache CXF 3.2.14 / 3.3.7 / 3.4.0 release trains bring a comprehesive support of RxJava3, RxJava2 and Project Reactor. Along this post we are going to see how easy it is to plug your favorite reactive library in and place it at the forefront of your REST web APIs and services.
Since the most applications and services on the JVM are built on top of excellent Spring framework and Spring Boot, we will be developing the reference implementations using those as a foundation. The Spring Boot starter which comes along with Apache CXF distribution is taking care of most of the boring wirings you would have needed to do otherwise.
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-spring-boot-starter-jaxrs</artifactId> <version>3.4.0</version> </dependency>
The Project Reactor is the number one choice as the reactive foundation for Spring-based applications and services, so let us just start from that.
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-rs-extension-reactor</artifactId> <version>3.4.0</version> </dependency>
Great, believe it or not, we are mostly done here. In order to teach Apache CXF to understand Project Reactor types like Mono or/and Flux we need to tune the configuration just a bit using ReactorCustomizer instance.
import org.apache.cxf.Bus; import org.apache.cxf.endpoint.Server; import org.apache.cxf.jaxrs.JAXRSServerFactoryBean; import org.apache.cxf.jaxrs.reactor.server.ReactorCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; @Configuration public class AppConfig { @Bean public Server rsServer(Bus bus, PeopleRestService service) { final JAXRSServerFactoryBean bean = new JAXRSServerFactoryBean(); bean.getProperties(true).put("useStreamingSubscriber", true); bean.setBus(bus); bean.setAddress("/"); bean.setServiceBean(service); bean.setProvider(new JacksonJsonProvider()); new ReactorCustomizer().customize(bean); return bean.create(); } }
With such customization in-place, our JAX-RS web services and APIs could freely utilize Project Reactor primitives in a streaming fashion, for example.
import reactor.core.publisher.Flux; @Service @Path("/people") public class PeopleRestService { @GET @Produces(MediaType.APPLICATION_JSON) public Flux<Person> getPeople() { return Flux.just(new Person("a@b.com", "Tom", "Knocker")); } }
As you probably noticed, the implementation purposely does not do anything complicated. However, once the reactive types are put at work, you could unleash the full power of the library of your choice (and Project Reactor is really good at that).
Now, when you undestand the principle, it comes the turn of the RxJava3, the last generation of the pioneering reactive library for the JVM platform.
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-rs-extension-rx3</artifactId> <version>3.4.0</version> </dependency>
The configuration tuning is mostly identical to the one we have seen with Project Reactor, the customizer instance, ReactiveIOCustomizer, is all that changes.
import org.apache.cxf.Bus; import org.apache.cxf.endpoint.Server; import org.apache.cxf.jaxrs.JAXRSServerFactoryBean; import org.apache.cxf.jaxrs.rx3.server.ReactiveIOCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; @Configuration public class AppConfig { @Bean public Server rsServer(Bus bus, PeopleRestService service) { final JAXRSServerFactoryBean bean = new JAXRSServerFactoryBean(); bean.getProperties(true).put("useStreamingSubscriber", true); bean.setBus(bus); bean.setAddress("/"); bean.setServiceBean(service); bean.setProvider(new JacksonJsonProvider()); new ReactiveIOCustomizer().customize(bean); return bean.create(); } }
The list of supported types includes Flowable, Single and Observable, the equivalent implementation in terms of RxJava3 primitives may look like this.
import io.reactivex.rxjava3.core.Flowable; @Service @Path("/people") public class PeopleRestService { @GET @Produces(MediaType.APPLICATION_JSON) public Flowable<Person> getPeople() { return Flowable.just(new Person("a@b.com", "Tom", "Knocker")); } }
Pretty simple, isn't it? If you stuck with an older generation, RxJava2, nothing to worry about, Apache CXF has you covered.
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-rs-extension-rx2</artifactId> <version>3.4.0</version> </dependency>
The same configuration trick with applying the customizer (which may look annoying at this point to be fair) is all that is required.
import org.apache.cxf.Bus; import org.apache.cxf.endpoint.Server; import org.apache.cxf.jaxrs.JAXRSServerFactoryBean; import org.apache.cxf.jaxrs.rx2.server.ReactiveIOCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; @Configuration public class AppConfig { @Bean public Server rsServer(Bus bus, PeopleRestService service) { final JAXRSServerFactoryBean bean = new JAXRSServerFactoryBean(); bean.getProperties(true).put("useStreamingSubscriber", true); bean.setBus(bus); bean.setAddress("/"); bean.setServiceBean(service); bean.setProvider(new JacksonJsonProvider()); new ReactiveIOCustomizer().customize(bean); return bean.create(); } }
And we are good to go, ready to use the familiar reactive types Observable, Flowable and Single.
import io.reactivex.Flowable; import io.reactivex.Observable; @Service @Path("/people") public class PeopleRestService { @GET @Produces(MediaType.APPLICATION_JSON) public Observable<Person> getPeople() { return Flowable .just(new Person("a@b.com", "Tom", "Knocker")) .toObservable(); } }
Last but not least, if you happens to be using the first generation of RxJava, it is also available with Apache CXF but certainly not recommended for production (as it has EOLed a couple of years ago).
Reactive programming paradigm is steadily getting more and more traction. It is great to see that the ecosystem embraces that and frameworks like Apache CXF are not an exception. If you are looking for robust foundation to build reactive and/or asynchronous REST web APIs on JVM, Apache CXF is worth considering, please give it a try!
The complete source code is available on Github.
No comments:
Post a Comment