DEV Community

Cover image for Comparing WebFlux and Spring MVC with JMeter and Kotlin
João Esperancinha
João Esperancinha

Posted on

Comparing WebFlux and Spring MVC with JMeter and Kotlin

1. Introduction

Many times, we need to back up certain claims we make in software development. More often than not, we need to show it very clearly to stakeholders, businesses, and management, the value of our claims. Many times we see a true value in changing some technology, and we see long-term benefits in doing so. Whether the improvements we see are performance-related, code readability, maintainability, or modularity, what actually matters to business are other parameters. These can be anything like fast delivery, feature enabling, image, user experience, profits and business growth. In this way, if we need to make an argument on why we should make a step towards a new technology, we need to be able to justify that. Many times this is not an easy task. We have to clearly identify the business goals and make it clear that our suggestions will make remarkable improvements.
For some time I’ve seen talks about reactive programming and my focus has always been WebFlux. RxJava is on the horizon but that will be the subject of another article.
In this article, I assume a few things about the readers. Experience and knowledge about MVC and reactive architectures are very important. Therefore the basics are very important. We will go through the implementation of two Spring boot services. One follows a blocking Spring MVC architecture and the other one follows a Spring WebFlux Reactive implementation in MVC. For the purpose of simplifying semantics, I’ll be using MVC to refer to the blocking MVC model and WebFlux to refer to the reactive MVC model. These implementations are all built with Kotlin. The unit tests are implemented with Spock and the language used is Groovy. These languages are very similar to Java and so if you don’t know them, but you do know Java, then you can be very much assured that you can fully understand what’s happening.
To keep matters simple, in this article, although we are going to look at some differences between the implementation of a WebFlux application and an MVC application, our tests will only be focused on the artist table. As a follow up to this article I will further explain the rest of the application and further expand the example. For now let’s focus on version 1.0.0.
Let’s imagine that we have to present a case in favour of Reactive Programming. We want to suggest migrating our old blocking MVC application to a reactive non-blocking MVC WebFlux application. We explain everything we know about non-blocking applications. We explain the magic of not blocking the main thread. We explain how this will in the end improve performance and how wonderful it will be to be able to process more requests. In our project there are different players and very often, the gap between the engineers and business needs adjustment. This is where benchmarking comes in. What we need to do in these cases is to clearly show business the benefits of changing to WebFlux. Talking about the servlet thread, multithreading, the subscriber pattern, the spawning of different threads per subscriber, vertical scaling, and JVM, can be a good approach. However, most of the time, what we need is to actually be able to point out in the shortest time possible the benefits of doing this. How do we do this? That’s what we are going to see in this article. Just before we continue, please consider the difference between thinking about how many requests we can get per minute and how long a request takes. We will see later on, why is this so important to keep in mind just now.
Reactive programming with WebFlux allows us to scale vertically up, from within the VM with small independent threads. Another very important aspect of this kind of application is that its producers don’t overwhelm consumers. In other words, given that it’s all event-driven, the small threads that are created by the publishers will trigger the subscriber when needed. Finally, it’s important to note that with Webflux, our programming style is declarative and not imperative. This means that our code doesn’t get executed in the main thread which normally allows us to debug our code sequentially.

2. Development

2.1. Environment setup

In order to be able to run this application and perform the benchmarking tests. Aside from the basic setup, we need to have JMeter installed. We will go through that in this article.
We will be looking at the implementation of two Spring boot applications. One will be implemented with the old MVC model and the other with a new WebFlux model. This is how it looks after running docker-compose:

blogcenter

2.2. Structure

Let’s now have a look at the root modules and folders of the main project we will be looking at:

  • concert-demos-gui — not available for this project yet. This project will be completed in the follow-up article where we will dive deeper into WebFlux and its paradigms.
  • concert-demos-rest-service-mvc — Our Spring Boot MVC application.
  • concert-demos-rest-service-webflux — Our WebFlux application.
  • concerts-demos-data — Data repo where our DTO (Data Transfer Objects) reside. This is where our REST contract is defined and its common for both applications.
  • docker-psql — The definition and files for our custom Postgres image. Here we will define two databases: concertsdb-mvc and concertsdb-webflux. They are separate in order to make more reliable test results.
  • jmeter — All JMeter configuration files location. Here we can also find the test results which will allow us to draw conclusions at the end of this article.

2.3. ER Model

For this article, I’ve created a concert management tool. The features expected are only at least to be able to save an artist on a database. In this case, we will work with two different databases running on our local PostgreSQL server. One database will serve the MVC (Model View Controller) typical architecture and the other database will serve the WebFlux architecture. The latter is one of the many implementations of a reactive programming architecture. Although both databases serve different services, they will share the same model.
Let’s start by examining the ER models of both applications:

blogcenter

As you can see, this is a very simple model in which we start out with a concert. Further, we have a number of listings per concert. When doing this, we are saying that our concert will have listings and each listing can be part of many concerts. This is in other words a many to many relation. A concert listing is essentially a set of an artist, a lyric of one music which we should know by heart during the concert, and a complete list of music the artist will play. Music can also be part of different listings and in fact it can also be sung and played by different artists. This is the reason why the relation between the listing and the music is also a many to many.

2.4. Spring MVC vs WebFlux

In my previous article Reactive Programming applied to Legacy Services — A WebFlux example, I explain a few workarounds to make a fully reactive application connected to SOAP services. Although this may have some use to organizations with legacy software, one important step that the article doesn’t have is a reactive data source. Our data source in that article was only a soap service that we, very simply said, wrapped around the Spring WebFlux architecture. In this article, we are going to actually see a completely reactive Web Application using R2DBC.
Both architectures can follow an Onion model or a Ports and Adapters architecture. We are going to go through an implementation which uses common rules for both architectures. Also, bear in mind that although the complete applications allow us to save concerts, listings, music and artists, our focus for this article is the Artist table. So we are going to focus on the creation and retrieval of artists for both applications.
Perhaps the best way to begin this is just to have a look at the repository layer. Let’s have a look at the MVC implementation (I’m purposely excluding the hashCode and the equals methods because of how long they are. They are also not relevant for this article).

2.4.1 Data Model

First, let’s look at the artist data model :

@Entity
data class Artist(

        @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
        var id: Long? = null,
        val name: String? = null,
        val gender: Gender? = null,
        val careerStart: Long? = null,
        val birthDate: String? = null,
        val birthCity: String? = null,
        val country: String? = null,
        val keywords: String? = null

) {}
Enter fullscreen mode Exit fullscreen mode

In Spring MVC, we have to identify which parameter is the primary key, how the id’s are going to be generated and of course we have to specify that our POJO (Plain Old Java Object) is properly identified by Spring Data JPA(Hibernate inside). This is done by stereotyping our POJO with @Entity and setting up our id annotated with @id and @GeneratedValue.
Let’s compare this implementation with the WebFlux implementation:
import org.jesperancinha.concerts.types.Gender
import org.springframework.data.annotation.Id

data class Artist(
        @Id var id: Long? = null,
        val name: String,
        val gender: Gender,
        val careerStart: Long,
        val birthDate: String,
        val birthCity: String,
        val country: String,
        val keywords: String

) {}
Enter fullscreen mode Exit fullscreen mode

The first difference we notice is the absence of the @Entity annotation and we also don’t see any id generation specific annotations. This is just the way R2DBC is currently implemented. In its current state, we will see that many of the annotations we love to use with JPA/Hibernate are just not there. Many reasons can be pointed out to this, but apparently the most important one is that many of the MVC model annotations enter in direct conflict with the Reactive programming principles. We need to make our application as reactive as possible and in this way a couple of important basic things like delegating the id generation to the database native system or not having to specify that our POJO is also an @Entity are important aspects of this. However, probably the most striking difference between the two implementations is how we establish relations between data tables. Next we will have a look at the relation between the Listing and Artist tables. I know I mentioned that the focus of this article was the Artist table only. However, given that this article is also about making a fully reactive application, it is important to understand how we can make ER relations work.

2.4.2. Making ER work

Let’s compare two implementations side by side. First, let’s look at the MVC implementation of the table Listing:

@Entity
data class Listing(
        @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
        var id: Long = 0,

        @OneToOne
        val artist: Artist? = null,

        @OneToOne
        val referenceMusic: Music? = null,

        @ManyToMany(cascade = [CascadeType.ALL])
        @JoinTable(
                name = "listing_music",
                joinColumns = [JoinColumn(name = "music_id")],
                inverseJoinColumns = [JoinColumn(name = "listing_id")]
        )
        var musics: MutableSet<Music> = HashSet(),

        @ManyToMany(mappedBy = "listings")
        val concerts: MutableSet<Concert> = HashSet()
) {}
Enter fullscreen mode Exit fullscreen mode

Let’s focus on two important annotations @OneToOne and @ManyToMany. And now the WebFlux version:

data class Listing(
        @Id var id: Long? = null,