I am trying to achieve a fire and forget type of effect with webflux, thymeleaf and r2dbc. I have two endpoints, one to add an employee and another to list all employees. I want to simulate a slow database access so I have a thread sleep of several seconds before I call the DB.
Now, the effect I expect to see when I call /add
is that my controller returns immediately and the page add
is rendered at once. However, I'm not sure how to achieve this. With the current code nap()
happens before I can return a Mono
. In other words, I'm trying to run a long running job in the background without blocking the controller.
I have the following model:
@Data
public class Employee {
@Id
private Long id;
private String name;
}
The annotated controller has following methods:
@GetMapping(value = "/")
public String home(Model model) {
model.addAttribute("employees", repo.findAll());
return "home";
}
@GetMapping(value = "/add")
public Mono<String> add() {
return Mono
.defer(this::getEmployee)
.doOnNext(e -> repo.save(e).subscribe())
.thenReturn("add");
}
private Mono<Employee> getEmployee() {
final var e = new Employee();
e.setName("John");
nap(); // calls thread sleep for a few sec
return Mono.just(e);
}
My question is how can I wrap the long running job but at the same time preserve a Controller based notation (instead of functional) and also render the add
page immediately? I am aware of some similar questions like this and this, but I don't seem to be able to achieve the behaviour I need.
Edit:
lkatiforis' suggestion and this SO question were a push in the right direction. I had to adjust their example a bit because the employee didn't persist. The change is in add()
:
public String add() {
Mono.just(employee)
.delayElement(Duration.ofSeconds(5))
.doOnNext(e -> repo.save(e).subscribe())
.subscribe();
return "add";
}
employee
is just an instance of Employee
with a populated name. The delayElement
operator pauses for 5 seconds without blocking. Finally, I had to call subscribe()
on repo.save()
and at the end in order for it to work. I assume that if subscribe()
is only called on doOnNext()
then the main chain that starts with Mono.just()
is never executed.
I guess nap()
method executes Thread.sleep
or something similar, right? Thread.sleep
is blocking the main thread making the application unresponsive. You can use delayElements
operator to simulate a long-running operation:
private Mono<Employee> getEmployee() {
final var e = new Employee();
e.setName("John");
return Mono.just(e).delayElement(Duration.ofSeconds(5));
}