Académique Documents
Professionnel Documents
Culture Documents
and MongoDB
Moisés Macero
This book is for sale at http://leanpub.com/full-reactive
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.
Chapter 4: Conclusions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
WebFlux vs. Blocking (MVC): user experience . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
WebFlux vs. Blocking (MVC): performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
WebFlux vs. Blocking (MVC): ease of development . . . . . . . . . . . . . . . . . . . . . . . . 44
Suitability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Conclusions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
CONTENTS
1. The Reactive Web. This chapter guides you through the main concepts of a reactive approach
and compares it to the classic web interfaces.
2. The backend. Covers the creation of the Spring Boot application using the required depen-
dencies and the development of the backend layers, paying special attention to the Controllers
with WebFlux and the MongoDB reactive driver.
3. The frontend. Goes through the development of an Angular application that consumes the
reactive endpoint created on the server’s side.
4. Conclusions. This part completes the guide, summarizing what you’ve built and comparing the
Reactive Web approach with the Classic Web approach. It gives some insights on performance,
testability, etc.
5. Run the app with Docker. This appendix helps you build and run the full stack application in
Docker.
All the source code (spring-boot, Angular, Docker) is available on GitHub: Full-Reactive Stack
repository¹. If you find it useful, please give it a star!
The goal of this Guide is that you can learn Reactive Web development by building an application
from scratch (or navigating through the code if you prefer that). The chosen technologies are:
• The Servlet 3.0 specification introduced asynchronous support. That means you can optimize
the usage of container threads using Callable and Spring’s DeferredResult as a response from
controllers. However, from the client’s perspective, they’re still blocking calls. Besides, the
imperative way of using the API is far from being developer-friendly, so they are seldom used.
¹https://github.com/mechero/full-reactive-stack
Chapter 1: The Reactive Web 4
• From the web client’s point of view, you can rely on asynchronous patterns as well. This is
the most popular way of performing a request to the server, using promises. Clients usually
perform requests and wait for the response in different threads so the main flow doesn’t block.
The response arrives in the background and then a callback function processes the data and
has the ability to, for example, update the HTML’s DOM. Again, even being asynchronous,
the additional thread is also blocked, waiting for a response that might take a long time to
complete and receiving all the data at once.
In a Reactive Web approach, threads don’t block until all the data is available. Instead, every layer
is capable of providing data as soon as they process it. Therefore, web clients can start doing their
part sooner, so we improve the user’s experience. It’s also more efficient since the different layers
can process information in a continuous stream instead of being idle until a whole chunk of data
comes.
that switching only one of these two sides of the communication wouldn’t fix this situation: both
would need to use a different approach.
Something similar to the slow connection issue would happen if the server is busy. Let’s imagine
that many users are searching for products on that online store at the same time, and the database’s
capacity is not ready for that. The database becomes slower, and the queries take a few seconds to
fetch 50 results. Now, the bottleneck in the client-server interaction is not the web interface but the
database connection. However, the result is the same. The user has to wait until the query is fully
completed to get the list of products. A better outcome could be achieved if the database connection,
instead of returning query results at once (blocking), would open streams with the database clients
(another backend layer) and return results as it’s finding them.
With a reactive web interface, the user would see three items on the screen per second, instead
of all at once after fifteen seconds. That’s a first non-blocking advantage: faster availability of the
data, which in our example means better user experience. We as developers should aim for that
and move away from blocking calls. According to a study from Google⁴, up to 53% of mobile users
abandon a site if it takes more than 3 seconds to load.
In a full-reactive stack scenario, the database retrieves results as soon as they’re available. Same for
the Web Interface (let’s say HTTP). The Business logic layer should be prepared to process items
in a reactive way too, e.g. by using reactive libraries. Finally, to close the loop and benefit from a
full-reactive stack, the client’s side should be able to process the data as soon as it arrives, following
a subscriber pattern.
There is an extra advantage that reactive web interfaces share with other asynchronous approaches:
a better thread usage. In a non-blocking system, you don’t have threads waiting to be completed.
Instead, the server parks the client thread quickly so there are fewer server threads occupied. Web
clients will be notified when new data becomes available, following a Publish-Subscribe pattern. This
advantage is very relevant if you have a server application that may perform slowly sometimes, thus
accumulating a lot of blocked threads and eventually not being able to process more.
(RxJS, RxJava, etc.), Akka⁷, and Project Reactor⁸ (the one used by WebFlux). Java 9 also introduced
the reactive streams specification with the Flow API⁹ so the implementors can choose the Java API
standards for their libraries. However, that hasn’t worked so far and most of the reactive libraries still
use their custom classes. In any case, all these frameworks implement (with some slight differences
in naming) patterns like Observables, Publishers, and Subscribers.
Additionally, these reactive frameworks adopt the backpressure concept. The main idea of backpres-
sure is that the subscriber has the control of the stream, so the consumer can signal the publisher
to stop producing data instead of having to accumulate it in buffers. Project Reactor implements
backpressure, as we’ll see and demonstrate later in this guide.
The application
To showcase the reactive capabilities, you’ll create a client web application that receives quotes from
the book Don Quixote (yes, you can tell I’m Spanish). Instead of just asking for the quotes using a
standard blocking call, you’ll open a Server-Sent Events channel¹⁰. If you know about WebSockets,
you can see this as a similar technology where the communication is unidirectional (server to
client). The backend application will send these events (quotes) once there is a subscriber (our client
application). This part is supported by WebFlux returning a Flux<Quote> object (don’t worry for now
about the terms, we’ll cover that). On the Angular app’s end, you’ll model the connection using an
EventSource object, mapped to an RxJS’s Observable<Queue>.
The Angular component subscribes to the Observable, adds the new element to an array, and re-
renders the UI.
While we build the Full Reactive Stack, we’ll create a classic blocking approach too. We’ll use it to
compare both strategies from the implementation and runtime perspectives.
We’ll dive into the implementation details in the rest of the chapters of this guide.
⁷https://akka.io/
⁸https://projectreactor.io/
⁹https://community.oracle.com/docs/DOC-1006738
¹⁰https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
Chapter 2: Full Reactive Stack
Backend
Within this chapter, you’ll learn how to develop the Reactive Web Server. We’ll use Spring WebFlux,
available since Spring 5, and included from the Spring Boot 2.0 version. We’ll connect to a Mongo
database using its reactive driver with Spring Data, also via Spring Boot.
We’ll include two different web approaches within the same backend application:
Before diving into the implementation details, we’ll introduce some topics. This will help those not
familiar with reactive programming. If you already have experience with these concepts, feel free
to jump directly into the code explanation.
All the source code (Spring Boot, Angular, Docker) is available on GitHub: Full-Reactive
Stack repository¹¹. If you find it useful, please star it!
Overview
Project Reactor is a framework built by Pivotal and powered by Spring. It implements Reactive
Programming patterns and, more specifically, the Reactive Streams specification.
If you’re familiar with Java 8 Streams you’ll quickly find many similarities between a Stream and a
Flux (or its single-element version, Mono). The main characteristics that make Fluxes and Monos
¹¹https://github.com/mechero/full-reactive-stack
Chapter 2: Full Reactive Stack Backend 8
different from the Stream API are that the first two follow a Publisher-Subscriber pattern and
implement backpressure.
For instance, if you declare a Flux that takes elements from a database, maps them applying an
operation, and filters them according to some random criteria, then nothing happens. It will do
all these operations only when a subscriber subscribes to the Flux. Furthermore, the items flow
through the processing logic only when the subscriber is ready to consume more elements. This
is the backpressure concept we mentioned earlier in this guide.
As a direct conclusion of this very brief summary, the main advantage of using Reactor is that you’re
in total control of the data flow: you can rely on the subscriber’s ability to ask for more information
when it’s ready to process it, or buffer some results on the publisher’s side, or even use a full push-
approach without backpressure.
To deep dive into Reactor’s knowledge, the Project Reactor¹² official docs are a very good place to
start. Anyway, we’ll cover some of its features in the next sections.
Reactor Integrations
Spring is including Reactor in some of their popular Spring modules, thus enforcing reactive
programming patterns when we use them. Following a smart approach, they’re not getting rid of
the previous programming style in the majority of these modules. That means we are not forced to
adopt reactive programming.
One of the most popular modules where Spring is leveraging Reactor is, as you may guess, the Web
framework. Starting with the Spring 5 you can use WebFlux, which comes with major updates like
a new way of declaring the controller routes, and transparent support for Server-Sent Events using
the Reactor API.
Spring Data has also embraced Reactive Patterns through its Reactive module, with the inclusion
of the ReactiveCrudRepository. That means we can perform reactive queries to databases like
MongoDB, which already have a reactive version of its driver. Then, the database driver pushes
data as a flow controlled by the subscribers, instead of pushing the query results all at once.
¹²http://projectreactor.io/docs/core/release/reference/
¹³http://projectreactor.io/docs/core/release/reference/#flux
¹⁴http://projectreactor.io/docs/core/release/reference/#reactive.backpressure
¹⁵http://projectreactor.io/docs/core/release/reference/#mono
Chapter 2: Full Reactive Stack Backend 9
In this guide, we’ll use the classic, simple way of declaring routes and controllers: declarative
annotations. The main reason is that I don’t want to distract you with multiples changes at once
but also, to be honest, I’m not yet convinced about the convenience of that new style of declaring
routes (feel free to judge for yourself¹⁸).
The dotted orange frame in the figure above represents the stack we’ll use: declarative annotations
with WebFlux and reactive streams.
¹⁶https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-fn-router-functions
¹⁷https://docs.spring.io/spring-framework/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/html/web-reactive.html#web-
reactive-server
¹⁸https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-fn-router-functions
Chapter 2: Full Reactive Stack Backend 10
WebClient
WebFlux comes with a reactive web client too: WebClient. You can use it to perform requests to
controllers that use reactive streams and benefit from backpressure as well. It’s a kind of reactive
and fluent version of the well-known RestTemplate.
As described in the first part of this guide, we won’t use WebClient. That would be only useful
for backend-to-backend communication and it’s straightforward to use. Instead, we’ll create a real
frontend with Angular to consume the reactive API. We’ll detail the differences of a frontend client
when compared to a WebFlux’s WebClient.
Spring Boot integrates the WebFlux capabilities so let’s use its power to set up our backend. The first
change we apply is already in our dependency management thanks to the Spring Boot Initializr: we
don’t include the classic web starter but the WebFlux one (spring-boot-starter-webflux). This starter
contains the Reactor Netty server library dependencies, so Spring Boot will know that’s the one to
start at runtime. We’ll also have the Reactor API available during development.
Chapter 2: Full Reactive Stack Backend 12
44 <groupId>org.springframework.boot</groupId>
45 <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
46 </dependency>
47 <dependency>
48 <groupId>org.springframework.boot</groupId>
49 <artifactId>spring-boot-starter-test</artifactId>
50 <scope>test</scope>
51 </dependency>
52 <dependency>
53 <groupId>io.projectreactor</groupId>
54 <artifactId>reactor-test</artifactId>
55 <scope>test</scope>
56 </dependency>
57 </dependencies>
58
59 <build>
60 <plugins>
61 <plugin>
62 <groupId>org.springframework.boot</groupId>
63 <artifactId>spring-boot-maven-plugin</artifactId>
64 </plugin>
65 </plugins>
66 </build>
67
68 </project>
The Reactive MongoDB dependencies are included within the starter spring-boot-starter-data-
mongodb-reactive. This one pulls the Spring Data MongoDB dependency, containing Spring Data
Commons with reactive support, and the asynchronous and reactive-streams versions of the
MongoDB driver. We also need the classic MongoDB starter to be able to compare both alternatives.
We have duplicated the Repository and the Controller elements as our main logic. The Quote
is our domain class. We intend to expose a list of Quotes using a Reactive Web API – with
QuoteReactiveController and QuoteMongoReactiveRepository -, and compare it with the classic
blocking REST API – QuoteBlockingController and QuoteMongoBlockingRepository. The CorsFilter
configuration class enables cross-origin requests to simplify the connection from the frontend, and
we create the data loader (QuijoteDataLoader) to put the quotes in the Mongo database if they’re
not there yet.
We’ll implement two endpoints per controller: one to retrieve all the quotes available in the
repository and another one that supports pagination. This is useful for us to compare both approaches
in a more realistic scenario, in which pagination is usually required.
Chapter 2: Full Reactive Stack Backend 15
Repository Layer
Let’s start from the source of our Reactive Data Stream: MongoDB. We need to create a class that
represents the domain object, Quote, and a Spring Data Repository to collect and map this data to
Java objects.
1 package com.thepracticaldeveloper.reactiveweb.domain;
2
3 public final class Quote {
4
5 private String id;
6 private String book;
7 private String content;
8
9 // Empty constructor is required by the data layer and JSON de/ser
10 public Quote() {
11 }
12
13 public Quote(String id, String book, String content) {
14 this.id = id;
15 this.book = book;
16 this.content = content;
17 }
18
19 public String getId() {
Chapter 2: Full Reactive Stack Backend 16
20 return id;
21 }
22
23 public String getBook() {
24 return book;
25 }
26
27 public String getContent() {
28 return content;
29 }
30
31 @Override
32 public String toString() {
33 return "Quote{" +
34 "id='" + id + '\'' +
35 ", book='" + book + '\'' +
36 ", content='" + content + '\'' +
37 '}';
38 }
39
40 @Override
41 public boolean equals(Object o) {
42 if (this == o) return true;
43 if (o == null || getClass() != o.getClass()) return false;
44
45 Quote quote = (Quote) o;
46
47 if (id != null ? !id.equals(quote.id) : quote.id != null) return false;
48 if (book != null ? !book.equals(quote.book) : quote.book != null) return fal\
49 se;
50 return content != null ? content.equals(quote.content) : quote.content == nu\
51 ll;
52 }
53
54 @Override
55 public int hashCode() {
56 int result = id != null ? id.hashCode() : 0;
57 result = 31 * result + (book != null ? book.hashCode() : 0);
58 result = 31 * result + (content != null ? content.hashCode() : 0);
59 return result;
60 }
61 }
Chapter 2: Full Reactive Stack Backend 17
Additional resources
If you’re curious about Spring Data and how it can be used in a typical Spring Boot
application for easy management of databases, consider getting a copy of my new book:
Learn microservices with Spring Boot 2nd Edition²⁰.
Let’s have a look at the interface, and then we’ll describe what it does.
1 package com.thepracticaldeveloper.reactiveweb.repository;
2
3 import com.thepracticaldeveloper.reactiveweb.domain.Quote;
4
5 import org.springframework.data.domain.Pageable;
6 import org.springframework.data.repository.reactive.ReactiveSortingRepository;
7
8 import reactor.core.publisher.Flux;
9
10 public interface QuoteMongoReactiveRepository extends ReactiveSortingRepository<Quot\
11 e, String> {
12
13 Flux<Quote> findAllByIdNotNullOrderByIdAsc(final Pageable page);
14 }
The interface we’re extending, ReactiveSortingRepository, adds sorting capabilities to the base
ReactiveCrudRepository, which contains the basic CRUD operations. We get everything we need
except for one requirement: retrieving pages of Quotes. To accomplish that, we make use of Spring
Data’s Query Methods and we pass a Pageable argument to define the offset and the results per
page. Note that the query will match all the quotes since our filter looks for non-null ids so it’s
there only because the query method findAllBy... always expects a filter. We also want to sort the
results by quote ID so we add the OrderByIdAsc suffix and Spring Data will take care of translating
this to a proper MongoDB sort clause.
Both the provided findAll method and the findAllByIdNotNullOrderByIdAsc that we’ll use from
the controller return a Flux. That means that our subscriber (the controller) can control how fast the
data should be pulled from the database.
²⁰https://thepracticaldeveloper.com/learn-microservices-v2/
Chapter 2: Full Reactive Stack Backend 18
Now imagine that we want to save a Quote and we’re so confident about the result that we just
ignore it, so we use:
1 quoteRepository.save(quote);
You can do that with an interface extending CrudRepository and the entity will be persisted.
However, if you do that with a ReactiveCrudRepository, the entity is not saved. The reactive
repository returns a Mono, which is a Publisher, so it won’t start working until you subscribe to
it. If you want to mimic the blocking behavior that CrudRepository offers, you need to call instead:
1 quoteRepository.save(quote).block();
But then you are not leveraging the reactive advantages and you could keep it simpler with a classic
repository definition. We’ll see an example of this when we get to the QuijoteDataLoader class.
Controller’s Code
Let’s focus now on the most important part of our backend application for the purpose we have
in this guide: the Reactive Controller. First, let’s see the full code source, and then we’ll navigate
through the different parts.
Chapter 2: Full Reactive Stack Backend 19
1 package com.thepracticaldeveloper.reactiveweb.controller;
2
3 import com.thepracticaldeveloper.reactiveweb.domain.Quote;
4 import com.thepracticaldeveloper.reactiveweb.repository.QuoteMongoReactiveRepository;
5 import org.springframework.data.domain.PageRequest;
6 import org.springframework.web.bind.annotation.GetMapping;
7 import org.springframework.web.bind.annotation.RequestParam;
8 import org.springframework.web.bind.annotation.RestController;
9 import reactor.core.publisher.Flux;
10
11 import java.time.Duration;
12
13 @RestController
14 public class QuoteReactiveController {
15
16 private static final int DELAY_PER_ITEM_MS = 100;
17
18 private final QuoteMongoReactiveRepository quoteMongoReactiveRepository;
19
20 public QuoteReactiveController(final QuoteMongoReactiveRepository quoteMongoReac\
21 tiveRepository) {
22 this.quoteMongoReactiveRepository = quoteMongoReactiveRepository;
23 }
24
25 @GetMapping("/quotes-reactive")
26 public Flux<Quote> getQuoteFlux() {
27 return quoteMongoReactiveRepository.findAll().delayElements(Duration.ofMilli\
28 s(DELAY_PER_ITEM_MS));
29 }
30
31 @GetMapping("/quotes-reactive-paged")
32 public Flux<Quote> getQuoteFlux(final @RequestParam(name = "page") int page,
33 final @RequestParam(name = "size") int size) {
34 return quoteMongoReactiveRepository.findAllByIdNotNullOrderByIdAsc(PageReque\
35 st.of(page, size))
36 .delayElements(Duration.ofMillis(DELAY_PER_ITEM_MS));
37 }
38
39 }
If you’re familiar with Spring Controllers and their annotations, you’ll find out quickly that the
only part of the code that seems different is the Flux object we’re returning as a result of the
methods. In Spring MVC, we would probably return a Java collection (e.g. List) instead. Let’s park
Chapter 2: Full Reactive Stack Backend 20
the delayElements method and the paging arguments for a moment; we’ll cover them in the next
sub-sections.
Note that you can also choose the Router Functions alternative instead of the annotated controllers
(@RestController and @GetMapping annotations). The implementation would be quite different,
but the resulting functionality would be exactly the same. Since that doesn’t add any value to our
code, I’m sticking to the classic style.
The controller is calling the QuoteMongoRepository to retrieve all quotes. The printed versions of
Don Quixote can easily get to more than 500 pages, so you can imagine that there are a lot of
quotes (more than 5000). Thanks to the reactive approach, we don’t need the full list of results to be
available in the backend before getting them: we can consume the quotes one by one as soon as
the MongoDB driver is publishing results.
Pagination
When we created the Repository, we introduced an additional method to retrieve paginated results.
We’re exposing that behavior in the Controller, mapped to the URL /reactive-quotes-paged?page=x&size=y.
Creating pages for results is always a good practice, no matter if you’re calling the backend in
a blocking or non-blocking style. On one hand, the client might be not interested in getting all
the results at once; on the other hand, you want to make the best use of resources by keeping the
request’s processing time as short as possible.
We build a Pageable object from the query parameters just by calling PageRequest.of(page, size).
To enable CORS with WebFlux globally, one of the options is to inject a CorsWebFilter bean in the
context with custom configuration of the allowed origins, methods, headers, etc. We’ll configure
it to allow any method and header from the origin localhost:4200, where our frontend will be
deployed.
1 package com.thepracticaldeveloper.reactiveweb.configuration;
2
3 import org.springframework.context.annotation.Bean;
4 import org.springframework.context.annotation.Configuration;
5 import org.springframework.web.cors.CorsConfiguration;
6 import org.springframework.web.cors.reactive.CorsWebFilter;
7 import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
8
9 @Configuration
10 public class WebConfiguration {
11
12 @Bean
13 CorsWebFilter corsFilter() {
14
15 CorsConfiguration config = new CorsConfiguration();
16
17 config.setAllowCredentials(true);
18 config.addAllowedOrigin("http://localhost:4200");
19 config.addAllowedHeader("*");
20 config.addAllowedMethod("*");
21
22 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource\
23 ();
24 source.registerCorsConfiguration("/**", config);
25
26 return new CorsWebFilter(source);
27 }
28
29 }
We’ll test these two different cases at the end of this chapter.
1 package com.thepracticaldeveloper.reactiveweb.controller;
2
3 import com.thepracticaldeveloper.reactiveweb.domain.Quote;
4 import com.thepracticaldeveloper.reactiveweb.repository.QuoteMongoBlockingRepository;
5 import org.springframework.data.domain.PageRequest;
6 import org.springframework.web.bind.annotation.GetMapping;
7 import org.springframework.web.bind.annotation.RequestParam;
8 import org.springframework.web.bind.annotation.RestController;
9
10 @RestController
11 public class QuoteBlockingController {
12
13 private static final int DELAY_PER_ITEM_MS = 100;
14
15 private final QuoteMongoBlockingRepository quoteMongoBlockingRepository;
16
17 public QuoteBlockingController(final QuoteMongoBlockingRepository quoteMongoBloc\
18 kingRepository) {
19 this.quoteMongoBlockingRepository = quoteMongoBlockingRepository;
20 }
21
22 @GetMapping("/quotes-blocking")
23 public Iterable<Quote> getQuotesBlocking() throws Exception {
²²https://www.w3schools.com/html/html5_serversentevents.asp
Chapter 2: Full Reactive Stack Backend 23
24 Thread.sleep(DELAY_PER_ITEM_MS * quoteMongoBlockingRepository.count());
25 return quoteMongoBlockingRepository.findAll();
26 }
27
28 @GetMapping("/quotes-blocking-paged")
29 public Iterable<Quote> getQuotesBlocking(final @RequestParam(name = "page") int \
30 page,
31 final @RequestParam(name = "size") int \
32 size) throws Exception {
33 Thread.sleep(DELAY_PER_ITEM_MS * size);
34 return quoteMongoBlockingRepository.retrieveAllQuotesPaged(PageRequest.of(pa\
35 ge, size));
36 }
37 }
The Repository is very similar to the reactive version, but this time we extend PagingAndSortingRepository,
which adds Paging and Sorting functionality on top of the basic CrudRepository. As you can see,
we return a List in this case:
1 package com.thepracticaldeveloper.reactiveweb.repository;
2
3 import java.util.List;
4
5 import com.thepracticaldeveloper.reactiveweb.domain.Quote;
6
7 import org.springframework.data.domain.Pageable;
8 import org.springframework.data.repository.PagingAndSortingRepository;
9
10 public interface QuoteMongoBlockingRepository extends PagingAndSortingRepository<Quo\
11 te, String> {
12
13 List<Quote> findAllByIdNotNullOrderByIdAsc(final Pageable page);
14 }
In the project’s GitHub repository, you’ll see a file containing the ebook in text mode: pg2000.txt.
The first time we run the application, every paragraph will be stored as a Quote in MongoDB.
To achieve this, we inject an ApplicationRunner implementation in the application’s context: the
QuijoteDataLoader class.
You can learn more about how the ApplicationRunner approach works in the Spring Boot’s reference
documentation²³.
In our case, we’ll check first if the data is already there. If it isn’t, we create a Flux from a
BufferedReader stream, and, for each line, we convert it to a Quote object and store it in the database.
For the identifiers, we use a functional Supplier interface to generate a sequence.
1 package com.thepracticaldeveloper.reactiveweb.configuration;
2 // ... imports
3
4 @Component
5 public class QuijoteDataLoader implements ApplicationRunner {
6
7 private static final Logger log = LoggerFactory.getLogger(QuijoteDataLoader.clas\
8 s);
9
10 private final QuoteMongoReactiveRepository quoteMongoReactiveRepository;
11
12 QuijoteDataLoader(final QuoteMongoReactiveRepository quoteMongoReactiveRepositor\
13 y) {
14 this.quoteMongoReactiveRepository = quoteMongoReactiveRepository;
15 }
16
17 @Override
18 public void run(final ApplicationArguments args) {
19 if (quoteMongoReactiveRepository.count().block() == 0L) {
20 var idSupplier = getIdSequenceSupplier();
21 var bufferedReader = new BufferedReader(
22 new InputStreamReader(getClass()
23 .getClassLoader()
24 .getResourceAsStream("pg2000.txt"))
25 );
26 Flux.fromStream(
27 bufferedReader.lines()
28 .filter(l -> !l.trim().isEmpty())
29 .map(l -> quoteMongoReactiveRepository.save(
30 new Quote(idSupplier.get(),
31 "El Quijote", l))
²³https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-command-line-runner
Chapter 2: Full Reactive Stack Backend 25
32 )
33 ).subscribe(m -> log.info("New quote loaded: {}", m.block()));
34 log.info("Repository contains now {} entries.",
35 quoteMongoReactiveRepository.count().block());
36 }
37 }
38
39 private Supplier<String> getIdSequenceSupplier() {
40 return new Supplier<>() {
41 Long l = 0L;
42
43 @Override
44 public String get() {
45 // adds padding zeroes
46 return String.format("%05d", l++);
47 }
48 };
49 }
50 }
The data loader is a good example of using a reactive programming style with a blocking logic and
self-subscription since the ApplicationRunner interface is not prepared for a reactive approach:
• Since the repository is reactive, we need to block() to wait for the result of the one-element
publisher (Mono) containing the number of quotes in the repository (the count method).
• We apply a reactive pattern to subscribe to the result of save() from the reactive repository.
Remember that, if we don’t consume the result, the quote is not stored.
If the ApplicationRunner interface would offer a reactive signature, meaning a Flux or Mono return
type, we could subscribe to the count() method instead and chain the mono and fluxes. Instead,
we need to call block() in our example to keep the runner executor thread alive. Otherwise, we
wouldn’t be able to load the data before the executor finishes.
In the loader case, it would make more sense to switch to a purely functional approach with
the classic repository. However, we used this as an example of how fluxes work in a blocking
programming style.
1 version: "2"
2
3 services:
4 mongo:
5 image: mongo:3.4
6 hostname: mongo
7 ports:
8 - "27017:27017"
9 volumes:
10 - mongodata:/data/db
11
12 volumes:
13 mongodata:
I’ll explain the contents in more detail in the dedicated Docker section in this guide. For now, just
keep in mind that this will create a docker container with a MongoDB instance in your Docker host
(which can be localhost or a virtual machine IP), and expose the default connection port, 27017. It’ll
create a persistent volume too, so the quotes’ data will remain after stopping the container. To run
mongo with this configuration, you need to execute:
1 $ docker-compose -f docker-compose-mongo-only.yml up
1 $ ./mvnw spring-boot:run
If everything goes well you should see the application starting and, immediately after, the loader
storing all quotes. This will happen only the first time you run the backend application.
Chapter 2: Full Reactive Stack Backend 27
you’ll need either the native Linux shell or a Linux shell simulator (like Git Bash, included with the
official Git distribution package, or Cygwin). You can also download cURL for Windows from this
site²⁵ or use any other HTTP client of your choice (e.g. Postman).
Then we try the paged version of the reactive endpoint:
1 $ curl "http://localhost:8080/quotes-reactive-paged?page=0&size=50"
Something is going wrong with that request. It takes at least 5 seconds to complete (due to the delay
of 100ms per element). After that time, we’ll get a big JSON output in the console. It’s still a blocking
call.
The reason is that we didn’t specify what are the contents we accept in our first request so Spring
WebFlux, by default, is returning us a JSON and treating the request as a blocking one. To activate
the Reactive patterns via a Server-Sent Events channel, we need to request a text/event-stream
content instead. Let’s do that:
Much better! The output format is different now and so it is the time that it takes to get each quote
on the console. This time, WebFlux is creating a Server-Sent Events connection and publishing a
quote every 100 milliseconds. The cURL tool supports SSE so it’s consuming these events. We’re not
²⁵https://curl.haxx.se/windows/
Chapter 2: Full Reactive Stack Backend 29
employing any kind of backpressure here so our client is just pulling data continuously and printing
it on screen.
Let’s keep that header in our request and evaluate what happens in the rest of the cases:
curl -H "Accept: text/event-stream" "http://localhost:8080/quotes-blocking-paged?page=0&size=50"
This call will block for five seconds since we’re requesting data to the classic controller (non-
reactive). The same will happen if we change the URL to http://localhost:8080/quotes-blocking.
In that case, you’ll need some patience since it will take almost ten minutes to complete.
curl -H "Accept: text/event-stream" "http://localhost:8080/quotes-reactive"
That one will also take almost ten minutes to finish but, in this reactive scenario, we’re getting quotes
as soon as they’re dispatched. If you’re a fast reader, you can try to read the book as it’s printed to
console. Otherwise, you can always cancel it with a Ctrl-C.
At this point, we proved one of the most important takeaways from this guide: it doesn’t matter if
you use a Reactive Web approach in the backend, it won’t be really reactive and non-blocking
unless your client can handle it as well.
We’ll implement a more realistic Reactive Web Client in the next section of this guide.
Chapter 3: Connecting Angular with
the WebFlux Backend
In the previous chapter, we created a Spring Boot application that offers a Reactive Web API. We’ll
cover in this one how to implement the client’s side using Angular with EventSource and RxJS’
Observable.
Goal
This chapter focuses on the client’s side of the system. We already created a Reactive Web Endpoint
that can supply Server-Sent Events but, how to use it in a more realistic example? That will be our
Angular application.
As a disclaimer, I’m far from being an expert in Angular and Typescript so the application has
some room for improvement. I’ll skip the Angular basics and jump almost directly into the Reactive
Service. If you want to learn Angular this guide is not the best choice; better check the official docs²⁶
for example.
Why Angular?
To be honest, I chose Angular to implement the frontend side since I have some experience with
this framework and it’s very backender-friendly. In any case, you could use the instructions in this
guide to implement the solution in any other framework. That’s because the reactive functionality
is supported by RxJS and the EventSource specification²⁷ that is part of HTML5²⁸.
UI Application Overview
What we’ll build is a simple application that allows us to load the available quotes from the server in a
reactive manner (using Server-Sent Events) or in a classic blocking style. That option will come from
the Angular Service we’ll use, so we’ll implement two different ones to showcase the differences.
The UI contains a table and the goal is that, as soon as we receive book quotes, the table rows get
updated.
To make it a bit more interesting, if we click on a quote the full content will be displayed in a different
component on the screen. So, it’s basically a master-detail view where the master rows are loaded
dynamically using Reactive patterns.
As you can see in the figure above, our application has three simple Angular components and two
services. The component at the top of the hierarchy is the App Component, which acts just as a
wrapper for the Quotes Component in our scenario.
The Quotes Component is the core component of our application and it uses the Quote Reactive
Service and the Quote Blocking Service through Dependency Injection. The Quote Detail Component
will be also included in the main component, and it just shows the full contents of the selected Quote
object.
The rest of the files in the GitHub repository³² are tests, styles, dependency management, module
definitions, etc. As mentioned before, if you’re not familiar with Angular and want to learn more, I
recommend you to follow the basic Tour of Heroes tutorial³³, in which you’ll grasp all these concepts.
In this chapter, we’ll focus on how the Quote Reactive Service works and its connection with the
Component.
³²https://github.com/mechero/full-reactive-stack/tree/master/angular-reactive
³³https://angular.io/tutorial
Chapter 3: Connecting Angular with the WebFlux Backend 32
38 });
39 }
40
41 }
Now, some explanation about the most important ideas in that code:
With EventSource³⁴ we connect to the endpoint setting a Content-Type text/event-stream. This
is implicitly set up by the API. That way, as we saw in the previous section, we open a channel in
which the server will send us events. We create this connection implicitly when we construct the
EventSource object, passing the endpoint in which the server exposes this functionality. Note that,
depending on whether the page and size parameters are used in the method, we’re passing them as
parameters in the URL.
Whenever we receive an event through the EventSource object (onmessage), we emit a new Quote
object using Observable.next. Note that we don’t use EventSource (SSE) to pass data within our
UI application. Instead, we use here a ReactiveX (RxJS) Observable object. A Subscriber of that
Observable will receive events when the array gets updated – an action that we perform by calling
next().
As detailed in the code comments, we’ll get an error when the stream is closed by the remote. We
can identify that error if readyState is zero, and then we treat it as a normal situation: we complete
the observer so the subscribers will know that they consumed all the available data.
³⁴https://developer.mozilla.org/en-US/docs/Web/API/EventSource
Chapter 3: Connecting Angular with the WebFlux Backend 34
44 this.quoteArray.push(quote);
45 this.cdr.detectChanges();
46 });
47 }
48
49 requestQuoteBlocking(): void {
50 this.resetData();
51 if (this.pagination === true) {
52 this.quoteBlockingService.getQuotes(this.page, this.size)
53 .subscribe(q => this.quoteArray = q);
54 } else {
55 this.quoteBlockingService.getQuotes()
56 .subscribe(q => this.quoteArray = q);
57 }
58 }
59
60 onSelect(quote: Quote): void {
61 this.selectedQuote = quote;
62 }
63 }
To complete the stack service-component-template, we have to provide HTML code that renders
this component. This implementation is very simple since it just uses Angular basics with a for over
an array. See the code below.
1 <div class="row">
2 <div class="col-md-8">
3 <p>
4 <label>Pagination:
5 <input type="checkbox" [checked]="pagination" (change)="pagination = !pagina\
6 tion" >
7 <label>Page #:
8 <input type="text" [(ngModel)]="page" size="2">
9 </label>
10 <label>Page Size:
11 <input type="text" [(ngModel)]="size" size="2">
12 </label>
13 </label>
14 <button (click)="requestQuoteStream()">Reactive Request</button>
15 <button (click)="requestQuoteBlocking()">Blocking Request</button>
16 </p>
17 <table class="table">
18 <thead>
Chapter 3: Connecting Angular with the WebFlux Backend 36
19 <tr>
20 <th>Quote ID</th>
21 <th>Book</th>
22 <th>Quote content</th>
23 </tr>
24 </thead>
25 <tbody>
26 <tr *ngFor="let quote of quoteArray" [class.success]="quote === selectedQuote"\
27 (click)="onSelect(quote)">
28 <td>{{quote.id}}</td>
29 <td>{{quote.book}}</td>
30 <td>{{quote.content.substr(0, 30)}}...</td>
31 <tr>
32 </tbody>
33 </table>
34 </div>
35 <div class="col-md-4">
36 <div class="message">Click on a quote for more details</div>
37 <app-quote-detail [quote]="selectedQuote"></app-quote-detail>
38 </div>
39 </div>
1 $ npm install
Then, we can run the frontend application from the command line with:
If everything goes well, we’ll see the next screen when we navigate with our browser to http://localhost:4200.
Chapter 3: Connecting Angular with the WebFlux Backend 37
Angular Reactive
We have two buttons available to choose between a Reactive or a Blocking request. That’s the switch
between calling one service or the other. Besides, we can also pick if we want pagination and its
corresponding configuration.
Keep in mind that, depending on your device, you may experience memory problems when
requesting all the data at once (no pagination). In that case, you can change the backend code
(controller) to limit the number of quotes returned. Check the backend chapter for more details.
We can now play with all these options and see how the quotes load either at once or one by one,
depending on whether we execute a blocking request or a reactive one.
You should be able to feel how the user experience improves with the reactive approach. The Quotes
are showing up as soon as the data is available, so we can access them almost immediately. We don’t
need to wait for the server to return all elements (or a complete page). In the next chapter, we’ll go
more into detail about performance and additional conclusions.
Chapter 3: Connecting Angular with the WebFlux Backend 38
As mentioned in the first chapter, it’s a known fact that web users abandon slow sites. Following the
same example we used back then, it’s much better to show a few products of your online store as
soon as possible to your potential customers than keeping them waiting for all the content to appear
at once.
folder, spring-boot-reactive-web³⁵, you’ll find the test class BenchmarkTest. Remove the @Ignore
annotation and run the tests or just use your IDE to execute it.
Benchmark details
I used an Intel Core i7 @ 2,5 GHz with 1 processor and 4 cores for the test. The backend runs from
IntelliJ IDEA (not Docker). I didn’t modify the default configuration for Netty, which in my case is
running 8 parallel server threads.
The benchmark allows you to configure:
Every request performed by the client should take at least one second since the delay per element is
100ms and we’re asking the server for 10 quotes. The existing code runs 1, 8, 32, 96 and 768 requests
four times:
To avoid potential differences in the test implementation, both reactive and not reactive requests
use a WebClient, the reactive client that comes with the new Spring WebFlux. I also tried with
RestTemplate for the blocking part with very similar results. The log levels of the application are in
the repository so you can get some valuable information from them when running the Benchmark:
threads started, locks acquired and released, etc.
When we execute blocking calls, the server can’t process so many requests in parallel. It’s clearly
limited by the number of server threads running, eight in this case.
This proves that a Reactive Web approach optimizes the server resources by unblocking the
server’s threads and processing the requests separately, in parallel.
Bear in mind that this benchmark shows a clear winner because it focuses on a key server resource:
the number of parallel threads available. We could make the classic Web stack perform much better:
Chapter 4: Conclusions 44
1. A first option would be to increase the number of parallel threads available in the server,
adjusting them to our needs.
2. On the implementation side, we could switch to asynchronous processing at the controller
layer. This could be achieved for example by returning a DeferredResult or a Callable as a
response. See the Asynchronous Requests section³⁶ in the Spring docs for more information.
Suitability
You should evaluate if a Full Reactive stack fits your case. Do you require SQL queries to complete
your Web response? Bad news: reactive support for relational databases are still at a very early stage.
Look also at the client’s side: can your web clients switch to a reactive approach? If the answer is
no, then the list of advantages you’ll get becomes smaller.
Testing
Writing Unit and Integration tests for reactive streams with WebFlux might be tricky. The
Project Reactor documentation³⁷ is a good starting point, showing how you can use for example
StepVerifier to check that your Fluxes and Monos work the way you want.
You can also find examples of Unit Tests in our backend application’s GitHub repository³⁸.
An extra inconvenience is that there isn’t a big community of developers from where you can see
examples of testing with WebFlux yet. Besides, the tests are not as readable as the MVC ones, and
they are also harder to debug if something goes wrong.
³⁶https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-async
³⁷https://projectreactor.io/docs/core/release/reference/#testing
³⁸https://github.com/mechero/full-reactive-stack/tree/master/spring-boot-reactive-web/src/test/java/com/thepracticaldeveloper/
reactiveweb/controller
Chapter 4: Conclusions 45
Conclusions
Like any other technology choice, WebFlux (and the Reactive Web approach in general) has
advantages and downsides. Don’t go for it just because it’s the new thing to do.
As we saw in this guide, WebFlux can bring performance benefits and can also leverage the
experience you provide to your users when they’re waiting for the response’s data. On the other
hand, reactive programming comes with new ways of writing, testing, and debugging code, so it’s
not a quick transition.
I always recommend a very good analysis of not only the requirements of your application but also
other aspects of your organization. Is the development team ready for the change? Can you solve
all the challenges you’ll face? Will you really use the benefits or are they just nice-to-haves?
For an existing project that may take advantage of it, I’d rather try it in a limited scope. You could
implement this approach in a separate component (or module, or microservice). Evaluate the results;
then, you can refactor the rest of the code if you see that it brings value.
I hope you enjoyed this guide. Feel free to give me some feedback via Twitter³⁹, GitHub⁴⁰, or any
other channel you prefer.
Would you like to know more about Spring Boot applied to a Microservices Architecture? Learn all
these concepts from a practical perspective with my new book⁴¹.
³⁹https://twitter.com/moises_macero
⁴⁰https://github.com/mechero/full-reactive-stack/issues
⁴¹https://thepracticaldeveloper.com/learn-microservices-v2/
Appendix: Running the application in
Docker
In previous chapters, we covered how to run the backend Spring Boot application and how to run
the frontend with Angular from the command line. However, it’s much easier to run everything
together in Docker containers. Let’s see how to set up and run the application this way.
Then, we can find the resulting .jar file in the target folder.
To build the frontend code, run this command from the angular-reactive folder:
In this case, the HTML and JavaScript output will be placed into the dist folder.
What we’ll do with Docker is to build both the backend and the frontend and deploy the resulting
artifacts as containers. We’ll also add a MongoDB image with a custom configuration.
1 # Build stage
2 FROM node:14 as build-stage
3 COPY ./ /usr/src/
4 WORKDIR /usr/src
5 RUN npm install
6 RUN npm run ng build --prod
7
8 # Compiled app based on nginx
9 FROM nginx:1.19
10 COPY --from=build-stage /usr/src/dist/angular-reactive/ /usr/share/nginx/html
11 COPY /nginx.conf /etc/nginx/conf.d/default.conf
12 EXPOSE 1827
⁴²https://github.com/mechero/full-reactive-stack/tree/master/docker
Appendix: Running the application in Docker 48
1 version: "2"
2
3 services:
4 mongo:
5 image: mongo:3.4
6 hostname: mongo
7 ports:
8 - "27017:27017"
9 volumes:
10 - mongodata:/data/db
11 networks:
12 - network-reactive
13
14 spring-boot-reactive:
15 build:
16 context: ../spring-boot-reactive-web
17 image: spring-boot-reactive-web-tpd
18 environment:
19 # Overrides the host in the Spring Boot application to use the Docker's hostna\
20 me
21 - SPRING_DATA_MONGODB_HOST=mongo
22 ports:
23 - "8080:8080"
24 networks:
25 - network-reactive
26
27 angular-reactive:
28 build:
29 context: ../angular-reactive
30 image: angular-reactive-tpd
31 ports:
32 - "4200:1827"
33 networks:
34 - network-reactive
35
36 volumes:
37 mongodata:
38
39 networks:
40 network-reactive:
This file is all we need for Docker to find out how to build the corresponding images and how to
run the containers together. Let’s describe its main features:
Appendix: Running the application in Docker 49
1. The build context for the backend and frontend images point to the corresponding folders
where we have the required Dockerfile files. Therefore, when we use docker-compose build,
Docker builds the images using the instructions included in each folder.
2. The mongo service uses directly the official image. Note that we override the hostname for
Spring Boot to be able to locate easily the MongoDB engine in the Docker network using
the URL http://mongo:27017. As you can see, the three services are connected to the same
network, declared at the bottom of the file (network-reactive). The MongoDB service also
uses a Docker volume, which gives us persistence between container restarts.
3. The backend service exposes the port 8080 and declares an environment variable, SPRING_-
DATA_MONGODB_HOST=mongo. By doing this, we replace the default’s Spring Boot configuration
for the database host, and we set it to the name of the host that we configured for the MongoDB
engine.
4. The Docker image we use as a base for the frontend, Nginx, runs the web server on port 1827.
Since we already configured CORS to allow the origin located at port 4200, we can expose this
one instead, and redirect it to the container’s port. You could also configure Nginx to run on a
different port.
To build our images, navigate to the docker folder and run this command:
1 $ docker-compose build
When we’re ready to run the complete system, we can execute this command:
1 $ docker-compose up
If you want to stop them you can run docker-compose stop from a different terminal, or press Ctrl-C
from the one which is running the containers. You can also run docker-compose in daemon mode if
you prefer so (with the -d flag).