How to transfer data via reactor's subscriber context? - java

I'm a new for a project reactor, but i have task to send some information from classic spring rest controller to some service, which is interacts with different system. Whole project developed with project reactor.
Here is my rest controller:
#RestController
public class Controller {
#Autowired
Service service;
#PostMapping("/path")
public Mono<String> test(#RequestHeader Map<String, String> headers) throws Exception {
testService.saveHeader(headers.get("header"));
return service.getData();
}
And here is my service:
#Service
public class Service {
private Mono<String> monoHeader;
private InteractionService interactor;
public Mono<String> getData() {
return Mono.fromSupplier(() -> interactor.interact(monoHeader.block()));
}
public void saveHeader(String header) {
String key = "header";
monoHeader = Mono.just("")
.flatMap( s -> Mono.subscriberContext()
.map( ctx -> s + ctx.get(key)))
.subscriberContext(ctx -> ctx.put(key, header));
}
Is it acceptable solution?

Fisrt off, I don't think you need the Context here. It is useful to implicitly pass data to a Flux or a Mono that you don't create (eg. one that a database driver creates for you). But here you're in charge of creating the Mono<String>.
Does the service saveHeader really achieve something? The call seem transient in nature: you always immediately call the interactor with the last saved header. (there could be a side effect there where two parallel calls to your endpoint end up overwriting each other's headers).
If you really want to store the headers, you could add a list or map in your service, but the most logical path would be to add the header as a parameter of getData().
This eliminates monoHeader field and saveHeader method.
Then getData itself: you don't need to ever block() on a Mono if you aim at returning a Mono. Adding an input parameter would allow you to rewrite the method as:
public Mono<String> getData(String header) {
return Mono.fromSupplier(() -> interactor.interact(header));
}
Last but not least, blocking.
The interactor seems to be an external service or library that is not reactive in nature. If the operation involves some latency (which it probably does) or blocks for more than a few milliseconds, then it should run on a separate thread.
Mono.fromSupplier runs in whatever thread is subscribing to it. In this case, Spring WebFlux will subscribe to it, and it will run in the Netty eventloop thread. If you block that thread, it means no other request can be serviced in the whole application!
So you want to execute the interactor in a dedicated thread, which you can do by using subscribeOn(Schedulers.boundedElastic()).
All in all:
#RestController
public class Controller {
#Autowired
Service service;
#PostMapping("/path")
public Mono<String> test(#RequestHeader Map<String, String> headers) throws Exception {
return service.getData(headers.get("header"));
}
}
#Service
public class Service {
private InteractionService interactor;
public Mono<String> getData(String header) {
return Mono.fromSupplier(() -> interactor.interact(header))
.subscribeOn(Schedulers.boundedElastic());
}
}

How to transfer data via reactor's subscriber context?
Is it acceptable solution?
No.
Your code of saveHeader() method is an equivalent of simple
public void saveHeader(String header) {
monoHeader = Mono.just(header);
}
A subscriberContext is needed if you consume the value elsewhere - if the mono is constructed elsewhere. In your case (where you have all code before your eyes in the same method) just use the actual value.
BTW, there are many ways to implement your getData() method.
One is as suggested by Simon Baslé to get rid of a separate saveHeader() method.
One other way, if you have to keep your monoHeader field, could be
public Mono<String> getData() {
return monoHeader.publishOn(Schedulers.boundedElastic())
.map(header -> interactor.interact(header));
}

Related

Reactive calls in #Post_construct

I'm trying to understand what is the right way of implementing post_construct methods in Spring Webflux.
On startup of an application I need to read data from the DB (I have an R2dbcRepository configured), and then perform some logic and save result as Bean's fields).
So I have a findAll() method returning Flux. How should it be done?
I tried using .block(), AtomicBoolean flag, none of these worked
First of all, never use block() method. Use it for tests at most, but there is a better solution out there than StepVerifier. (If you use Kotlin there are await prefixed methods that work like block but not blocking.)
If you need data at launch, that says it is bad design to me because if there is no user, what do you do with it? I think it's illogical. What happens when you use query when you need it, add to cache and reuse it when you need it again. In the case of WebFlux, you can prepare a Mono object that uses a query from the database and use .cache() end of chain. So Spring Bean can contain this Mono object that will be run when you subscribe.
Ofc below example, repo.find will never call if function of Service won't run.
https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html#cache--
#Configuration
public class Config {
private R2dbcRepository repo;
public Config(R2dbcRepository repo) {
this.repo = repo;
}
#Bean
public Mono<Data> myCachedDbData() {
return repo.find(...)
.map(it -> new Data(it))
.cache()
}
}
#Service
public class Service {
private Mono<Data> data;
public Config(Mono<Data> data) {
this.data = data;
}
public Object function() {
return data.flatMap(...)
}
}

Why context is not propagated to (RxJava) Single from (Reactor) Mono?

For example, let's say I have a WebFilter that writes some Context
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.contextWrite(Context.of("my-context", "foobar"));
}
Downstream, my controller does this
#GetMapping(path = "test")
public Mono<String> test() throws Exception {
final Mono<ContextView> contextMono = Mono.deferContextual(Mono::just);
return contextMono.flatMap(ctx -> Mono.just(ctx.get("my-context")));
}
The above all works fine.
What if I wanted to return a Single from the controller method? I tried using RxJava3Adapter.monoToSingle() but it breaks the reactor chain.
#GetMapping(path = "test")
public Single<String> test() throws Exception {
final Mono<ContextView> contextMono = Mono.deferContextual(Mono::just);
return RxJava3Adapter.monoToSingle(
contextMono.flatMap(ctx -> Mono.just(ctx.get("my-context"))));
}
My guess is that since I'm not returning the Mono, nothing subscribes to this contextMono inside of the RxJava3Adapter. Is that the right explanation?
Is there any way to return a Single while having the Context be passed in?
The subscription itself works fine. The problem is that Context is a Reactor specific feature which is not part of the Reactive Streams standard. So when you convert a Mono to Single, the Context is lost.
In the code you attached you should just simply omit the Rx part to make it work but I imagine that your real world use case might be more convoluted. A good approach can be to convert the Rx code to Reactor at the earliest possible place (e.g. when you call the third-party library which returns the Rx type) and use Reactor in the rest of the codebase including the controller return type.

How to convert one object into another without blocking

I need to rewrite some spring code to reactive-spring. So there's a reactive spring repository service which provides on type of object. But the return type of mapping method is another one type, which depends on locale. Now there's a blocking algorithm which calls get method from repository then split result, then take String locale and then make from them all one response object. How can I combine it into one non-blocking chain of reactive methods?
I see that the only thing I have to wait is Mongo Db service which is kind of reactive, but I want to write code properly to make Spring framework block Mono itself. Here is the example code:
#RestController
#RequestMapping
public class PhoneCheckController {
#Autowired
private MessageLocalization loc;
#Autowired
private PhoneCheckService service;
#GetMapping("/check")
public PhoneCheckResponse checkPhone(#Valid #ModelAttribute PhoneCheckRequest request, Locale locale) {
PhoneCheckResult result = service.checkPhoneNumber(DtoTransformer.toParams(request)).block();
return new PhoneCheckResponse(
result.getViolationLevel(),
loc.getMessage(result.getMessage(), locale)
);
}
}
I tried to make like this
public Mono<PhoneCheckResponse> checkPhone(#Valid #ModelAttribute PhoneCheckRequest request, Locale locale) {
PhoneCheckResult result = service.checkPhoneNumber(DtoTransformer.toParams(request));
return Mono.just(new PhoneCheckResponse(
result.getViolationLevel(), //reactive part I need put
loc.getMessage(result.getMessage(), locale) //reactive part
)
);
}

How to use CompletableFuture.thenCompose() when returning entities from repositories?

I started working with CompletableFuture in Spring Boot, and I'm seeing in some places that the usual repository methods return CompletableFuture <Entity> instead of Entity.
I do not know what is happening, but when I return instances of CompletableFuture in repositories, the code runs perfectly. However when I return entities, the code does not work asynchronously and always returns null.
Here is an example:
#Service
public class AsyncServiceImpl{
/** .. Init repository instances .. **/
#Async(AsyncConfiguration.TASK_EXECUTOR_SERVICE)
public CompletableFuture<Token> getTokenByUser(Credential credential) {
return userRepository.getUser(credential)
.thenCompose(s -> TokenRepository.getToken(s));
}
}
#Repository
public class UserRepository {
#Async(AsyncConfiguration.TASK_EXECUTOR_REPOSITORY)
public CompletableFuture<User> getUser(Credential credentials) {
return CompletableFuture.supplyAsync(() ->
new User(credentials.getUsername())
);
}
}
#Repository
public class TokenRepository {
#Async(AsyncConfiguration.TASK_EXECUTOR_REPOSITORY)
public CompletableFuture<Token> getToken(User user) {
return CompletableFuture.supplyAsync(() ->
new Token(user.getUserId())
);
}
}
The previous code runs perfectly but the following code doesn't run asynchronously and the result is always null.
#Service
public class AsyncServiceImpl {
/** .. Init repository instances .. **/
#Async(AsyncConfiguration.TASK_EXECUTOR_SERVICE)
public CompletableFuture<Token> requestToken(Credential credential) {
return CompletableFuture.supplyAsync(() -> userRepository.getUser(credential))
.thenCompose(s ->
CompletableFuture.supplyAsync(() -> TokenRepository.getToken(s)));
}
}
#Repository
public class UserRepository {
#Async(AsyncConfiguration.TASK_EXECUTOR_REPOSITORY)
public User getUser(Credential credentials) {
return new User(credentials.getUsername());
}
}
#Repository
public class TokenRepository {
#Async(AsyncConfiguration.TASK_EXECUTOR_SERVICE)
public Token getToken(User user) {
return new Token(user.getUserId());
}
}
Why doesn't this second code work?
As per the Spring #Async Javadoc:
the return type is constrained to either void or Future
and it is also further detailed in the reference documentation:
In the simplest case, the annotation may be applied to a void-returning method.
[…]
Even methods that return a value can be invoked asynchronously. However, such methods are required to have a Future typed return value. This still provides the benefit of asynchronous execution so that the caller can perform other tasks prior to calling get() on that Future.
In your second example, your #Async-annotated methods do not return a Future (or ListenableFuture and CompletableFuture which are also supported). However, Spring has to run your method asynchronously. It can thus only behave as if your method had a void return type, and thus it returns null.
As a side note, when you use #Async, your method will already run asynchronously, so you shouldn't use CompletableFuture.supplyAsync() inside the method. You should simply compute your result and return it, wrapped in CompletableFuture.completedFuture() if necessary. If your method is only composing futures (like your service that simply composes asynchronous repository results), then you probably don't need the #Async annotation. See also the example from the Getting Started guide.

Spring boot send async tasks

I need to send the email/sms/events as a background async task inside spring boot rest.
My REST controller
#RestController
public class UserController {
#PostMapping(value = "/register")
public ResponseEntity<Object> registerUser(#RequestBody UserRequest userRequest){
// I will create the user
// I need to make the asyn call to background job to send email/sms/events
sendEvents(userId, type) // this shouldn't block the response.
// need to send immediate response
Response x = new Response();
x.setCode("success");
x.setMessage("success message");
return new ResponseEntity<>(x, HttpStatus.OK);
}
}
How can I make sendEvents without blocking the response (no need to get the return just a background task) ?
sendEvents- call the sms/email third part api or send events to kafka topic.
Thanks.
Sounds like a perfect use case for the Spring #Async annotation.
#Async
public void sendEvents() {
// this method is executed asynchronously, client code isn't blocked
}
Important: #Async works only on the public methods and can't be called from inside of a single class. If you put the sendEvents() method in the UserController class it will be executed synchronously (because the proxy mechanism is bypassed). Create a separate class to extract the asynchronous operation.
In order to enable the async processing in your Spring Boot application, you have to mark your main class with appropriate annotation.
#EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application.class);
application.run(args);
}
}
Alternatively, you can also place the #EnableAsync annotation on any #Configuration class. The result will be the same.

Categories

Resources