Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Reactive Spring 5 and Application Design Impact

DZone's Guide to

Reactive Spring 5 and Application Design Impact

Spring 5 is coming, with lots of reactive features. Get ready, it's not just a bunch of new annotations, it's a totally different approach to coding your application!

· Java Zone ·
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

The 5th version of Spring Framework brings Reactive Programming support. In the same time, trying to put reactivity into practice brings a number of new challenges. I want to share my learnings what are the new rules of reactive design and how they can influence Spring-based applications development. This post assumes the reader has already a prior knowledge of the new features in Spring 5 and Reactor Project.

Functional Programming

An unavoidable impact of Reactive Programming with Spring 5 is shifting towards functional transformations. When non-blocking implementations, such as Spring’s WebClient or Spring Data’s reactive repositories, are used, we have to deal with reactive data types: Mono and Flux. To stay non-blocking, the result of the queries can be only accessed through functional transformations.

This is how we would write code for imperative execution with blocking dependencies:

public Person addPerson(Person person) {
    IpAddressDetails ipDetails = ipService.getDetails(person.getIpAddress());
    person.setIpAddressDetails(ipDetails);
    return repository.save(person); 
}

And here we do the same with Reactor-based implementations:

private Mono<Person> addPerson(Mono<Person> mono) {
    return mono
        .flatMap(this::addIpAddressDetails)
        .flatMap(repository::save); 
}

Now, you don’t have a choice — iterate or flatMap. Flat-mapping is the only choice. The only way to switch back to imperative style programming is to use the block() method. But blocking is exactly what we wanted to avoid. So our code becomes more functional. We don’t have to use purely functional programming, but knowledge of functional programming can help. Many functional programming patterns, such as Free Monads or Monad Transformers, can make reactive code better.

(Im)mutability

Local variables are captured by lambda expression and might be accessed asynchronously by different threads. If our objects are mutable, there is a danger of concurrency issues, such as race conditions. The solution could be to make all the objects immutable. Sounds easier than it actually is. In theory, we just make object properties final and make nested objects mutable as well:

public final class Person { 
    private final String id; 
    private final String firstname; 
    private final String lastname;
    private final IpAddress ipAddress; // immutable inside

    public Person(String id, String firstname, String lastname, IpAddress ipAddress) {
        this.id = id;
        this.firstname = firstname;
        this.lastname = lastname;
        this.ipAddress = ipipAddress
    }

    // getters
}

However, in practice, there could be the following difficulties:

  • Problems with reflection libraries. “No default constructor for entity found” is a message we see from different serializers, such as Hibernate, if we don’t provide mutable fields and default constructor.
  • No interoperable immutable collections interfaces. There are immutable collections in vavr or Guava. There is JDK9 in the roadmap which brings more convenient immutable collection creation. But still most of the third-party code we rely on uses mutable collections.
  • It’s not enough just to have a constructor. With direct usage of the “all args” constructor, our code will quickly become ugly. Another set of patterns come here to rescue: Builder and Lenses. We should know and use them.
  • Needless to say that other JVM languages, such as Scala, Clojure or Kotlin, have immutability as a language native citizen.

Spring 5 brings dedicated Kotlin support. Kotlin is definitely a good candidate to consider for implementing reactive Spring 5 application and it can simplify usage of immutable objects. Another option could just be proper encapsulation of mutability.

Back-Pressure

There are some reactive features of Spring 5 framework that can be useful in non-reactive applications as well. Such a feature is back-pressure. Not to drop messages and communicate demand in a controlled manner, Reactive Streams’ pull-based back-pressure was introduced.

Back-pressure.

We may add back-pressure and parallelism, even into a non-reactive blocking application, for example, for processing big data sets. Here is how we can achieve parallelism and processing in batches, while properly communicating demand with back-pressure:

public void refreshIpAddressDetails() {
    LocalDateTime dateTime = LocalDateTime.now().minusDays(90);
    repository.findByUpdatedAtLessThan(dateTime)
        .buffer(300)
        .parallel(2)
        .flatMap(this::updateIpAddressDetails)
        .subscribe(System.out::println, System.err::println);
}


Of course, back-pressure will not replace batch frameworks functionality, but in some scenarios, it might be the only extra tool we need.

Blocking Libraries

Even in asynchronous environments, sometimes it is unavoidable to use blocking implementations, simply because there is no non-blocking alternative available. A typical example is JDBC. It is designed to be blocking and consumes a thread per database call. So the question is how can we use blocking libraries in a reactive Spring-based application.

We can dispatch blocking calls into an isolated Scheduler to avoid mixing blocking and non-blocking calls together. This will allow us to control the overall number of threads and will let the CPU serve non-blocking tasks in the main execution context, applying various optimizations. Reactor documentation recommends using the elastic() Scheduler for this type of job.

Blocking calls isolation.

Resilience Patterns

Reactive systems have to be resilient. The Reactive Manifesto by itself doesn’t say much how to achieve resilience. Many other sources do. A good source of resilience patterns is Michael Nygard’s book “Release It!” Some useful patterns that can be added to Reactive Systems are Bulkheads and Circuit Breaker.

Systems do not fail as a whole. We can isolate different parts of the system to isolate failures. In this case out bulkheads are failure units. Bulkheading can happen on many levels: we can isolate networks, machines, applications or threads.

The primary goal of the Circuit Breaker is to fail fast. Rather than waiting for a timeout, you can signal an error immediately.

Circuit Breaker.

When it comes to resilience patterns implementation, a couple of libraries exist. Other than well-known Netflix’s Hystrix, there is also resilience4j, which is very friendly to reactive and functional programming.

Verifying Reactivity

Tests are the only place where we can block(). The downsides of blocking will come when we run many tests concurrently or try to verify reactivity. Reactor instruments, such as StepVerifier, can help here.

After code level tests, it is highly desired to verify reactiveness in deployment. Special tools, such as Netflix’s Chaos Monkey, simplify this task. If you are not going to use Chaos Monkey, you may reproduce various failure scenarios with an own scripting facility, but it makes not so much sense to implement Resilience without testing how it works with realistic failures in a production-like environment.

Load testing is another important aspect. Actually, Reactive systems' goals address challenges that come with load, so load testing should be an important aspect of reactivity testing. Luckily, many load testing tools exist: JMeter, Tsung, LoadComplete, and more.

Overall Design Impact

To be Reactive, an application has to be responsive, resilient, elastic, and message-driven. The last criterion in this list caused a big movement into an asynchronous way of communications. This includes asynchronous request/reply and messaging libraries, database drivers, and more. To be fully non-blocking, we have to make the whole execution cycle as a set of functional compositions around concurrent structures. Changing an existent blocking application to non-blocking reactive one might result in a huge redesign.

WebFlux or MVC

Spring 5 by itself brings WebFlux with Reactive programming support but doesn’t remove Spring MVC. In the same time, concurrent functional programming, that is unavoidable with WebFlux, might give a mental overhead and is not intended to make code more readable. We need to stay pragmatic and choose one abstraction over another depending on the software requirements. Reactive Systems address challenges of modern applications, the challenges related to a high number of users and high throughput. Non-blocking libraries are available not for every technology. Currently supported integrations for Reactor (such as MongoDB, Cassandra or Kafka) rely on non-blocking drivers and also address challenges of high throughput. Not every application has high enough throughput to benefit from non-blocking Reactive design. Imperative blocking Spring MVC applications can still be fast, resilient and responsive.

Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat

Topics:
spring 5 ,webflux ,reactive programming ,circuit breaker ,resilience patterns ,java

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}