Refactoring from imperative to reactive implementation
Background
As software industry is embracing the new Microservice Architecture paradigm, myriad applications have been built with Spring Boot framework. By the time organizations have got its early versions of microservice applications in production, industry has found out newer and better avenues for further optimizing microservices, so that systems can be more robust, resilient and responsive a.k.a Reactive Systems (as per Reactive Manifesto ). Thanks to Spring Reactor and Spring Webflux which can help us in building reactive systems using Spring framework.
What I intend to demonstrate over here is, how a Spring Boot application can be refactored from imperative to reactive implementation along with compelling justifiable reasons.
Reference Application
Lets consider a hypothetical scenario where in we have a Spring Boot based developed application i.e. Card Service App which exposes REST endpoints using Spring WebMVC. Similar to a conventional Microservice Architecture, this application in turn not only invokes an API (using Spring's RestTemplate) exposed by downstream system (written in Go) but also performs some Database operations (using Spring Data Repository). The sole purpose of adding this infrastructure complexity is to mimic a real world scenario and thereby really justify the refactoring efforts that we will be putting in.
APIs exposed by application
HTTP Method | URI | Description |
---|---|---|
GET | /card/{id} | Fetches a card based on cardId |
GET | /cards | Fetches all the cards from the system |
POST | /card | Adds a card within system |
DELETE | /cards | Deletes all the cards from the system |
Source Code
Imperative Implementation (Before refactoring) Reactive Implementation (After refactoring)
Steps for refactoring
1. Build Assembly
Conventionally speaking build assembly of a typical Spring Boot application (which exposes imperative REST APIs) will have Spring Boot Starter Web. So we will be replacing this by Spring Boot Starter Webflux, which is parallel version of Spring MVC that supports fully non-blocking reactive streams. We will also be moving from Tomcat to Reactor Netty (based on Netty framework) which provides non-blocking HTTP clients and servers. Ideally Spring Boot should automatically configure Reactor Netty as default server if we are using Webflux. Since it was not getting configured automatically due to some dependency issue, I had to explicitly exclude Tomcat and add Reactor Netty as dependency.
2. REST Controllers
Now that we have added Webflux as one of the dependencies, we can use its APIs to refactor controllers. Easiest way to get started is by refactoring GET APIs first and then follow it up with other APIs
2.1 GET API
Basically GET APIs either return a response object or a collection of response objects. We will be using Mono and Flux to refactor our APIs from imperative to reactive implementation by making use of just and _fromIterable _operator
2.2 POST API
Now here comes the complexity as we will need to refactor CardService before refactoring Controller. And we being Software Craftsman love solving complex pieces. Hence this is going to be fun and exciting with multi step process as mentioned below -
2.2.1 Refactoring CardService from REST template to WebClient
We will replace RestTemplate with WebClient which is a non blocking and reactive client to perform HTTP requests
We will also refactor the method that invokes downstream system's API by replacing instantiated RestTemplate with WebClient object
2.2.2 Refactoring enroll method of CardService
Another tricky piece will be to refactor enroll
method. So we will be flattening the returned Mono from downstream system, which will allow us to extract card alias, than set in Card object which will be eventually saved in database
And finally we will refactor Controller
2.2.3 Refactoring Controller
Comparison on performance metrics
After performing all the above refactoring steps, lets understand whether these efforts are really going to add any business value or is this refactoring activity going to sound like a buzz word driven development to the business / product team :simple_smile:
I certainly do not want to digress from core agenda of this post, hence I will try to keep this section as much concise as I can.
Throughput
- For 1500 concurrent users throughput of Webflux based implementation increases by 1.52 times
- Spring WebMvc based implementation was so slow that not a single Get Card request got executed for 1000 and 1500 concurrent users
Latency
- For Webflux based implementation, 95 percentile response time is 50% less when compared with Spring WebMvc based implementation
CPU Utilization
- Based on average readings Spring Webflux based implementation is 8-10% less in CPU utilization
No. of threads
- For Spring Webflux based implementation, total no. of threads is reduced by 7 times (31 Vs 234)
Conclusion
We clearly saw how to systematically refactor imperative code to reactive implementation. And with above performance metrics it is quiet apparent that even though this refactoring may not enhance any business value of application, but it has significant impact from NFR standpoint, as it mainly aids in making the system Reactive. Needless to say that comparative analysis would have convinced you regarding the benefits it bring from performance and resource utilization standpoint.
Happy REFACTORING!
comments powered by Disqus