Full Reactive Stack with Spring Boot, WebFlux and MongoDB

Full Reactive Stack with Spring Boot, WebFlux and MongoDB

Through this post (part of a series), you’ll learn how to develop the backend Reactive Web Application. We’ll use Spring WebFlux, available from Spring Boot 2.0 version, and connect to a Mongo database using its reactive driver with Spring Data.

Guide’s Table of Contents

This post is part of the Full Reactive Stack Guide:

  1. Full Reactive Stack - Introduction
  2. Backend side with WebFlux, Spring Boot and MongoDB (this post)
  3. Frontend side with Angular and EventSource
  4. Running the application, Comparing WebFlux and MVC, and Conclusions
Do you prefer the print-friendly version of the guide? Get access now to the mini-book (37 pages) at a reduced price. Download your copy on LeanPub.

In this post

You’ll build (or analyze the existing code if you prefer) a Spring Boot 2.0 application that uses two different backend approaches:

  1. A reactive style, using WebFlux and a MongoDB ReactiveCrudRepository.
  2. The classical stack, using a blocking REST controller and a blocking query to MongoDB.

Additionally, we’ll need:

  • Docker, to simplify MongoDB deployment.
  • A class to pre-load the data into MongoDB. For that we'll use a CommandLineRunner implementation.

Before that, we’ll introduce some topics for a better understanding for those not familiar with reactive programming. If you already have experience, 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 give it a star!

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 differ between these Fluxes and Monos and 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, say numbers, maps them applying an operation, and filters them according to a threshold… nothing happens. It will start doing all the operations only when a subscriber starts pulling data. Furthermore, the command processing in the Flux is always triggered by the subscriber when it asks for more elements (applying the backpressure concept).

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 naturally 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 revert back to a full push-approach like in the Java Streams API.

To deep dive into Reactor’s knowledge, this article is a good starting point. The Project Reactor official docs are also a very good source of information.

Get the book Practical Software Architecture

Fluxes and Monos

In our scenario, we use the Flux class. It implements the Publisher interface and, as we briefly introduced, it is just a reactive stream that pushes items 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 subset of methods so, for instance, concatenating a Mono with another Mono, or a Flux, will return a Flux.

Reactor Integrations

Spring is including Reactor in some of their libraries, thus applying reactive programming patterns to them. Following a smart approach, they’re not getting rid of the previous programming style so don’t worry, updating to Spring 5 doesn’t mean being forced to adopt reactive programming.

The first big framework into which Spring is leveraging Reactor is, as you may guess, the Web framework. Starting with the version 5 you can use Spring WebFlux, which comes with major updates like a new way of declaring the 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 you can perform reactive queries to databases like MongoDB, which already have a reactive version of its driver, and pull data in a flow controlled by the subscribers instead of pushing the query results.

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 to 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, Spring 5 still allows you to use the @Controller and @RequestMapping annotations, so you can actually decide.

Spring WebFlux Alternatives Spring WebFlux Alternatives - Image based on the one available on the official docs

In this guide, I’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 of the convenience of that new style of declaring routes (feel free to judge for yourself).

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 a RestTemplate.

As described in the first part of this guide, we won’t use here this WebClient to avoid endogamic Java-Spring examples. We’ll create an Angular frontend to consume the endpoints, and detail what we get and what we don’t get when compared to 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 and the Reactive MongoDB dependencies.

Spring Boot 2.0 can integrate 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: we don’t include the MVC 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, and we’ll have available the Reactor API during development.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
    </dependency>
    <!-- ... -->
</dependencies>

On the other hand, the Reactive MongoDB dependencies are included with the starter spring-boot-starter-data-mongodb-reactive. This one pulls the Spring Data MongoDB dependency, containing Spring Data Commons 2.0 with reactive support, and the asynchronous and reactive-streams versions of the MongoDB driver.

Planning our way

Reactive Project StructureYou’ll create a typical layered backend application. Since there is no business logic involved, the Controllers will use directly the repositories. Besides, you’ll create two parallel ways of retrieving data from the Mongo database: the reactive way and the classic way. In any case, we’ll focus on the reactive part of it while explaining the ideas.

See the image for the final structure of the backend application (as you find it on GitHub).

We have twice the Repository and the Controller elements as our main logic, and a Quote class representing our domain. Our intention is to expose a list of Quotes using a Reactive Web API - QuoteReactiveController and QuoteMongoReactiveRepository -, and compare it with the classic blocking REST API  - QuoteBlockingController and QuoteMongoBlockingRepository. We need to implement a CorsFilter to enable cross-origin requests to simplify connection from the frontend, and we create the data loader (QuijoteDataLoader) too inside the same project (so it’ll create the quotes at startup into MongoDB if they’re not there yet).

We’ll implement two endpoints per controller: one that retrieves all the quotes available in the repository and another that supports pagination. This is useful for us to compare both approaches in a more realistic scenario, in which pagination is usually required.

Spring WebFlux Reactive UML

Repository Layer

Reactive Repository with Spring 5 and Spring Data

To implement a truly Full Reactive Stack we’ll use a MongoDB datastore using its reactive driver. The QuoteMongoRepository implementation consumes quotes from a Spanish masterpiece: Don Quixote. Therefore, this repository data is the source of our Flux.

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

Since we want the paged version of findAll, we define a new interface method that uses a Pageable and has a defined query to return all the quotes with an id - thus all of them. See the MongoDB documentation if you’re curious about this json query specification. Note that, to avoid side effects, you shouldn’t name your method starting with find nor query, otherwise you’ll activate the Query Methods feature.

package com.thepracticaldeveloper.reactiveweb.repository;

import com.thepracticaldeveloper.reactiveweb.domain.Quote;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import reactor.core.publisher.Flux;

public interface QuoteMongoReactiveRepository extends ReactiveCrudRepository<Quote, String> {

    @Query("{ id: { $exists: true }}")
    Flux<Quote> retrieveAllQuotesPaged(final Pageable page);
}

Both the findAll and the retrieveAllQuotesPaged 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.

Saving entities in a reactive manner

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

// This is the signature of CrudRepository::save
S save(S entity);

// And this is the one in ReactiveCrudRepository
Mono<S> save(S entity);

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:

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 stored. The reason is simple: the latter is returning a Mono, which is a Publisher, and it won’t start working until you subscribe to it. If you want to keep the blocking behavior that CrudRepository offers, you need to call instead quoteRepository.save(quote).block(). Anyway, we’ll see an example of this while explaining the QuijoteDataLoader class.

The Quote class

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

Get the book Practical Software Architecture
Quote
package com.thepracticaldeveloper.reactiveweb.domain;

public final class Quote {

    private String id;
    private String book;
    private String content;

    // Empty constructor is needed for Jackson to deserialize JSON
    public Quote() {
    }

    public Quote(String id, String book, String content) {
        this.id = id;
        this.book = book;
        this.content = content;
    }

    public String getId() {
        return id;
    }

    public String getBook() {
        return book;
    }

    public String getContent() {
        return content;
    }

}

The Reactive Controller

Controller Code

Let’s focus now on the most important part of our backend application for the purpose we have in this post: the Reactive Controller.

package com.thepracticaldeveloper.reactiveweb.controller;

import com.thepracticaldeveloper.reactiveweb.domain.Quote;
import com.thepracticaldeveloper.reactiveweb.repository.QuoteMongoReactiveRepository;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.time.Duration;

@RestController
public class QuoteReactiveController {

    private static final int DELAY_PER_ITEM_MS = 100;

    private QuoteMongoReactiveRepository quoteMongoReactiveRepository;

    public QuoteReactiveController(final QuoteMongoReactiveRepository quoteMongoReactiveRepository) {
        this.quoteMongoReactiveRepository = quoteMongoReactiveRepository;
    }

    @GetMapping("/quotes-reactive")
    public Flux<Quote> getQuoteFlux() {
        // If you want to use a shorter version of the Flux, use take(100) at the end of the statement below
        return quoteMongoReactiveRepository.findAll().delayElements(Duration.ofMillis(DELAY_PER_ITEM_MS));
    }

    @GetMapping("/quotes-reactive-paged")
    public Flux<Quote> getQuoteFlux(final @RequestParam(name = "page") int page,
                                    final @RequestParam(name = "size") int size) {
        return quoteMongoReactiveRepository.retrieveAllQuotesPaged(PageRequest.of(page, size))
                .delayElements(Duration.ofMillis(DELAY_PER_ITEM_MS));
    }

}

If you’re familiar with Spring Controllers, you’ll find out quickly that the only part of the code that seems new is the Flux object we’re returning as a result of the methods. In Spring MVC, we would return a List (Iterable) instead (which we can still use, as we’ll see later).

Note that, if we would have chosen the Router Functions alternative instead of the annotated controllers (@RestController and @GetMapping annotations), the implementation would be quite different. Anyhow, the resulting functionality would be exactly the same.

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 things like an irregular network latency or an overloaded server. To keep it simple, we’ll go for the latter and play the situation in which every quote has a processing time of 100 milliseconds. It will take almost ten minutes (>5000 quotes * 100 ms each) to retrieve the whole data; if you want to try a smaller set of quotes you can tell your publisher to take only the first N elements (as described in the code’s comment).

This will also help us visualize the differences between Reactive and MVC strategies. Since we’ll run client and server on the same machine, if we don’t introduce the delay the response times are so good that it would be hard to see the differences.

Pagination

When we created the Repository we introduced an additional method to retrieve results in pages. 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.

Note that in Spring MVC you don’t need to explicitly create the page and size parameters as I’m doing in this case. However, I didn’t get the automatic conversion of parameters into a Pageable working when I specified that as the endpoint’s method argument so I chose the explicit way. In any case, see how easy is to get a Pageable just by calling PageRequest.of(page, size). Let me know via comments if you find how to do that with automatic conversion.

Enabling CORS in Spring WebFlux

We want to connect to the backend from a client application deployed in a different port (at least during development). In Web terms, that means that the frontend part of our application has an origin which is different from the backend. Unless we explicitly state that as valid in our configuration, that connection is going to be refused.

To enable CORS with WebFlux we need to implement a new reactive-like WebFilter (returns a Mono) to set some headers related with access control. The most important one is Access-Control-Allow-Origin, which we set to allow any (which is good only for development purposes).

@Component
public class CorsFilter implements WebFilter {

    @Override
    public Mono<Void> filter(final ServerWebExchange serverWebExchange, final WebFilterChain webFilterChain) {
        // Adapted from https://sandstorm.de/de/blog/post/cors-headers-for-spring-boot-kotlin-webflux-reactor-project.html
        serverWebExchange.getResponse().getHeaders().add("Access-Control-Allow-Origin", "*");
        serverWebExchange.getResponse().getHeaders().add("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS");
        serverWebExchange.getResponse().getHeaders().add("Access-Control-Allow-Headers", "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range");
        if (serverWebExchange.getRequest().getMethod() == HttpMethod.OPTIONS) {
            serverWebExchange.getResponse().getHeaders().add("Access-Control-Max-Age", "1728000");
            serverWebExchange.getResponse().setStatusCode(HttpStatus.NO_CONTENT);
            return Mono.empty();
        } else {
            serverWebExchange.getResponse().getHeaders().add("Access-Control-Expose-Headers", "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range");
            return webFilterChain.filter(serverWebExchange);
        }
    }
}

Apart from that header, you can see in this snippet that we’re setting some others. They are related to the process used by the browser to open a channel with the server (SSE). What we want for this experiment is to allow any HTTP method, and all related headers. Important note: don’t do this in production environments. Be more restrictive and adapt this configuration to your case, e.g. don’t use * as allowed origins but the specific hosts instead.

Returning a Flux from a Controller: behind the scenes

Let’s dive a bit more into details. The new Spring WebFlux documentation lists the reactive types (Flux included) as possible return types. But, what happens when we request content to the server? How does WebFlux convert it into a valid response? It actually depends on how we request it:

  • If we request content without using an Accept header, or setting it to application/json, we'll get a synchronous, JSON-formatted response.
  • If we want to use the Server-Sent Events support in Spring to implement our full reactive stack, we set in our request (explicitly or behind the scenes) the Accept header to text/event-stream, therefore activating the reactive functionality in Spring.

We’ll test this within this post, once we have our backend set up.

The blocking Controller and Repository

To better show the comparison between the blocking and reactive approaches, let’s create a separate Controller with specific paths, and connect them to a standard CrudRepository. This code is pretty straightforward and well-known to all developers familiar with MVC.

Here is the Controller. Note that here we also apply the same delay as in the reactive approach, this time all at once since the query is returning results also as a whole pack.

package com.thepracticaldeveloper.reactiveweb.controller;

import com.thepracticaldeveloper.reactiveweb.domain.Quote;
import com.thepracticaldeveloper.reactiveweb.repository.QuoteMongoBlockingRepository;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class QuoteBlockingController {

    private static final int DELAY_PER_ITEM_MS = 100;

    private QuoteMongoBlockingRepository quoteMongoBlockingRepository;

    public QuoteBlockingController(final QuoteMongoBlockingRepository quoteMongoBlockingRepository) {
        this.quoteMongoBlockingRepository = quoteMongoBlockingRepository;
    }

    @GetMapping("/quotes-blocking")
    public Iterable<Quote> getQuotesBlocking() throws Exception {
        Thread.sleep(DELAY_PER_ITEM_MS * quoteMongoBlockingRepository.count());
        return quoteMongoBlockingRepository.findAll();
    }

    @GetMapping("/quotes-blocking-paged")
    public Iterable<Quote> getQuotesBlocking(final @RequestParam(name = "page") int page,
                                             final @RequestParam(name = "size") int size) throws Exception {
        Thread.sleep(DELAY_PER_ITEM_MS * size);
        return quoteMongoBlockingRepository.retrieveAllQuotesPaged(PageRequest.of(page, size));
    }
}
Get the book Practical Software Architecture

The Repository is very similar to reactive, but this time we extend CrudRepository and return a List:

package com.thepracticaldeveloper.reactiveweb.repository;

import com.thepracticaldeveloper.reactiveweb.domain.Quote;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.repository.CrudRepository;

import java.util.List;

public interface QuoteMongoBlockingRepository extends CrudRepository<Quote, String> {

    @Query("{ id: { $exists: true }}")
    List<Quote> retrieveAllQuotesPaged(final Pageable page);
}

Loading data into MongoDB with a CommandLineRunner

We have all the code we need to run our Spring Boot application. But we don’t have yet any way of storing quotes so 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.

In the project 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 a CommandLineRunner implementation in the application’s context: the QuijoteDataLoader class.

package com.thepracticaldeveloper.reactiveweb.configuration;

import com.thepracticaldeveloper.reactiveweb.domain.Quote;
import com.thepracticaldeveloper.reactiveweb.repository.QuoteMongoReactiveRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.function.LongSupplier;

@Component
public final class QuijoteDataLoader implements CommandLineRunner {

    private static final Logger log = LoggerFactory.getLogger(QuijoteDataLoader.class);

    private QuoteMongoReactiveRepository quoteMongoReactiveRepository;

    QuijoteDataLoader(final QuoteMongoReactiveRepository quoteMongoReactiveRepository) {
        this.quoteMongoReactiveRepository = quoteMongoReactiveRepository;
    }

    @Override
    public void run(final String... args) throws Exception {
        if (quoteMongoReactiveRepository.count().block() == 0L) {
            final LongSupplier longSupplier = new LongSupplier() {
                Long l = 0L;

                @Override
                public long getAsLong() {
                    return l++;
                }
            };
            BufferedReader bufferedReader = new BufferedReader(
                    new InputStreamReader(getClass().getClassLoader().getResourceAsStream("pg2000.txt")));
            Flux.fromStream(
                    bufferedReader.lines().filter(l -> !l.trim().isEmpty())
                            .map(l -> quoteMongoReactiveRepository.save(new Quote(String.valueOf(longSupplier.getAsLong()), "El Quijote", l)))
            ).subscribe(m -> log.info("New quote loaded: {}", m.block()));
            log.info("Repository contains now {} entries.", quoteMongoReactiveRepository.count().block());
        }
    }

}

There are several aspects that might be interesting to point out:

  • 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.
  • We apply a reactive pattern to subscribe to the result of save() in ReactiveCrudRepository. Remember that, if we don't consume the result, the quote is not stored.

Running the backend

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!

Running MongoDB with Docker

This step is optional, you can also install MongoDB on your machine and it’ll work the same way. However, in a next post I’ll show you how to run the complete reactive system using Docker so it’s good to follow this approach to get a first contact with it.

You need to install Docker if you haven’t done it yet. Then, create a file named docker-compose.yml with this content:

version: "2"

services:
  mongo:
    image: mongo:3.4
    hostname: mongo
    ports:
      - "27017:27017"
    volumes:
      - mongodata:/data/db

volumes:
  mongodata:

I’ll explain this in more details in the dedicated post/section of 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 volume too, and the quotes data will remain after stopping the container. To run this container, you need to execute:

$ docker-compose 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 IDE to run the ReactiveWebApplication class, or we can use the included maven wrapper from the spring-boot-reactive-web folder:

(on Linux or Mac)  ./mvnw spring-boot:run
(on Windows)       mvnw spring-boot:run

If everything goes well you should see messages like these on the console:

INFO 1546 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
INFO 1546 --- [      Thread-14] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:3, serverValue:13}] to localhost:27017
INFO 1546 --- [           main] c.t.reactiveweb.ReactiveWebApplication   : Started ReactiveWebApplication in 4.094 seconds (JVM running for 4.739)

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, you’ll need either a Linux shell simulator (like Git Bash, included with the official Git distribution package, or Cygwin) or to download cURL from this site.

Blocking call

Then we try the paged version of the reactive endpoint:

$ curl "http://localhost:8080/quotes-reactive-paged?page=0&size=50"

What’s going wrong? It will take 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.

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 in the shape of a Server-Sent Events channel, we need to ask for a text/event-stream instead.

Streaming call

To test a stream approach, we just need to add a header:

$ curl -H "Accept: text/event-stream" "http://localhost:8080/quotes-reactive-paged?page=0&size=50"

data:{"id":"1002","book":"El Quijote","content":"En tanto que don Quijote estaba diciendo lo que queda dicho, se le había caído a Cardenio la cabeza sobre el pecho, dando muestras de estar profundamente pensativo. Y, puesto que dos veces le dijo don Quijote que prosiguiese su historia, ni alzaba la cabeza ni respondía palabra; pero, al cabo de un buen espacio, la levantó y dijo: "}

data:{"id":"1003","book":"El Quijote","content":"-No se me puede quitar del pensamiento, ni habrá quien me lo quite en el mundo, ni quien me dé a entender otra cosa (y sería un majadero el que lo contrario entendiese o creyese), sino que aquel bellaconazo del maestro Elisabat estaba amancebado con la reina Madésima. "}

data:{"id":"1004","book":"El Quijote","content":"-Eso no, ¡voto a tal! -respondió con mucha cólera don Quijote (y arrojóle, como tenía de costumbre)-; y ésa es una muy gran malicia, o bellaquería, por mejor decir: la reina Madásima fue muy principal señora, y no se ha de presumir que tan alta princesa se había de amancebar con un sacapotras; y quien lo contrario entendiere, miente como muy gran bellaco. Y yo se lo daré a entender, a pie o a caballo, armado o desarmado, de noche o de día, o como más gusto le diere. "}

...

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. We’re not employing any kind of backpressure here so our client is 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 call http://localhost:8080/quotes-blocking and, 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" will also take almost ten minutes to finish but, in this reactive scenario, we're getting quotes as soon as they're dispatched.

At this point we got a very important conclusion: 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 is able to handle it as well.

We’ll implement the real Reactive Web Client in the next section of this guide. And, as promised, we’ll get out of Spring Boot and Reactor to implement the client code so we make it more realistic.

Continue reading the Guide

You can now keep reading this Guide: The Frontend Reactive Client with Angular, or jump directly to the conclusions and benchmark.

Do you prefer the print-friendly version of the guide? Get access now to the mini-book (37 pages) at a reduced price. Download your copy on LeanPub.

Continue reading:

Moisés Macero's Picture

About Moisés Macero

Software Developer, Architect, and Author.
Do you need help?

Amsterdam, The Netherlands https://thepracticaldeveloper.com