Reactive Spring Data Introduction

When building applications, we often want to save information in a database. Databases are notoriously I/O-bound, meaning that when a CPU has to wait for its results the application’s threads get blocked, and it is wasting a lot of valuable cycles. These cycles are far better off being used to do other useful work, like for example handling additional requests in a webserver. This can definitely help non-CPU-bound applications to scale to more concurrent users. This of course heavily contrasts the way we are used to dealing with a database – where we are used to running a query and getting a result right away.

In this article we will have a look at using the Reactive Spring Data MongoDB integration in a Spring Boot Application. We expect you to have basic experience with Spring Boot. We’ll be creating a small Proof of Concept application that will chain up a couple of database interactions. In it we clear up the database, add a couple of documents, and finally read them from the database. These flows will be in a declarative way, using Project Reactor to guide them.

To start off with, make sure you have a running MongoDB Database. In case you have multiple MongoDB databases running on your system, make sure you point to the correct one in your property file. It is also possible to use an embedded MongoDB, you can get one through the following dependency.

1
2
3
4
5
<dependency>
    <groupId>de.flapdoodle.embed</groupId>
    <artifactId>de.flapdoodle.embed.mongo</artifactId>
    <version>2.0.3</version>
</dependency>

Then let’s create a standard Spring Boot Application, and import an extra maven dependency in your pom.xml file (or use Spring Initializr). This Spring Starter will help us to have a Reactive MongoDB integration running swiftly.

1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>

Next up we require a data class. For this test application we’ll store a couple of ConcertTicket documents, for this, we can simply use a standard MongoDB Document class.

1
2
3
4
5
6
7
8
9
10
11
12
@Document
public class ConcertTicket {

    @Id
    public String id;

    private String artist;

    private String buyer;

    // Constructor, getters and setters, etc
}

We also require a special repository for our Reactive Data Access. This follows the same system that the standard Spring Data does, so only requires an interface from the developer.

1
2
public interface ConcertTicketRepository extends ReactiveMongoRepository<ConcertTicket, Long> {
}

So far, the setup of our application looks quite close to the general setup of a Spring application with a Spring Data MongoDB integration. The key difference is in the API though. When taking a closer look at some of the methods of this interface, we can see that the results of the queries aren’t returned as-is, but rather Fluxes and Monos are returned. This of course follows the Reactive Streams API offered by Project Reactor. When calling the findAll methods for example, we get a Flux result, meaning the data of the result will flow into our application in an event-driven way.

1
2
3
4
5
6
7
8
9
10
11
public interface ReactiveMongoRepository<T, ID> extends ReactiveSortingRepository<T, ID>, ReactiveQueryByExampleExecutor<T> {
  <S extends T> Mono<S> insert(S var1);

  <S extends T> Flux<S> insert(Iterable<S> var1);

  <S extends T> Flux<S> insert(Publisher<S> var1);

  <S extends T> Flux<S> findAll(Example<S> var1);

  <S extends T> Flux<S> findAll(Example<S> var1, Sort var2);
}

Now that we’ve gotten accustomed to our Reactive Repository interface a bit more, we can start to implement the body of our application logic.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@SpringBootApplication
public class ReactiveSpringDataStartApplication  throws InterruptedException {

    public static void main(String[] args) {
        ConfigurableApplicationContext application = SpringApplication.run(ReactiveSpringDataStartApplication.class, args);

        final ConcertTicketRepository concertTicketRepository = application.getBean(ConcertTicketRepository.class);

        final Mono<Void> emptyDatabaseFlow =
                concertTicketRepository.deleteAll();

        final Mono<Void> saveTicketsFlow =
                concertTicketRepository.save((new ConcertTicket("ABBA", "Jerry")))
                .and(concertTicketRepository.save((new ConcertTicket("Hawkwind", "Kris"))));

        final Flux<ConcertTicket> printConcertTicketsOverviewFlow =
                concertTicketRepository.findAll()
                .flatMap(ticket -> printTicketInformation(ticket));

        emptyDatabaseFlow
        .then(saveTicketsFlow)
        .thenMany(printConcertTicketsOverviewFlow)
        .subscribe();

        System.out.println("Will wait for the information to be printed from the database");
        Thread.sleep(1000);
    }

    private static Mono<ConcertTicket> printTicketInformation(ConcertTicket concertTicket) {
        System.out.println(String.format("Tick Artist: %s Buyer: %s", concertTicket.getArtist(), concertTicket.getBuyer()));
        return Mono.just(concertTicket);
    }
}

The application itself is quite simple.

When our application creates its application context, the component scanning offered by Spring will ensure that the Repository interface we created will automatically be picked up, and an instance will be created based on it. Next, we pick up this instance through the application context by getting the bean.

Afterwards, we start to create a couple of Reactive Flows. As discussed in previous articles, these flows are created in a declarative fashion, and will not be “triggered” automatically.

Finally we create an implicit fourth flow which connects the 3 flows we defined up front. We trigger the flows by calling the subscribe method on this flow, which will result in the database being emptied, concert tickets being saved, and concert ticket information being printed for the user to see. Finally we print a message and make our current Thread sleep (to make sure our application does not simply “end”).

When running the code, we get the following result:

1
2
3
Will wait for the information to be printed from the database
Ticket Artist: ABBA Buyer: Jerry
Ticket Artist: Hawkwind Buyer: Kris

This result shows that our program works as expected. We first set up a couple of flows in a declarative way. The flows make sure that the database gets emptied, elements get added, and then retrieved again to be printed out on the terminal. The database driver makes sure that a callback occurs when some new data gets returned, so the application can do other work in the mean time using a thread that would otherwise be blocked. We effectively set up a Reactive Stream from the database to our application in an event-driven way.

This sample application can be found on Github