I've recently started working with Spring Boot in a new project that has the following code (simplified):
@RestController
@RequestMapping("/users")
public class MyController {
@Autowired
UserService service;
@GetMapping
public Mono<ResponseEntity<User>> getUser() {
// note, the non-reactive call is wrapped into Mono.just
return Mono.just(new ResponseEntity<User>(service.getUser(), ...);
}
}
// both service and dao (below) are non reactive
@Service
public class UserService {
@Autowired
UserDao dao;
User getUser() {
// some logic (everything is in memory, nothing is io-bound)
return dao.getUser();
}
}
@Repository
public class UserDao {
public User getUser() {
// call the non-reactive relational db and get the user
// it takes 99% of the time I believe
// return this user;
}
}
Now I've never really used reactive approach but I understand that under-the-hood this spring boot application must run netty and use webflux I've used a database as an example of I/O bound operation, but sometimes in the code there are also calls to remote services (REST), and also they're not reactive... These DB operations/remote rest calls I believe take like 99% of the execution time, so this makes the application to be a good candidate for reactive implementation.
However, I'm confused by the current situation with the code:
My question is - whether such a usage of mixture of non-reactive and reactive approaches is ok or it is wrong?
In my understanding if I want to go with reactive web-flux approach, all my code must be reactive all the way down to the database/remote rest call (including them of course), otherwise I'll effectively keep the event-loop threads of netty busy and this is something I can't do... So it seems wrong to me in a nutshell, But I'm not sure what happens actually in this code. Is my understanding correct or I'm missing something?
These DB operations/remote rest calls I believe take like 99% of the execution time, so this makes the application to be a good candidate for reactive implementation.
You will still have the same execution time. The Reactive frameworks is not for reducing execution time. https://projectreactor.io/docs/core/release/reference/#getting-started-introducing-reactor.
Reactor is a fully non-blocking reactive programming foundation for the JVM, with efficient demand management (in the form of managing “backpressure”)
Is this your use case?
The controller is reactive
Really? You made it reactive with Mono::just
but you're using non-reactive annotations.
The service / DAO / Remote Calls are not
Why not switch them as well? If you need to wrap them then check the docs. C.1. How Do I Wrap a Synchronous, Blocking Call?.
In my understanding if I want to go with reactive web-flux approach, all my code must be reactive all the way down to the database/remote rest call (including them of course), otherwise I'll effectively keep the event-loop threads of netty busy and this is something I can't do... So it seems wrong to me in a nutshell, But I'm not sure what happens actually in this code. Is my understanding correct or I'm missing something?
Yes. All the way down. The purpose is to create a flow
and pass it to the client. When the client requests the result the flow
is executed within the reactive framework.
When the framework encounters a blocking call, DB or REST for example, it makes the call and repurposes the thread to execute another concurrent request. When the blocking call returns the thread is repurposed back to the original request. With apache tomcat using system threads you can handle maybe 1 or 2 thousand concurrent requests with very fine timing. Out of the box, 384. With WebFlux 30 to 50 thousand if you do it correctly. Do you have that many users that they will be making a steady 30K concurrent requests per second?
It's also wrapped up in the functional
programming paradigm style. It's rare anyone writes good code with that paradigm. I just fixed a block of code that went 20 lines deep just to add a bunch of leaf nodes into a map. Replaced it with a couple forEach
loops.
Have fun with this rabbit hole. People like the new stuff because it's exotic and fascinating. Wait until you need to maintain someone's code who didn't really understand the framework and it's purpose. Functional
is just ugly and WebFlux
is very obfuscated.
PS> Just to illustrate the point, Mono::just
will execute the code first then build the flow with the result. It just places the result in a Mono
, so in fact you have done nothing reactive whatsoever. So if you put a REST call in Mono.just it will invoke the endpoint before returning the flow to the client. Major reactive trap for beginners.