Reactive template engines

Along with regular API features, one of the most popular parts of modern web applications is the UI. Of course, web application UIs today are based on sophisticated JavaScript rendering and, in most cases, developers prefer client-side rendering rather than server-side. Despite this, a lot of enterprise applications are still using server-side rendering technologies that are relevant to their use cases. Web MVC has support for various technologies, such as JSP, JSTL, FreeMarker, Groovy Markup, Thymeleaf, Apache Tiles, and many others. Unfortunately, with Spring 5.x and the WebFlux module, support for many of these, including Apache Velocity, has been dropped.

Nevertheless, Spring WebFlux has the same technique for view rendering as Web MVC. The following example shows a familiar way of specifying a view for rendering: 

@RequestMapping("/")
public String index() {
return "index";
}

In the previous example, as a result of the index method invocation, we return a String with the name of the view. Under the hood, the framework looks up that view in the configured folder and then renders it using an appropriate template engine. 

By default, WebFlux only supports the FreeMarker server-side rendering engine. However, it is important to work out how the reactive approach is supported in the template rendering process. To this end, let's consider a case involving the rendering of a large music playlist:

@RequestMapping("/play-list-view")
public Mono<String> getPlaylist(final Model model) { // (1)
final Flux<Song> playlistStream = ...; // (2)
return playlistStream //
.collectList() // (3)
.doOnNext(list -> model.addAttribute("playList", list)) // (4)
.then(Mono.just("freemarker/play-list-view")); // (5)
}

As we can see from the previous example, we are using a reactive type, Mono<String> (shown at point 1), in order to asynchronously return the view name. In addition, our template has a placeholder, dataSource, which should be filled by the list of given Song, shown at point (2). A common way of providing context-specific data is to define the Model, (1) and put the required attribute in it, as shown at point (4). Unfortunately, FreeMarker does not support reactive and non-blocking rendering of data, so we have to collect all the songs into a list and put the collected data in the Model. Finally, once all entries are collected and stored in the Model, we may return the name of the view and start rendering it.

Unfortunately, rendering templates such as these is a CPU-intensive operation. If we have an enormous dataset, this may take some time and memory. Fortunately, Thymeleaf's community decided to support Reactive WebFlux and provide further possibilities for asynchronous and streaming template rendering. Thymeleaf provides similar functionality to FreeMarker and allows to write identical code to render the UI. Thymeleaf also provides us with the ability to use reactive types as a source of data inside the template and render a part of the template when a new element in the stream becomes available. The following example shows how we can use Reactive Streams with Thymeleaf during the handling of a request:

@RequestMapping("/play-list-view")
public String view(final Model model) {
final Flux<Song> playlistStream = ...;

model.addAttribute(
"playList",
new ReactiveDataDriverContextVariable(playlistStream, 1, 1)
);

return "thymeleaf/play-list-view";
}

This example introduces a new data type called ReactiveDataDriverContextVariable, which accepts reactive types such as Publisher, Flux, Mono, Observable, and other reactive types supported by the ReactiveAdapterRegistry class. 

Even though reactive support requires an additional class wrapper around streams, the template side does not require any changes. The following example shows how we can work with reactive streams in a similar way as with a normal collection:

<!DOCTYPE html>                                                    // (1)
<html> //
... //
<body> //
... //
<table> // (2)
<thead> //
... // (3)
</thead> //
<tbody> // (4)
<tr th:each="e : ${playList}"> // (5)
<td th:text="${e.id}">...</td> //
<td th:text="${e.name}">...</td> //
<td th:text="${e.artist}">...</td> //
<td th:text="${e.album}">...</td> //
</tr> //
</tbody> //
</table> //
</body> //
</html> //

This code demonstrates how to use the markup of Thymeleaf's template, which has a common HTML document declaration, as shown at point 1. It renders a table, shown at point (2), with some headers (point 3) and a body (4). This is filled by the rows constructed from the playList of Song entries and information about them.

The most valuable advantage here is that Thymeleaf's rendering engine starts streaming data to the client without having to wait for the last element to be emitted. Moreover, it supports the rendering of an infinite stream of elements. This became possible with the addition of support for Transfer-Encoding: chunked. Instead of rendering the whole template in memory, Thymeleaf renders the available parts first and then sends the rest of the template asynchronously in chunks, once a new element becomes available. 

Unfortunately, at the time of writing, Thymeleaf only supports one reactive data source per template. Nevertheless, this technique returns the first chunk of data much faster than with usual rendering, which requires the whole dataset to be present, and decreases the latency between the request and the first feedback from the server, hence improving the overall user experience.

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

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