Create reactive Java web services that scale from the database up using the EventSource API
Every developer knows that user patience is a precious commodity. If an application makes them wait, they will at best hit refresh and at worst case give up. Developing a high-performance application at scale requires efficient resource management. This article looks at how to solve these issues for Java applications using reactive programming and non-blocking requests from the database to the front end.
The reactive programming paradigm seeks to solve scalability problems in modern software applications by eliminating inefficient thread pools. While Project Loom might alleviate some of these problems in the future, reactive programming is more than a mechanism to avoid blocking threads. It offers a wide range of operators to compose complex computation graphs through a functional API that abstracts away threading complexities, and synchronous and asynchronous execution details.
To fully harness the technical advantages of reactive programming, an application must be reactive from the bottom up, that is, from the moment a user makes a request all the way down to any database interactions and the return of a response back to the user. This is particularly relevant in the case of web applications. In this scenario, there are two key moments—database access and HTTP response. How to make these two points non-blocking? In this article, I’ll show you how to implement a fully-reactive REST web service with database connectivity using Spring Boot and R2DBC, and how to invoke this service from the browser by using the EventSource JavaScript API.
The problem
Let’s study a typical request to a REST web service to understand why R2DBC is needed to develop fully-reactive applications. When a user makes a request to a web service, the server creates (or borrows from a pool) a thread. This thread is used to run all the logic required to fulfill the request. One of the actions that are required is a query to a database. In Java, we typically use a JDBC driver to connect to the database. JDBC has one limitation, though. It is blocking. This means that the server thread needs to wait before being able to process the data and start sending it back to the browser. This is depicted in the following diagram:
Let’s see the classic way of implementing this. Suppose we have a database with products that we sell online. We have a JPA Entity called Product
:
import javax.persistence.Entity; import javax.persistence.Id; @Entity public class Product { @Id private Long id ... more fields, getters, and setters ... }
We accompany this class with a Repository
interface:
import org.springframework.data.jpa.repository.JpaRepository; public interface ProductRepository extends JpaRepository<Product, Long> { }
There’s no need to implement this interface. Spring Data JPA will provide an implementation at runtime. A reference to this implementation is injected through a constructor in a service class. For example, the following service uses the previous repository to expose the products in the database through a REST web service:
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; import java.util.stream.Collectors; @RestController @RequestMapping("/api/products") public class ProductService { private final ProductRepository repository; public ProductService(ProductRepository repository) { this.repository = repository; } @GetMapping("/") public List<Product> findProducts() { return repository.findAll(); } }
The key point here is that the line in the findProducts()
method is blocked until all the products are returned by the database. The browser will have to wait as well. Here’s how the web service is consumed in JavaScript:
const response = await fetch("http://localhost:8080/api/products/");
There’s nothing much we can do in the browser while the server thread is waiting for the database. We have to wait for the response before being able to show something on the page.
The solution
R2DBC stands for Reactive Relational Database Connectivity and it’s an API specification that database vendors can implement to bring reactive programming to the relational database world. This makes database calls non-blocking in the web server, allowing more optimal use of computing power and avoiding the need for huge thread pools. When the server thread makes a database call (or any call to I/O for that matter), it doesn’t wait for it to finish in order to continue. Instead, it subscribes to an observer which notifies when data starts to be available. This can be handled row by row as they are made available by the database. In contrast with JDBC we have to wait until all the rows are ready before processing and returning them to the browser. R2DBC makes non-blocking calls to the database and immediately continues the execution of code. Here’s a depiction of what R2DBC “brings to the table”:
Transforming the web service implemented in the previous section is surprisingly easy when moving to R2DBC (real-world applications are much more complex and require more work). We won’t be able to use JPA with R2DBC since JPA depends on JDBC. However, Spring Data has an R2DBC flavor. That makes it easy to consume relational databases in a reactive way through Project Reactor. The first difference is that we don’t need a JPA Entity. We only need a POJO with the fields that we want to get from the database and mark one of them with @Id
:
import org.springframework.data.annotation.Id; public class Product { @Id private Long id ... more fields, getters, and setters ... }
The repository interface is remarkably similar to the JPA version:
import org.springframework.data.r2dbc.repository.R2dbcRepository; public interface ProductRepository extends R2dbcRepository<Product, Long> { }
The query methods in the repository interface return objects of type Mono
or Flux
. These are the building blocks in reactive programming with Project Reactor. See the official documentation for an introduction to this topic if you don’t have experience with it.
The service implementation can now take advantage of the reactive repository and return a Flux
instead of a List
. It can also produce a different kind of media (more on this in the next section):
import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; @RestController @RequestMapping("/api/products") public class ProductService { private final ProductRepository repository; public ProductService(ProductRepository repository) { this.repository = repository; } @GetMapping(value = "/", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<Product> findProducs() { return repository.findAll(); } }
The client-side
On the client side (the web browser), we can use the EventSource JavaScript API to get data as it becomes available (in contrast to having to wait for the full result set to be ready):
const eventSource = new EventSource('http://localhost:8080/api/simulation/test1/'); eventSource.onmessage = event => { console.log(event.data); }
In this example, we are simply logging the data in the console, but we could modify the DOM to show products instead. You get the idea.
The connection can be closed as follows:
eventSource.close();
What’s next?
If you want to see how I implement these two RESTful services and compare their performance watch this webinar. You can find a more complete example with full source code on GitHub. Make sure to explore the application.properties file to see how the connection is configured with R2DBC (there are some minimal differences when compared to JDBC), and also the pom.xml file to see how to add the reactive R2DBC driver for MariaDB databases.