10

Working with Data Reactively

In the previous chapter, we learned how to write a reactive web controller using Spring WebFlux. We loaded it with canned data and used a reactive templating engine, Thymeleaf, to create an HTML frontend. We also created a reactive API with pure JSON and then with hypermedia using Spring HATEOAS. However, we had to use canned data. That’s because we didn’t have a reactive data store on hand, an issue we will solve in this chapter.

In this chapter, we’ll be covering the following topics:

  • Learning what it means to fetch data reactively
  • Picking a reactive data store
  • Creating a reactive data repository
  • Trying out R2DBC

Where to find this chapter’s code

The code for this chapter can be found in this repository: https://github.com/PacktPublishing/Learning-Spring-Boot-3.0/tree/main/ch10.

Learning what it means to fetch data reactively

In the previous chapter, we covered a lot of the basics needed to build a web page reactively, except we were missing a vital ingredient: real data.

Real data comes from databases.

There are few applications that don’t use a database to manage their data. And in this era of e-commerce sites serving global communities, there has never been a broader choice of various types of databases, be they relational, key-value, document, or whatever.

This can make it tricky to pick the right one for our needs. It’s even harder considering that even a database needs to be accessed reactively.

That’s right. If we don’t access our database using the same reactive tactics introduced in the previous chapter, all of our efforts would be for naught. To repeat a key point: all parts of the system must be reactive. Otherwise, we risk a blocking call tying up a thread and clobbering our throughput.

Project Reactor’s default thread pool size is the number of cores on the operating machine. That’s because we’ve seen that context switching is expensive. By having no more threads than cores, we’re guaranteed to never have to suspend a thread, save its state, activate another thread, and restore its state.

By taking such an expensive operation off the table, reactive applications can instead focus on the more effective tactic of simply going back to Reactor’s runtime for the next task (a.k.a. work stealing). However, this is only possible when we use Reactor’s Mono and Flux types along with their various operators.

If we ever invoke some blocking call on a remote database, the entire thread will halt, waiting for an answer. Imagine a four-core machine having one of its cores blocked like that. A four-core system suddenly only using three cores would show an instant 25% drop in throughput.

This is the reason that database systems across the spectrum are implementing alternative drivers that use the Reactive Streams specification: MongoDB, Neo4j, Apache Cassandra, Redis, and more.

But what, exactly, does a reactive driver look like? Database drivers handle the process of opening a connection to a database, parsing queries and turning them into commands, and finally, shepherding the results back to the caller. Reactive Streams-based programming has been growing in popularity, which has motivated these various vendors to build reactive drivers.

But one area that is stuck is JDBC.

When it comes to Java, all toolkits, drivers, and strategies go through JDBC to talk to a relational database. jOOQ, JPA, MyBatis, and QueryDSL all use JDBC under the hood. And because JDBC is blocking, it simply won’t work in a reactive system.

Are you sure?

People have asked in various ways why can’t we just carve out a JDBC thread pool and put a reactor-friendly proxy in front of it. The truth is, while each incoming request could be dispatched to a thread pool, you run into the risk of hitting the pool’s limit. At that point, the next reactive call would be blocked, waiting for a thread to free up, effectively clobbering the whole system. The point of reactive systems is to NOT block but instead yield so that other work can be done. A thread pool simply delays the inevitable while costing you the overhead of context switching. A database driver, right up to the point of talking to the database engine itself, needs to speak Reactive Streams or it won’t cut it.

JDBC being a spec and not simply a driver makes this all but impossible. But there’s hope, as we’ll see in the next section.

Picking a reactive data store

Realizing that JDBC wouldn’t be able to change enough to support Reactive Streams and needing to serve the growing community of Spring users that wanted to go reactive, the Spring team embarked upon a new solution in 2018. They drafted the Reactive Relational Database Connectivity (R2DBC) specification.

R2DBC as a specification reached 1.0 in early 2022, and for the rest of this chapter, we’ll be using it to build a reactive relational data story.

R2DBC? Tell me more!

If you want a little more detail about R2DBC, please check out former Spring Data team leader Oliver Drotbohm’s keynote presentation from the 2018 SpringOne conference at https://springbootlearning.com/r2dbc-2018.

Since we want something super simple, we can use H2 as our relational database of choice. H2 is an in-memory, embeddable database. It’s frequently used for test purposes, but we can use it as a production application for now.

Along with H2, we’ll also use Spring Data R2DBC. To get both of these, let’s visit our old friend, https://start.spring.io. If we pick the same version of Spring Boot as the previous chapter and plug in the same metadata, we can then choose the following dependencies:

  • H2 Database
  • Spring Data R2DBC

If we then click on the EXPLORE button and scroll about halfway down the pom.xml file, we should see the following three entries:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-h2</artifactId>
    <scope>runtime</scope>
</dependency>

These three dependencies can be described as follows:

  • spring-boot-starter-data-r2dbc: Spring Boot’s starter for Spring Data R2DBC
  • h2: The third-party embeddable database
  • r2dbc-h2: The Spring team’s R2DBC driver for H2

It’s important to understand that R2DBC is very low-level. It’s fundamentally aimed at making it easy for database driver authors to implement. Certain aspects of JDBC as a driver interface were compromised to make it easier to consume by applications. R2DBC has sought to remedy this. The consequence is that having an application talk directly through R2DBC is actually quite cumbersome.

That’s why it’s recommended to use a toolkit. In this case, we’ll be using Spring Data R2DBC, but you can pick another that you’d prefer, such as Spring Framework’s DatabaseClient or another third-party one.

With our tools set up, it’s time to start building a reactive data repository!

Creating a reactive data repository

Earlier in Chapter 3, Querying for Data with Spring Boot, we built an easy-to-read data repository by extending JpaRepository from Spring Data JPA. For Spring Data R2DBC, let’s write something like this:

public interface EmployeeRepository extends //
  ReactiveCrudRepository<Employee, Long> {}

This code can be described as follows:

  • EmployeeRepository: The name for our Spring Data repository.
  • ReactiveCrudRepository: Spring Data Commons’ base interface for any reactive repository. Note that this isn’t specific to R2DBC but instead for ANY reactive Spring Data module.
  • Employee: The domain type for this repository (which we’ll code further in the chapter).
  • Long: The primary key’s type.

In the previous chapter, we scratched together an Employee domain type using a Java 17 record. However, to interact with our database, we need something a little more detailed than that, so let’s craft the following:

public class Employee {
  private @Id Long id;
  private String name;
  private String role;
  public Employee(String name, String role) {
    this.name = name;
    this.role = role;
}
  // getters, setters, equals, hashCode, and toString 
     omitted for brevity
}

This code can be described as follows:

  • Employee: Our domain’s type, as required in the EmployeeRepository declaration.
  • @Id: Spring Data Commons’ annotation to denote the field that contains the primary key. Note that this is NOT JPA’s jakarta.persistence.Id annotation but instead a Spring Data-specific annotation.
  • name and role: The two other fields we’ll be using.

The rest of this domain type’s methods can be generated by any modern IDE using its utilities.

With all this, we’re ready to start speaking R2DBC!

Trying out R2DBC

Before we can fetch any data, we have to load some data. While this is normally something our DBA deals with, for this chapter we’ll just have to do it ourselves. To do that, we need to create a Spring component that will automatically kick off once our application is up. Create a new class named Startup and add the following code:

@Configuration
public class Startup {
  @Bean
  CommandLineRunner initDatabase(R2dbcEntityTemplate 
    template) {
      return args -> {
        // Coming soon!
      }
    }
}

This code can be described as follows:

  • @Configuration: Spring’s annotation to flag this class as a collection of bean definitions, needed to autoconfigure our application
  • @Bean: Spring’s annotation to turn this method into a Spring bean, added to the application context
  • CommandLineRunner: Spring Boot’s functional interface for an object that is automatically executed once the application is started
  • R2dbcEntityTemplate: Inject a copy of this Spring Data R2DBC bean so we can load a little test data
  • args -> {}: A Java 8 lambda function that is coerced into CommandLineRunner

What do we put inside this Java 8 lambda function? Well, for Spring Data R2DBC, we need to define the schema ourselves. If it’s not already defined externally (which it isn’t for this chapter), we need to write something like this:

    template.getDatabaseClient() //
      .sql("CREATE TABLE EMPLOYEE (id IDENTITY NOT NULL 
        PRIMARY KEY , name VARCHAR(255), role 
          VARCHAR(255))") //
      .fetch() //
      .rowsUpdated() //
      .as(StepVerifier::create) //
      .expectNextCount(1) //
      .verifyComplete();

This chunk of code can be described as follows:

  • template.getDatabaseClient(): For pure SQL, we need access to the underlying DatabaseClient from Spring Framework’s R2DBC module that does all the work.
  • sql(): A method to feed a SQL CREATE TABLE operation. This uses H2’s dialect to create an EMPLOYEE table with a self-incrementing id field.
  • fetch(): The operation that carries out the SQL statement.
  • rowsUpdate(): Gets back the number of rows affected so we can verify that it worked.
  • as(StepVerifier::create): Reactor Test’s operator to convert this whole reactive flow into StepVerifier. StepVerifier is another way to conveniently force a reactive flow to be executed.
  • expectNextCount(1): Verifies whether we got back one row, indicating the operation worked.
  • verifyComplete(): Ensures we received a Reactive Streams onComplete signal.

The preceding method lets us run a little bit of SQL code in order to create our barebones schema. And it may have gotten a little confusing when, halfway down, we pivoted into Reactor Test’s StepVerifier.

StepVerifier is very handy for testing out reactor flows, but it also affords us a useful means to force small reactor flows while allowing us to see the results when needed. The only problem is that we can’t use this because, by default, reactor-test is test-scoped when we use the Spring Initializr. To make the preceding code work, we must go into our pom.xml file and remove the <scope>test</scope> line. Then, refresh the project, and it should work!

Now, with this in place, let’s load some data.

Loading data with R2dbcEntityTemplate

So far, we’ve set up the schema for our Employee domain type. With that in place, we’re ready to add some more R2dbcEntityTemplate calls inside that initDatabase() CommandLineRunner:

template.insert(Employee.class) //
  .using(new Employee("Frodo Baggins", "ring bearer"))
  .as(StepVerifier::create) //
  .expectNextCount(1) //
  .verifyComplete();
template.insert(Employee.class) //
  .using(new Employee("Samwise Gamgee", "gardener")) //
  .as(StepVerifier::create) //
  .expectNextCount(1) //
  .verifyComplete();
template.insert(Employee.class) //
  .using(new Employee("Bilbo Baggins", "burglar")) //
  .as(StepVerifier::create) //
  .expectNextCount(1) //
  .verifyComplete();

These three calls all have the same pattern. Each can be described as follows:

  • insert(Employee.class): Defines an insert operation. By providing a type parameter, the subsequent operations are typesafe.
  • using(new Employee(…)): Here is where the actual data is provided.
  • as(StepVerifier::create): The same pattern of using Reactor Test to force the execution of our reactive flow.
  • expectNextCount(1): For a single insert, we are expecting a single response.
  • verifyComplete(): Verifies whether we received the onComplete signal.

The insert() operation actually returns Mono<Employee>. It would be possible to inspect the results, even getting hold of the newly minted id value. But since we’re just loading data, all we want is to confirm that it worked.

In the next section, we’ll see how to hook up our reactive data supply to an API controller.

Returning data reactively to an API controller

The heavy work has been done. From here on in, we can tap into what we learned in the previous chapter. To build an API controller class, create a class named ApiController, as shown here:

@RestController
public class ApiController {
  private final EmployeeRepository repository;
  public ApiController(EmployeeRepository repository) {
    this.repository = repository;
  }
}

This API controller class be described as follows:

  • @RestController: Spring’s annotation to signal that this class doesn’t process templates but instead that all outputs are directly serialized to the HTML response
  • EmployeeRepository: We are injecting the repository we defined earlier in this section through constructor injection

The simplest thing of all is to return all the Employee records we have. This can easily be done by adding the following method to our ApiController class:

    @GetMapping("/api/employees")
    Flux<Employee> employees() {
      return repository.findAll();
    }

This web method is quite simple:

  • @GetMapping: Maps HTTP GET /api/employees calls to this method
  • Flux<Employee>: Indicates that this returns one (or more) Employee records
  • repository.findAll(): By using the prebuilt findAll method from Spring Data Commons’ ReactiveCrudRepository interface, we already have a method that will fetch all the data

The previous chapter had a simple Java Map, which required some finagling to make it work reactively. Because EmployeeRepository extends ReactiveCrudRepository, it’s already got the reactive types baked into its method’s return types – no finagling needed!

This also means that coding an API-based POST operation can be done like this:

    @PostMapping("/api/employees")
    Mono<Employee> add(@RequestBody Mono<Employee> 
      newEmployee) {
        return newEmployee.flatMap(e -> {
          Employee employeeToLoad = 
            new Employee(e.getName(), e.getRole());
          return repository.save(employeeToLoad);
        });
    }

This web method has the following features:

  • @PostMapping(): Maps HTTP POST /api/employees calls to this method.
  • Mono<Employee>: This method returns, at most, one entry.
  • @RequestBody(Mono<Employee>): This method will deserialize the incoming request body into an Employee object, but because it’s wrapped as Mono, this processing only happens when the system is ready.
  • newEmployee.flatMap(): This is how we access the incoming Employee object. Inside the flatMap operation, we actually craft a brand-new Employee object, deliberately dropping any id value provided in the input. This ensures a completely new entry will be made to the database.
  • repository.save(): Our EmployeeRepository will execute a save operation and return Mono<Employee>, with a newly created Employee object inside. This new object will have everything, including a fresh id field.

If you’re new to reactive programming, the ceremony of these preceding bullet points may be a little confusing. For example, why are we flatMapping? Mapping is usually used when converting from one type to another. In this situation, we’re trying to map from an incoming Employee to a newly saved Employee type. So why don’t we just map?

That’s because what we got handed back from save() wasn’t anEmployee object. It was Mono<Employee>. If we had mapped over it, we’d have Mono<Mono<Employee>>.

flatMap is Reactor’s golden hammer

A lot of times, when you’re not sure what to do, or the Reactor APIs seem to be working against you, the secret is often flatMap(). All the Reactor types are heavily overloaded to support flatMap such that Flux<Flux<?>>, Mono<Mono<?>>, and every combination of them will nicely work out when you simply apply flapMap(). This also applies if you use Reactor’s then() operator. Using flatMap() before you use then() will usually ensure the previous step is carried out!

The last step in putting together our reactive web app is to populate our Thymeleaf template, which we’ll tackle in the next section.

Reactively dealing with data in a template

To finish things off, we need to create a HomeController class, like this:

@Controller
public class HomeController {
  private final EmployeeRepository repository;
  public HomeController(EmployeeRepository repository) {
    this.repository = repository;
  }
}

This class has some key aspects:

  • @Controller: Indicates that this controller class is focused on rendering templates
  • EmployeeRepository: Our beloved EmployeeRepository is also injected into this controller using constructor injection

With this in place, we can generate the web template served up at the root of our domain using this:

    @GetMapping("/")
    Mono<Rendering> index() {
      return repository.findAll() //
        .collectList() //
        .map(employees -> Rendering //
           .view("index") //
           .modelAttribute("employees", employees) //
           .modelAttribute
            ("newEmployee", new Employee("", "")) 
          .build());
    }

This is almost the same as the previous chapter’s index() method except for the highlighted fragment:

  • repository.findAll(): Instead of converting a map’s values into Flux, EmployeeRepository already gives us one with its findAll() method

Everything else is the same.

Now, in order to process that form-backed Employee bean, we need a POST-based web method like this:

    @PostMapping("/new-employee")
    Mono<String> newEmployee(@ModelAttribute Mono<Employee> 
      newEmployee) {
        return newEmployee //
          .flatMap(e -> {
            Employee employeeToSave = 
              new Employee(e.getName(), e.getRole());
            return repository.save(employeeToSave);
          }) //
          .map(employee -> "redirect:/");
    }

This is very similar to the previous chapter’s newEmployee() method, except for the highlighted parts:

  • flatMap(): As mentioned in the previous section, because save() returns a Mono<Employee>, we need to flatMap the results.
  • The previous section also showed how we extract the name and role from the incoming Employee object but ignore any possible id value, since we’re inserting a new entry. We then return the results of our repository’s save() method.
  • map(employee -> "redirect:/"): Here, we translate the saved Employee object into a redirect request.

Something that’s important to point out, when compared to the previous chapter, is that in the preceding code, we split things up. In the previous chapter, we essentially mapped the incoming Employee object into a redirect request. That’s because our mock database was non-reactive and only needed an imperative call to store the data.

Because our EmployeeRepository in this chapter is reactive, we need to split this up, with one operation focused on save(), followed by the next one, to convert that outcome to the redirect request.

Also, we had to use flatMap because the response from save() was wrapped inside a Reactor Mono class. Converting an employee to "redirect:/" doesn’t involve any Reactor types, so simple mapping is all we need.

To get the index.html template, you can simply copy it from the previous chapter. Since it’s the same as the previous chapter, there is no need to write it out here – no changes required! (Alternatively, grab it from the source code listing shown at the start of this chapter.)

And with that, we have a fully armed and operational reactive data store!

Summary

In this chapter, we learned about what it means to fetch data reactively. Based upon that, we selected a reactive data store and leveraged Spring Data to help manage our content. After hooking things into our reactive web controller, we then took a peek at R2DBC, a reactive driver for relational databases. And with that, we were able to build an introductory reactive Spring Boot application.

The same tactics used in earlier chapters for deployment will work just as well. In addition to that, many of the features we used throughout this book also work.

With all the things covered in this book, you should be prepared to take on your next (or perhaps current) project using Spring Boot 3.0. I sincerely hope Spring Boot 3.0 makes you as excited as I am about building new apps.

In the meantime, if you want to explore even more content regarding Spring Boot, check out the following resources:

Happy coding!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset