Vous êtes sur la page 1sur 53

Full Reactive Stack with Spring Boot, WebFlux

and MongoDB
Moisés Macero
This book is for sale at http://leanpub.com/full-reactive

This version was published on 2020-06-15

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.

© 2018 - 2020 Moisés Macero


Contents

Chapter 1: The Reactive Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1


Reactive Web Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
WebFlux and Project Reactor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Reactive Web: Advantages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Is Non-Blocking the same as Reactive? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
The application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

Chapter 2: Full Reactive Stack Backend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7


Project Reactor – Main Features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
WebFlux – Main Features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Creating the application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Repository Layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
The Reactive Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
The Blocking Controller and Repository . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Loading data into MongoDB with an ApplicationRunner . . . . . . . . . . . . . . . . . . . . 23
Running the backend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Playing with Reactive and Classic endpoints . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

Chapter 3: Connecting Angular with the WebFlux Backend . . . . . . . . . . . . . . . . . . . 30


Goal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Why Angular? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Creating the Angular application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
UI Application Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
The Angular Reactive service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
The Angular Components – Quick Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Running the frontend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

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

Appendix: Running the application in Docker . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46


Generate the build artifacts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Build and run the Spring Boot app in Docker . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Build the Angular app and run it in Docker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Putting everything together with Docker Compose . . . . . . . . . . . . . . . . . . . . . . . . 47
Chapter 1: The Reactive Web
This guide focuses on the capabilities of Spring WebFlux and Spring Boot 2 to create a Reactive
Web Application, supported by a code example that you can build step by step.
To avoid dumb, non-realistic examples where Spring is also the client of the Reactive API, you will
complete the stack with a client application in Angular 9. To make it reactive, you’ll use Server-Sent
Events (SSE) to communicate the backend with the frontend. See the figure below for a quick view
of the stack we’ll build.
Chapter 1: The Reactive Web 2

Full Reactive Stack - Quick View


Chapter 1: The Reactive Web 3

This guide has four main chapters and an appendix:

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:

• Spring Boot 2.3.


• Spring WebFlux (also Spring Web to compare).
• Spring Data JPA Reactive Repositories
• Angular 9
• RxJS
• EventSource API.

Reactive Web Patterns


Reactive patterns and non-blocking programming techniques are widely extended technologies
nowadays. However, when we focus on the Web layer and exposed APIs, there is still a wide majority
of applications that use blocking strategies.
To try to clarify a bit better how this new Reactive Web approach differs from other ones, let’s
compare it with two existing techniques:

• 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.

WebFlux and Project Reactor


Spring 5 provides a non-blocking, reactive alternative to the standard Spring Web MVC approach:
Spring WebFlux. It is based on the Project Reactor² (also part of the Spring family), which follows
the Reactive Streams Specification³. That means WebFlux follows the standard concepts of Reactive
Programming: Publishers, Subscribers, Observables, etc.
We’ll cover WebFlux and Project Reactor in more detail in the next chapter.

Reactive Web: Advantages


So WebFlux implements a reactive, non-blocking API. That sounds pretty cool but, what does that
mean exactly? Why should we care about non-blocking web calls?
The first reason is slow Internet connections. Having a slow connection is annoying, but it gets even
worse with classic blocking HTTP calls. Let’s say you’re searching for the best mobile phones on
your favorite online store app. You enter the query terms and press ‘Search’. Your Internet connection
is slow, so you need to wait for 15 seconds and stare at a spinner or a blank page. Then, the full first
page with the results pops up, all at once. That happens because the web server doesn’t care about
your slow connection, so it’s responding with a -let’s say- 300 kilobytes response of full JSON with
summaries, descriptions, and prices of the first 50 items that match your criteria. On your side -the
client’s side-, the problem is related to classic web client approaches that use blocking HTTP calls
(even in different threads), that wait until the full HTTP response is received to process the data, and
then render the page. A better alternative would be that the server, instead of returning the 50 items
together, would send to the client the items one by one as soon as they’re found. The client could
then adapt its interface to react to these individual item pushes, and render them as they come. Note
²https://projectreactor.io/
³https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.1/README.md#specification
Chapter 1: The Reactive Web 5

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.

Is Non-Blocking the same as Reactive?


It depends on whom you ask that question. ‘Reactive’ is a generic concept describing that you’re
acting triggered by a previous action. You could argue that programming techniques that follow a
non-blocking approach are reactive programming styles because you normally use a callback func-
tion, that reacts to the result. In that broad scope of the ‘reactive’ word, using a CompletableFuture,
or any other tool based on promises with callbacks, would be considered reactive programming.
However, people normally refer to these libraries and APIs as asynchronous programming.
It’s more common to talk about reactive programming when our code uses one of the existing frame-
works that follow the Reactive Streams spec⁵. Some popular reactive frameworks are ReactiveX⁶
⁴https://www.soasta.com/blog/google-mobile-web-performance-study/
⁵https://github.com/reactive-streams/reactive-streams-jvm/blob/master/README.md
⁶http://reactivex.io/
Chapter 1: The Reactive Web 6

(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:

1. A reactive style, using WebFlux and a MongoDB ReactiveCrudRepository.


2. The classical Spring Web MVC stack, using a blocking REST controller and a blocking
MongoDB query.

Additionally, we’ll use:

• Docker, to simplify MongoDB deployment.


• A script to pre-load data into MongoDB. For that, we’ll use a Spring Boot’s CommandLineRunner
implementation.

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!

Project Reactor – Main Features

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.

Fluxes and Monos


In our application, we’ll use the Flux¹³ class, an asynchronous sequence of 0-N items. It implements
the Publisher interface and, as we briefly introduced, it is just a reactive stream that pushes elements
whenever the subscriber instructs it to do so¹⁴.
There is a handy version of a Flux for the special case in which the reactive stream will either emit
only one item, or none: Mono¹⁵. It contains a different set of methods to, for instance, concatenate
Mono streams into a Flux.

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

WebFlux – Main Features

Standard Controllers and Router Functions


Spring WebFlux comes with an extra new feature called Router Functions. The idea is to apply
Functional Programming to the Web layer and get rid of the declarative way of implementing
Controllers and RequestMapping’s – see the full docs here¹⁶. However, as you can see depicted
in the picture below (based on the one available on the official docs¹⁷), Spring 5 still allows you to
use the @Controller and @RequestMapping annotations, so you can decide.

Spring WebFlux Alternatives

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.

Creating the application

Spring Boot Reactive Skeleton


To start with, the easiest option is to navigate to http://start.spring.io/¹⁹ and create a Spring Boot
application skeleton project. Make sure to select a Spring Boot version 2.0 or newer, the Reactive
Web, Reactive MongoDB, and the classic MongoDB dependencies. We’ll use Spring Boot 2.3 and
Java 14, with Maven.
¹⁹http://start.spring.io/
Chapter 2: Full Reactive Stack Backend 11

Create a Spring Boot reactive application

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

1 <?xml version="1.0" encoding="UTF-8"?>


2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001\
3 /XMLSchema-instance"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/m\
5 aven-4.0.0.xsd">
6 <modelVersion>4.0.0</modelVersion>
7
8 <groupId>com.thepracticaldeveloper</groupId>
9 <artifactId>reactive-web</artifactId>
10 <version>1.0.0-SNAPSHOT</version>
11 <packaging>jar</packaging>
12
13 <name>reactive-web</name>
14 <description>Reactive Web demo project</description>
15
16 <parent>
17 <groupId>org.springframework.boot</groupId>
18 <artifactId>spring-boot-starter-parent</artifactId>
19 <version>2.3.0.RELEASE</version>
20 <relativePath/> <!-- lookup parent from repository -->
21 </parent>
22
23 <properties>
24 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
25 <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
26 <java.version>14</java.version>
27 <sonar.scm.provider>git</sonar.scm.provider>
28 </properties>
29
30 <dependencies>
31 <dependency>
32 <groupId>org.springframework.boot</groupId>
33 <artifactId>spring-boot-starter-webflux</artifactId>
34 </dependency>
35 <dependency>
36 <groupId>org.springframework.boot</groupId>
37 <artifactId>spring-boot-starter-actuator</artifactId>
38 </dependency>
39 <dependency>
40 <groupId>org.springframework.boot</groupId>
41 <artifactId>spring-boot-starter-data-mongodb</artifactId>
42 </dependency>
43 <dependency>
Chapter 2: Full Reactive Stack Backend 13

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.

Designing our app


You’ll create a typical 3-layered backend application. The exception here is that there is no business
logic involved, so the Controllers will use directly the repositories. Besides, we’ll create two co-
existing stacks: the reactive way and the classic way. In any case, we’ll focus on the reactive side
while explaining the ideas. See the image for the final structure of the backend application (as you
find it on the GitHub repo).
Chapter 2: Full Reactive Stack Backend 14

Reactive Project Structure

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

Spring WebFlux Reactive UML

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.

The Quote class


Below you can find the Quote class implementation. It contains just an identifier, the book title, and
the quote contents.

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

Reactive Repository with Spring 5 and Spring Data


Creating a basic Reactive repository is as simple as creating a classic one in Spring Data: you just
need to create an interface that extends ReactiveCrudRepository, which is the reactive version
of CrudRepository. You’ll have access then to default methods to create, read, update, and delete
(CRUD) Quotes.

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

Saving entities the reactive way


One thing that draws the attention of people not familiar with reactive patterns is how the save
method (and its variants) works. Let’s compare the CrudRepository and the ReactiveCrudRepository
signatures:

1 public interface CrudRepository<T, ID> extends Repository<T, ID> {


2
3 <S extends T> S save(S entity);
4
5 // ... other methods
6 }

1 public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> {


2
3 <S extends T> Mono<S> save(S entity);
4
5 // ... other methods
6 }

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.

The Reactive Controller

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.

Simulating poor performance


To evaluate the Reactive Web properly, we should simulate issues like an irregular network latency
or an overloaded server. To keep it simple, we’ll go for the latter and mimic a situation where every
quote has a processing time of 100 milliseconds. It will take almost ten minutes (more than 5000
quotes divided by 100ms each) to retrieve the whole dataset. If you want to try a smaller set of quotes
you can limit the Flux using the method take at the end of the expression.
Having a simulated delay will also help us visualize the differences between Reactive and MVC
strategies. We’ll run client and server on the same machine so, if we don’t introduce the delay, the
response times are so good that it would be hard to spot the differences.

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).

Enabling CORS in Spring WebFlux


We want to connect to the backend from a client application that is deployed at a different origin
because it uses a different port (at least during development). In Web terms, that means the frontend
will do a Cross-Origin Request. Unless we explicitly allow CORS (Cross-Origin Resource Sharing)
in our configuration, that connection is going to be rejected.
Chapter 2: Full Reactive Stack Backend 21

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 }

Returning a Flux from a Controller: behind the scenes


Let’s dive a bit more into details. The Spring WebFlux documentation lists the reactive types (Flux
included) as possible return types²¹. So, what does it happen when we request content to the server?
How does WebFlux convert the data into a valid response? It actually depends on how we request
it:
²¹https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-ann-return-types
Chapter 2: Full Reactive Stack Backend 22

• If we request content without using an Accept header, or we set it to application/json, we’ll


get a blocking process and a JSON-formatted response.
• If we want to go Full Reactive and use the Server-Sent Events²² support in Spring to implement
our full reactive stack, we have to support an Event-Stream response. To do that, we have to
set (explicitly or implicitly) the Accept header to text/event-stream, therefore activating the
reactive functionality in Spring to open an SSE channel and publish server-to-client events.

We’ll test these two different cases at the end of this chapter.

The Blocking Controller and Repository


To better show the comparison between the blocking and reactive approaches, let’s create a separate
Controller with different request paths, and connect them to a standard CrudRepository. This code
is pretty straightforward and well-known to all developers familiar with Spring Web MVC.
Let’s see the Controller’s code. Note that here we also apply the same delay as in the reactive
approach. This time, the delay is just a big one, since the query is returning results at once too.

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 }

Loading data into MongoDB with an


ApplicationRunner
We have all the code we need to run our Spring Boot application. However, we don’t have the
Quotes stored in the database yet. We’ll solve this by reading them from a text version of the book
and storing them into MongoDB the first time the application runs.
Chapter 2: Full Reactive Stack Backend 24

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.

Running the backend


Remember that the complete source code (Spring Boot, Angular, Docker) is available on GitHub:
Full-Reactive Stack repository²⁴. If you find it useful, please star it!
²⁴https://github.com/mechero/full-reactive-stack
Chapter 2: Full Reactive Stack Backend 26

Running MongoDB with Docker


This step is optional, you can also install MongoDB on your machine. It’ll work the same way.
However, in the guide’s appendix, you’ll see how to run the complete reactive system using Docker,
so it’s good to follow this approach to get first contact with this tool.
You need to install Docker if you haven’t done it yet. Then, create a file named docker-compose-mongo-only.yml
with this content:

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

Running the Spring Boot Reactive application


We now run our backend application to see if everything works as expected. To do so, we can either
use our preferred IDE to run the ReactiveWebApplication class, or we can use the included maven
wrapper from the spring-boot-reactive-web folder:

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

1 INFO 36399 --- [ main] c.t.reactiveweb.ReactiveWebApplication : Starting\


2 ReactiveWebApplication on /target/classes started by moises in /frs/
3 INFO 36399 --- [ main] c.t.reactiveweb.ReactiveWebApplication : No activ\
4 e profile set, falling back to default profiles:
5 INFO 36399 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstra\
6 pping Spring Data Reactive MongoDB repositories in
7 INFO 36399 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished\
8 Spring Data repository scanning in 57ms. Found 1 ry interfaces.
9 INFO 36399 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstra\
10 pping Spring Data MongoDB repositories in DEFAULT mode.
11 INFO 36399 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished\
12 Spring Data repository scanning in 5ms. Found 1 aces.
13 INFO 36399 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing\
14 2 endpoint(s) beneath base path '/actuator'
15 INFO 36399 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty st\
16 arted on port(s): 8080
17 INFO 36399 --- [ main] c.t.reactiveweb.ReactiveWebApplication : Started \
18 ReactiveWebApplication in 1.911 seconds (JVM running
19 INFO 36305 --- [ main] c.t.r.configuration.QuijoteDataLoader : New quot\
20 e loaded: Quote{id='03142', book='El Quijote', el mancebo-, yo llevo en este envolto\
21 rio unos greguescos de terciopelo, compañeros desta ropilla; si los gasto en el cami\
22 no, no me podré udad, y no tengo con qué comprar otros; y, así por esto como por ore\
23 arme, voy desta manera, hasta alcanzar unas compañías de infantería de aquí, donde a\
24 sentaré mi plaza, y no faltarán bagajes en que caminar de allí adelante hasta el emb\
25 arcadero, que dicen ha de ser en ener por amo y por señor al rey, y servirle en la g\
26 uerra, que no a un pelón en la corte. '}
27 INFO 36305 --- [ main] c.t.r.configuration.QuijoteDataLoader : New quot\
28 e loaded: Quote{id='03143', book='El Quijote', merced alguna ventaja por ventura? -p\
29 reguntó el primo. '}
30 INFO 36305 --- [ main] c.t.r.configuration.QuijoteDataLoader : New quot\
31 e loaded: Quote{id='03144', book='El Quijote', ervido a algún grande de España, o al\
32 gún principal personaje -respondió el mozo-, a buen seguro que yo la llevara, que es\
33 o tiene el servir elo suelen salir a ser alférez o capitanes, o con algún buen entre\
34 tenimiento; pero yo, desventurado, serví siempre a catarriberas y a ón y quitación t\
35 an mísera y atenuada, que en pagar el almidonar un cuello se consumía la mitad della\
36 ; y sería tenido a milagro que un paje na siquiera razonable ventura. '}
37 ...

Playing with Reactive and Classic endpoints


Let’s test our API endpoints using cURL, a command-line tool that supports Server-Sent Events, so
we can have a look at the blocking and non-blocking alternatives in our application. On Windows,
Chapter 2: Full Reactive Stack Backend 28

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:

1 $ curl -H "Accept: text/event-stream" "http://localhost:8080/quotes-reactive-paged?p\


2 age=0&size=50"
3 data:{"id":"00000","book":"El Quijote","content":"El ingenioso hidalgo don Quijote d\
4 e la Mancha"}
5
6 data:{"id":"00001","book":"El Quijote","content":"TASA "}
7
8 data:{"id":"00002","book":"El Quijote","content":"Yo, Juan Gallo de Andrada, escriba\
9 no de Cámara del Rey nuestro señor, de los que residen en su Consejo, certifico y do\
10 y fe que, habiendo visto por los señores dél un libro intitulado El ingenioso hidalg\
11 o de la Mancha, compuesto por Miguel de Cervantes Saavedra, tasaron cada pliego del \
12 dicho libro a tres maravedís y medio; el cual tiene ochenta y tres pliegos, que al d\
13 icho precio monta el dicho libro docientos y noventa maravedís y medio, en que se ha\
14 de vender en papel; y dieron licencia para que a este precio se pueda vender, y man\
15 daron que esta tasa se ponga al principio del dicho libro, y no se pueda vender sin \
16 ella. Y, para que dello conste, di la presente en Valladolid, a veinte días del mes \
17 de deciembre de mil y seiscientos y cuatro años. "}
18
19 data:{"id":"00003","book":"El Quijote","content":"Juan Gallo de Andrada. "}
20
21 ...

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²⁸.

Creating the Angular application


To get the Angular project skeleton, the best option is to use the Angular CLI tool as described in
the Setup guide on the Angular’s website²⁹.
You’ll also need Node.js³⁰ (including npm) installed on your system.
Remember that the complete source code (Spring Boot, Angular, Docker) is available on GitHub:
Full-Reactive Stack repository³¹. If you find it useful, please star it!
²⁶https://angular.io/docs
²⁷https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events
²⁸https://www.w3schools.com/html/html5_serversentevents.asp
²⁹https://angular.io/guide/setup
³⁰https://nodejs.org/en/download/
³¹https://github.com/mechero/full-reactive-stack
Chapter 3: Connecting Angular with the WebFlux Backend 31

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.

Angular Reactive diagram

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

The Angular Reactive service


Let’s have a look at the code of the reactive service that interacts with the backend using Server-
Sent Events. We’ll cover it in detail later.

1 import { Injectable } from '@angular/core';


2
3 import { Quote } from './quote';
4
5 import {Observable} from 'rxjs';
6
7 @Injectable()
8 export class QuoteReactiveService {
9
10 url: string = 'http://localhost:8080/quotes-reactive';
11 urlPaged: string = 'http://localhost:8080/quotes-reactive-paged';
12
13 getQuoteStream(page?: number, size?: number): Observable<Quote> {
14 return new Observable<Quote>((observer) => {
15 let url = this.url;
16 if (page != null) {
17 url = this.urlPaged + '?page=' + page + '&size=' + size;
18 }
19 let eventSource = new EventSource(url);
20 eventSource.onmessage = (event) => {
21 console.debug('Received event: ', event);
22 let json = JSON.parse(event.data);
23 observer.next(new Quote(json['id'], json['book'], json['content']));
24 };
25 eventSource.onerror = (error) => {
26 // readyState === 0 (closed) means the remote source closed the connection,
27 // so we can safely treat it as a normal situation. Another way
28 // of detecting the end of the stream is to insert a special element
29 // in the stream of events, which the client can identify as the last one.
30 if(eventSource.readyState === 0) {
31 console.log('The stream has been closed by the server.');
32 eventSource.close();
33 observer.complete();
34 } else {
35 observer.error('EventSource error: ' + error);
36 }
37 }
Chapter 3: Connecting Angular with the WebFlux Backend 33

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.

The Angular Components – Quick Overview


The Quotes Component (quotes.component.ts) is the part interacting with the Services, both the
blocking and the reactive one.
This component uses the Reactive or the Blocking service depending on the user’s choice (via buttons
on the HTML page as we’ll see in the next sub-section). It subscribes to the Observable<Quote>
object and it pushes a new element in the data array every time a new item arrives. That’s the most
important part of the component logic. To make sure that the UI refreshes after each update, we use
Angular’s ChangeDetectorRef via injection to force change detection.
As you can see, no matter which service we’re calling, the result will be an Observable. The reason
is that the blocking HTTP API we use also returns an Observable object. The only difference is that
the blocking service returns a full array of Quotes, all at once.

³⁴https://developer.mozilla.org/en-US/docs/Web/API/EventSource
Chapter 3: Connecting Angular with the WebFlux Backend 34

1 import {Quote} from './quote';


2 import {QuoteReactiveService} from './quote-reactive.service';
3 import {QuoteBlockingService} from './quote-blocking.service';
4
5 import {Observable} from 'rxjs';
6 import {ChangeDetectorRef, Component} from "@angular/core";
7
8 @Component({
9 selector: 'app-component-quotes',
10 providers: [QuoteReactiveService],
11 templateUrl: './quotes.component.html'
12 })
13 export class QuotesComponent {
14
15 quoteArray: Quote[] = [];
16 selectedQuote: Quote;
17 mode: string;
18 pagination: boolean;
19 page: number;
20 size: number;
21
22 constructor(private quoteReactiveService: QuoteReactiveService, private quoteBlock\
23 ingService: QuoteBlockingService, private cdr: ChangeDetectorRef) {
24 this.mode = "reactive";
25 this.pagination = true;
26 this.page = 0;
27 this.size = 50;
28 }
29
30 resetData() {
31 this.quoteArray = [];
32 }
33
34 requestQuoteStream(): void {
35 this.resetData();
36 let quoteObservable: Observable<Quote>;
37 if (this.pagination === true) {
38 quoteObservable = this.quoteReactiveService.getQuoteStream(this.page, this.siz\
39 e);
40 } else {
41 quoteObservable = this.quoteReactiveService.getQuoteStream();
42 }
43 quoteObservable.subscribe(quote => {
Chapter 3: Connecting Angular with the WebFlux Backend 35

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>

Running the frontend


First, make sure the backend side is up and running, MongoDB included. If you cloned the git
repository, you’ll need to download the node dependencies. From the angular-reactive folder,
you have to execute:

1 $ npm install

Then, we can run the frontend application from the command line with:

1 $ npm run ng serve

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

Angular Reactive Quotes


Chapter 4: Conclusions
WebFlux vs. Blocking (MVC): user experience
Let’s start our comparison with the most noticeable difference for Web Users: user experience.
We’ll use the frontend for this comparison.
Playing the user’s role is quite simple in this application: enter values for the given parameters
(pagination, page number, and size) and press either the ‘Reactive Request’ or the ‘Blocking Request’
button. That will put our implementation into practice, calling the backend services from the web
page and processing the response either as small portions (WebFlux) or as a whole thing (Web MVC).
This is covered by the previous chapters, so we won’t dive into details about how this works.
You’ll find out quickly that, for the non-paginated requests and big page sizes, the user’s experience
for the reactive case is much better. You don’t need to wait for the request to finish to start reading
the quotes. Besides, keep in mind that this is a simulated scenario in which we’re forcing a constant
delay per element from the server. In a real situation, where the server’s response time and the
network latency may vary unpredictably, the benefit would be even more noticeable.
Chapter 4: Conclusions 40

Angular Reactive Quotes

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.

WebFlux vs. Blocking (MVC): performance


Interestingly enough, implementing a Full Reactive Web Stack introduces some extra delay in the
total time required to retrieve a response from the server. That’s due to the extra processing needed
to make the communication work. Therefore, we can’t extract conclusions by looking at the requests
one by one.
The Reactive Web approach shines when we measure the whole performance of the server when
handling multiple requests at the same time. In this section, we’ll go through different test
configuration options and we’ll see the results in graphs that compare the WebFlux implementation
with the Web MVC one.
The results come from a benchmark test that you can run yourself. In the GitHub backend’s project
Chapter 4: Conclusions 41

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:

• The total number of requests to execute.


• The number of requests to run in parallel (parallelism).

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:

• With parallelism of 32 and invoking the reactive endpoint.


• With parallelism of 32 and invoking the blocking endpoint.
• With parallelism of 96 and invoking the reactive endpoint.
• With parallelism of 96 and invoking the blocking endpoint.

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.

Server’s Side: Requests Served per Second


As you can see in the graph below, WebFlux scores much better for a high load. It’s capable
of processing almost all the requests in parallel, reaching 30 requests/second when we execute 32
requests at once and 90 requests/second for 96 simultaneous calls.
³⁵https://github.com/mechero/full-reactive-stack/tree/master/spring-boot-reactive-web
Chapter 4: Conclusions 42

server requests per second MVC vs WebFlux

server requests per second MVC vs WebFlux

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.

Client’s Side: Average Time per Request


This metric also shows WebFlux as the winner option. The Netty server keeps all the reactive requests
close to the minimum of one second, which is actually the consequence of the server being able to
Chapter 4: Conclusions 43

cope with almost every request in parallel.


On the blocking landscape, the higher the number of parallel requests the longer the request takes
to complete in average. It makes sense: given that all the server threads are busy, the remaining
requests need to wait, thus increasing the average time. On the functional side, many users would
be unhappy because the web page takes a long time to load.

Average time per request WebFlux vs MVC

Average time per request WebFlux vs MVC

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.

WebFlux vs. Blocking (MVC): ease of development


This is the most subjective angle that we can use to compare both approaches. However, I think it’s
very important because it would be risky to blindly choose WebFlux because of the value it brings
to user’s experience without considering other factors.
For instance, take into account the knowledge of Reactive Patterns in your organization. It’s clearly
a change of paradigm and there are many ways in which things can go wrong if people are not
properly trained in these concepts. You might end up with an amazingly-performing web server
which is a nightmare to maintain from the developer’s point of view.
In my opinion, the readability of code may get worse with reactive patterns. Large chains of methods
with a weird indentation where the reader struggles to figure out the logic behind unless there is
good inline documentation or a very good code assistance from the IDE. On top of that, there is also
extra complexity associated to debug these code blocks that do not return values immediately.

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.

Generate the build artifacts


We’ll create docker images from the build artifacts so, first, we need to make sure we generate the jar
file for the Spring Boot application and the HTML and Javascript artifacts from the Angular code.
To build the backend you need to execute, from the spring-boot-reactive-web folder:

1 $ mvnw clean package

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:

1 $ npm run ng build

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.

Build and run the Spring Boot app in Docker


Let’s start by having a look at the Dockerfile contents inside the backend folder. These docker
instructions set up a builder container that uses the official openjdk image. Then, we use the
resulting jar file to build a Docker image that will run the Spring Boot application and expose the
port 8080.
Appendix: Running the application in Docker 47

1 FROM openjdk:14 AS builder


2 COPY . /usr/src/reactive-backend
3 WORKDIR /usr/src/reactive-backend
4 RUN ./mvnw clean package
5
6 FROM openjdk:14
7 COPY --from=builder /usr/src/reactive-backend/target/reactive-web-1.0.0-SNAPSHOT.jar\
8 /usr/src/reactive-backend/
9 WORKDIR /usr/src/reactive-backend
10 EXPOSE 8080
11 CMD ["java", "-jar", "reactive-backend-1.0.0-SNAPSHOT.jar"]

Build the Angular app and run it in Docker


Inside the angular-reactive folder, we can find the Dockerfile that builds and prepare the image for
the frontend side. In this case, the build logic uses the official node.js image and copies the resulting
HTML/JS inside an image built on top of the web server, Nginx.

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

Putting everything together with Docker Compose


We’ll build these images and run the whole application via a Docker Compose file that is located in
the docker folder in the GitHub repository⁴².

⁴²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).

Vous aimerez peut-être aussi