This document provides an overview of reactive programming in Java and Spring 5. It discusses reactive programming concepts like reactive streams specification, Reactor library, and operators. It also covers how to build reactive applications with Spring WebFlux, including creating reactive controllers, routing with functional endpoints, using WebClient for HTTP requests, and testing with WebTestClient.
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
Reactive Programming in Java and Spring 5 with Reactor Core
1. Reactive Programming in Java and Spring 5
Richard Langlois P. Eng., Solutions Architect
January 24th, 2018
2. >
• Introduction to Reactive Programming
• Reactive Manifesto
• Reactive Streams Specification
• Reactor
• Reactive Programming with Spring 5
• Reactive Spring Data
Agenda
3. >
• Is a programming paradigm.
• Is about:
• Non-blocking applications which are
• asynchronous
• even-driven
• require a small amount of threads to scale vertically (within the JVM).
Introduction to Reactive Programming
4. >
From Merriam Webster: “Readily responsive to a stimulus”
• React to events (event-driven)
• React to load (scalable)
• React to failures (resilient)
• React to users (responsive)
Key to understand reactive programming is to think about it as operating on a stream of data.
Steam of data meaning a sequence of events, where an event could be
• user input (like a tap on a button),
• a response from an API request (like a Facebook feed),
• data contained in a database,
• a single variable.
Reactive
5. >
Reactive Manifesto is a prescription for building modern, cloud scale architecture, which is well prepared to meet the increasing
demands that applications face today.
Homepage: http://reactivemanifesto.org/
Reactive Manifesto
6. >
The Four Reactive Traits:
• Responsive: Application provides rich, real-time interaction with its users even under load and in the presence of failures.
• Resilient: Application recover quickly from failure (e.g. software, hardware, connections).
• Scalable: Application is able to be expanded according to its usage
• scale up: make use of more powerful hardware (aka vertical scaling)
• scale out: make use of multiple server nodes (aka horizontal scaling)
• Event-Driven: Application is composed of loosely coupled event handlers. Events can be handled asynchronously, without
blocking.
Reactive Manifesto
Characteristics
7. >
Reactive Streams is a Standard and specification for Stream‐oriented libraries for the JVM that
• process a potentially unbounded number of elements in sequence
• asynchronously passing elements between components
• with mandatory non‐blocking backpressure.
Specification consists of the following parts:
• The API specifies the types to implement Reactive Streams and achieve interoperability between different implementations.
• The Technology Compatibility Kit (TCK): a standard test suite for conformance testing of implementations.
Reactive Streams are only concerned with mediating the stream of data between different API Components (so operators
are not specified).
Reactive Streams specification
8. >
Allows to control the amount of inflight data
Regulate the transfer between:
• Slow publisher and fast consumer
• Fast publisher and slow consumer
Back Pressure (a.k.a. Flow Control)
10. >
Those interfaces are included in Java 9 (under java.util.concurrent):
• Flow.Publisher
• Flow.Subscriber
• Flow.Subscription
• Flow.Processor
Reactive Streams Specification
Java 9
11. >
API Explanation:
• Communication between publisher and subscriber is set up via the publisher’s subscribe(), through which the two parties
are introduced to each other.
• After successful initialization of the communication, the subscriber gets to know about a Subscription (which models the
established connection) via a call to its onSubscribe method.
• At the core of the Reactive Streams mechanism is the request method of the Subscription. Through the request() method,
the subscriber signals to the publisher how many elements it’s ready to process.
• The publisher communicates every element one by one to the subscriber via its onNext() method, as well as fatal stream
failures through the onError() method.
• Because the publisher knows exactly how many items it’s expected to publish at any time (it has been asked for a number of
elements in the Subscription’s request method), it’s able to produce as many elements as required without producing too
many for the subscriber to consume, eliminating the need to block while waiting for the subscriber.
• Additionally, the subscriber is called by the publisher for each published element through the onNext() method, meaning
that it does not explicitly need to block while waiting for new elements to be available.
Reactive Streams Specification
12. >
Some Reactive Libraries on the JVM:
• RxJava: Reactive Extensions implemented on the JVM.
• Akka Streams
• Reactor Core:
Reactive Stream Specification
13. >
Is a fourth-generation Reactive library for building non-blocking applications on the
JVM based on the Reactive Streams Specification.
• Targets Java 8.
• Extends a Reactive Streams Publisher with the Flux and Mono API types.
It adds the concept of operators, which are chained together to describe what processing to apply at
each stage to the data.
Applying an operator returns a new intermediate Publisher.
Reactor
Core 3
15. >
Flux:
• A Flux<T> is a standard Publisher<T> representing an asynchronous sequence of 0 to N emitted
items, optionally terminated by either a completion signal or an error.
• Possible values of a flux: a value, a completion signal, an error.
• Translates to calls to a downstream object’s onNext, onComplete, onError methods.
• Marble diagram:
Reactor
Core Features
16. >
Mono:
• A Mono is a standard Publisher<T> that emits at most one item and then optionally terminates with
an onComplete signal or an onError signal.
• It offers only a subset of the operators that are available for a Flux.
• Can represent no-value asynchronous processes which have the concept of completion
(Mono<void>).
Reactor
Core Features
17. >
Numerous factory methods:
• Flux<String> seq1 = Flux.just("foo", "bar", "foobar");
• private static List<String> iterable = Arrays.asList ("foo", "bar", "foobar");
Flux<String> fluxFromIterable = Flux.fromIterable(iterable);
• Mono<String> noData = Mono.empty();
• Mono<String> data = Mono.just("foo");
• Flux<Integer> numbersFromFiveToSeven = Flux.range(5, 3); // 1st parameter is start of the range, 2nd is number of items
Reactor
Flux or Mono Creation
18. >
.subscribe variants take lambdas for different combination of callbacks:
1. subscribe(); // subscribe and trigger the sequence
2. subscribe(Consumer<? super T> consumer); // consumer run when values
3. subscribe(Consumer<? super T> consumer, // deal with values
Consumer<? super Throwable> errorConsumer); // consumer run when error
4. subscribe(Consumer<? super T> consumer, // deal with values
Consumer<? super Throwable> errorConsumer, // Consumer run when error
Runnable completeConsumer); // consumer run when sequence successfully complete
5. subscribe(Consumer<? super T> consumer, // deal with values
Consumer<? super Throwable> errorConsumer, // consumer run when error
Runnable completeConsumer, // consumer run when the sequence successfully complete
Consumer<? super Subscription> subscriptionConsumer); // consumer run when subscription produced
Reactor - Subscribe
19. >
Example:
// Use the subscribe() method to collect all the elements in a stream
Flux<Integer> elementsFlux = Flux.just(1, 2, 3, 4);
// subscribe with a subscriber that will print the values, The data will not start flowing until we subscribe
elementsFlux().subscribe(System.out::println(i));
Logs:
[main] INFO reactor.Flux.Array.1 - | onSubscribe([Synchronous Fuseable] FluxArray.ArraySubscription)
20:25:19.553 [main] INFO reactor.Flux.Array.1 - | request(unbounded)
20:25:19.553 [main] INFO reactor.Flux.Array.1 - | onNext(1)
20:25:19.553 [main] INFO reactor.Flux.Array.1 - | onNext(2)
20:25:19.553 [main] INFO reactor.Flux.Array.1 - | onNext(3)
20:25:19.553 [main] INFO reactor.Flux.Array.1 - | onNext(4)
20:25:19.553 [main] INFO reactor.Flux.Array.1 - | onComplete()
Reactor - Subscribe
20. >
Example: Tell upstream to only send 2 elements at a time by using request().
Flux.just(1, 2, 3, 4)
.log()
.subscribe(new Subscriber<Integer>() {
private Subscription s;
int onNextAmount;
@Override
public void onSubscribe(Subscription s) {
this.s = s;
s.request(2);
}
…
Reactor – Back Pressure
Example (1 of 3)
21. >
…
@Override
public void onNext(Integer integer) {
elements.add(integer);
onNextAmount++;
if (onNextAmount % 2 == 0) {
s.request(2);
}
}
@Override
public void onError(Throwable t) {}
@Override
public void onComplete() {}
});
Reactor – Back Pressure
Example (2 of 3)
22. >
Logs:
23:31:15.395 [main] INFO reactor.Flux.Array.1 - | onSubscribe([Synchronous Fuseable] FluxArray.ArraySubscription)
23:31:15.397 [main] INFO reactor.Flux.Array.1 - | request(2)
23:31:15.397 [main] INFO reactor.Flux.Array.1 - | onNext(1)
23:31:15.398 [main] INFO reactor.Flux.Array.1 - | onNext(2)
23:31:15.398 [main] INFO reactor.Flux.Array.1 - | request(2)
23:31:15.398 [main] INFO reactor.Flux.Array.1 - | onNext(3)
23:31:15.398 [main] INFO reactor.Flux.Array.1 - | onNext(4)
23:31:15.398 [main] INFO reactor.Flux.Array.1 - | request(2)
23:31:15.398 [main] INFO reactor.Flux.Array.1 - | onComplete()
Notice:
The request(2) is called (when Subscribing), followed by two onNext() calls, then request(2) again, then on Complete().
Reactor – Back Pressure
Example (3 of 3)
23. >
Reactive Operators applies a wide range of transformations to the sequence of data (e.g. Map, Flatmap, Filter, Concat, Merge,
buffer, split, transform, delay …)
Special diagrams called Marble Diagrams efficiently explains what an operator does, for example:
Reactor - Reactive Operators
24. >
Map: Transform every item of the emitted sequence with a specified function.
Reactor – Reactive Operators
Map()
25. >
Concat:
concatenates 2 or more emissions, generating one emission where all the items from the 1st source emission appear before the
items of the 2nd source emission.
Reactor - Reactive Operators
Concat()
26. >
flatMap():
performs 2 types of actions:
1. the “map” action that transforms the emitted item into Flux
2. The “flatten” action that converts those Flux into one Flux.
Reactor - Reactive Operators
flatMap
27. >
Merge:
When we want to get feeds from multiple sources as one stream.
Reactor - Reactive Operators
merge()
28. >
Zip:
takes multiple observables as inputs and combines each emission via a specified function
and emits the results of this function as a new sequence.
Reactor - Reactive Operators
zip()
29. >
StepVerifier API:
• Builder API allowing to test how a Flux or Mono behave when subscribed to.
Some methods:
• expectNext()
• expectError()
• expectErrorMessage()
• expectComplete()
• Verify(), VerifyComplete(), VerifyError(), VerifyErrorMessage()
Reactor - Testing
StepVerifier
30. >
Example:
// empty Flux
Flux<String> emptyFlux = Flux.empty();
StepVerifier.create(emptyFlux).verifyComplete();
// non-empty Flux
Flux<String> nonEmptyFlux = Flux.just("John", "Mike", "Sarah");
StepVerifier.create(nonEmptyFlux).expectNext("John", "Mike", "Sarah").verify();
Call to verify():
• triggers the verification by subscribing to the Flux/Mono under test.
• Plays the sequence, comparing each new signal with the next step in the scenario
• As long as these match, the test is considered a success, else, an AssertionError is thrown.
Reactor - Testing
StepVerifier
31. >
• New spring-webflux module to support reactive HTTP client-side and server-side.
• Uses Reactor internally for its own reactive support.
Spring Framework 5
Reactive Support
33. >
Add Dependency to pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Spring 5
WebFlux
34. >
• Supports 2 distinct programming models:
• Annotation-based with @Controller and the other annotations of Spring MVC
• Functional Style routing and handling with Java 8 lambda.
Spring 5
Server Side
36. >
Get all customers test case:
Map<Long, Customer> custStores = new HashMap<Long, Customer>();
…
@GetMapping
public Flux<Customer> getAll() {
return Flux.fromIterable(custStores.entrySet().stream()
.map(entry -> entry.getValue())
.collect(Collectors.toList()));
}
Spring 5 - WebFlux
Server Side - Annotation-based - Example
37. >
Get a customer test case:
@GetMapping("/{id}")
public Mono<Customer> getCustomer(@PathVariable Long id) {
return Mono.justOrEmpty(custStores.get(id));
}
Spring 5 - WebFlux
Server Side - Annotation-based - Example
38. >
Create a customer test case:
@PostMapping("/post")
public Mono<ResponseEntity<String>> postCustomer(@RequestBody Customer customer) {
// do post
custStores.put(customer.getCustId(), customer);
return Mono.just(new ResponseEntity<>("Post Successfully!", HttpStatus.CREATED));
}
Spring 5 - WebFlux
Server Side - Annotation-based - Example
39. >
Framework introduces 2 fundamental components:
• Routes a given HTTP requests via a RouterFunction (alternative to using annotations like @RequestMapping)
• Handles the request via HandlerFunction (alternative to @Controller’s handler methods).
RouterFunction utility classes are used to create RouterFunction:
e.g.
RouterFunction.route(RequestPredicate, HandlerFunction):
• helper function to create routes.
• Allows to route requests by applying a RequestPredicate.
• When the predicate is matched, then the second argument is run.
Spring 5 - WebFlux
Server Side - Functional Style
40. >
Example:
RouterFunction router = route( GET("/test"),
request -> ok().build() ) ;
• GET("/test") is the RequestPredicate.
• req -> ok().build() is the handler producing the response.
Spring 5
Server Side – Functional Style
41. >
WebClient
• Is a reactive client for performing HTTP requests with Reactive Streams back
pressure.
• Is replacing the classic RestTemplate.
• Is an interface which has a single implementation – DefaultWebClient class.
Usage(similar to HttpClient):
1. Create an instance
2. Make a request
3. Handle the reponse
Spring 5
Client side
42. >
Create WebClient using one of the 3 options :
• With default settings:
• WebClient client1 = WebClient.create();
• With a given base URI:
• WebClient client2 = WebClient.create("http://localhost:8080 (http://localhost:8080)");
• Using the DefaultWebClientBuilder class:
• WebClient client3 = WebClient
.builder()
.baseUrl("http://localhost:8080 (http://localhost:8080)")
.defaultCookie("cookieKey", "cookieValue")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080 (http://localhost:8080)"))
.build();
Spring 5
Client side - WebClient
43. >
// Setting a request body
//where a Publisher is a reactive component that is in charge of providing a potentially unbounded number of sequenced
elements.
WebClient.RequestHeadersSpec requestSpec1
= WebClient.create()
.method(HttpMethod.POST)
.uri("/resource")
.body(BodyInserters.fromPublisher(Mono.just("data")), String.class);
WebClient.RequestHeadersSpec<?> requestSpec2
= WebClient.create("http://localhost:8080 (http://localhost:8080)")
.post()
.uri(URI.create("/resource"))
.body(BodyInserters.fromObject("data"));
Spring 5
Client side - WebClient
44. >
A Response is obtained with exchange() or retrieve():
• exchange(): provides a ClientResponse along with its status, headers
• retrieve(): fetches a body directly
Example:
String response2 = request1.exchange()
.block()
.bodyToMono(String.class)
.block();
String response3 = request2.retrieve()
.bodyToMono(String.class)
.block();
Note: The block method on Monos is used to subscribe and retrieve an actual data which was sent with the response.
Spring 5
Client side - WebClient
45. >
WebTestClient
• is a non-blocking, reactive client for Spring-webflux integration testing support.
• Offers an identical API as the WebClient.
• can connect to any server over an HTTP connection.
• provides a fluent API to verify responses.
• can also bind directly to WebFlux applications with mock request and response objects.
Spring 5
Spring WebFlux Testing
47. >
@WebFluxTest:
• auto-configures WebTestClient to quickly test WebFlux controllers without starting
a full HTTP server
Example of an unit test:
@RunWith(SpringRunner.class)
@WebFluxTest
public class SpringWebFluxTestApplicationTests {
@Autowired
private WebTestClient webTestClient;
…
}
Spring 5 - WebFlux
Testing
48. >
Test without starting an actual server:
WebTestClient client
= WebTestClient.bindToRouterFunction(routingFunction())
.build();
client.get().uri("/").exchange()
.expectStatus().isOk(); // Assert
Test with a real server:
WebTestClient client
= WebTestClient.bindToServer()
.baseUrl("http://localhost:8080 (http://localhost:8080)")
.build();
client.get().uri("/").exchange()
.expectStatus().isOk(); // Assert
Spring 5 - WebFlux
WebTestClient
49. >
Get test case:
Using WebTestClient API:
webTestClient.get().uri("/api/customer/{id}", 2).accept(MediaType.APPLICATION_JSON).exchange()
.expectStatus().isOk().expectBody(Customer.class).isEqualTo(customerMap.get("Peter")); // Assert
Using RouterFunction:
// The Router Function
RouterFunction myRouterFunction = route(GET("/test"), // RouterFunction
request -> ok().body(fromObject("hello world"))); // HandlerFunction
// Register the RouterFunction
WebTestClient webTestClient = WebTestClient.bindToRouterFunction(myRouterFunction).build();
// Assert the response
webTestClient.get().uri("/test").exchange().expectStatus().isOk();
Spring 5
WebTestClient - Example
50. >
POST test case:
webTestClient
// Create a POST request
.post()
.uri("/api/customer/post")
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(customerMap.get("Mary")))
.exchange()
// Assert the response
.expectStatus().isCreated()
.expectBody(String.class)
.isEqualTo("Post Successfully!");
Spring 5
WebTestClient - Example
51. >
PUT test case:
webTestClient
// Create a PUT request
.put().uri("/api/customer/put/{id}", 3)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(customerMap.get("Amy")))
.exchange()
// Assert the response
.expectStatus().isCreated()
.expectBody(Customer.class)
isEqualTo(customerMap.get("Amy"));
Spring 5
WebTestClient - Example
52. >
DELETE test case:
webTestClient
// Create a DELETE request
.delete().uri("/api/customer/delete/{id}", 1)
.exchange()
// Assert the response
.expectStatus().isAccepted()
.expectBody(String.class)
.isEqualTo("Delete Succesfully!");
Spring 5
WebTestClient - Example
53. >
Spring WebFlux is supported on the following servers:
• Netty
• Undertow
• Tomcat
• Jetty
• Servlet 3.1+ containers
Spring Boot 2 uses Netty by default with WebFlux
Spring 5
Choosing a server
55. >
Spring Data support Reactive Streams
Reactive Spring Data Modules:
• MongoDb
• Redis
• Cassandra
• CouchDB
Reactive Spring Data
56. >
Lets see an example based on Spring Data and MongoDB
Spring Data - MongoDB
57. >
Getting up a running Reactive Streams with Spring Data and MongoDB:
• Add the dependency (in pom.xml)
• Configuration (@EnableReactiveMongoRepositories)
• Repository interface (extends ReactiveMongoRepository, and use Flux and Mono
types)
Spring Data - MongoDB
58. >
Add Reactive MongoDB to our pom.xml:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-libs-snapshot</id>
<name>Spring Snapshot Repository</name>
<url>http://repo.spring.io/libs-snapshot</url>
</repository>
</repositories>
Spring Data - MongoDB
Example
59. >
@Configuration
@EnableReactiveMongoRepositories(basePackageClasses = PersonRepository.class)
public class MongoConfig extends AbstractReactiveMongoConfiguration {
@Bean
public MongoClient mongoClient() {
return MongoClients.create();
}
@Override
protected String getDatabaseName() {
return "test";
}
@Bean
public ReactiveMongoTemplate reactiveMongoTemplate() {
return new ReactiveMongoTemplate(mongoClient(), getDatabaseName());
}
}
Spring Data – MongoDB
Example - Configuration
60. >
Configuration:
@EnableMongoRepositories enable the use of ReactiveCrudRepository.
For example:
• Mono<S> = save(S entity)
• Mono<T> = findById(ID id)
• Mono<T> = findById(Publisher<ID> id)
• Uses the first emitted element to perform the find-query
• Mono<void> = deleteById(ID id)
• Mono signaling when operation is completed
Spring Data – MongoDB
Example
61. >
Repository:
@EnableMongoRepositories enable the use of ReactiveCrudRepository.
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
Flux<Person> findByFirstName(final String firstName);
Mono<Person> findOneByFirstName(final String firstName);
Mono<void> = deleteById(ID id); //Mono signaling when operation is completed
}
Notice:
• Flux<Person> replaces the use of List
• Mono<Person> is used instead of Person.
This allows to perform functions on each element as they come from MongoDB database, instead of waiting until
all records are returned, and then do something with them.
Spring Data – MongoD
Example
62. >
Application:
@SpringBootApplication
public class Application implements CommandLineRunner {
…
@Override
public void run(String args[]) {
final Person johnAoe = new Person("john", "aoe", LocalDateTime.now(), "loser", 0);
final Person johnBoe = new Person("john", "boe", LocalDateTime.now(), "a bit of a lose
personRepository.saveAll(Flux.just(johnAoe, johnBoe)).subscribe();
personRepository.findByFirstName("john").log().map(Person::getSecondName)
.subscribe(System.out::println);
personRepository.findOneByFirstName("john").log().map(Person::getId)
.subscribe(System.out::println);
}
}
Spring Data – MongoD
Example
63. >
Associated Logs:
2017-07-16 16:44:09.201 INFO 13476 --- [ main] reactor.Flux.OnErrorResume.1 : onSubscribe(FluxOnErrorResume.ResumeSubscriber)
2017-07-16 16:44:09.208 INFO 13476 --- [ main] reactor.Flux.OnErrorResume.1 : request(unbounded)
2017-07-16 16:44:09.242 INFO 13476 --- [ Thread-4] reactor.Flux.OnErrorResume.1 : onNext(Person(firstName=john, secondName=aoe, profession=loser, salary=0))
aoe
2017-07-16 16:44:09.243 INFO 13476 --- [ Thread-4] reactor.Flux.OnErrorResume.1 : onNext(Person(firstName=john, secondName=boe, profession=a bit of a loser,
salary=10))
boe
2017-07-16 16:44:09.247 INFO 13476 --- [ Thread-4] reactor.Flux.OnErrorResume.1 : onComplete()
2017-07-16 16:44:09.254 INFO 13476 --- [ main] reactor.Mono.OnErrorResume.2 : onSubscribe(FluxOnErrorResume.ResumeSubscriber)
2017-07-16 16:44:09.255 INFO 13476 --- [ main] reactor.Mono.OnErrorResume.2 : request(unbounded)
2017-07-16 16:44:09.260 INFO 13476 --- [ Thread-4] reactor.Mono.OnErrorResume.2 : onNext(Person(firstName=john, secondName=aoe, profession=loser, salary=0))
596b89c97ab38934a404a80c
2017-07-16 16:44:09.260 INFO 13476 --- [ Thread-4] reactor.Mono.OnErrorResume.2 : onComplete()
Spring Data – MongoD
Example
64. >
• The Reactive Manifesto:
• https://www.reactivemanifesto.org/
• Reactor:
• https://projectreactor.io/
• Spring WebFlux:
• https://docs.spring.io/spring/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/html/web-reactive.html
• Going reactive with Spring Data:
• https://spring.io/blog/2016/11/28/going-reactive-with-spring-data
• Servlet vs Reactive Stacks in Five Use Cases:
• https://www.infoq.com/presentations/servlet-reactive-stack
References