I want to call the method when previous returned Mono<Void>:
#Override
public Mono<Void> sendEmail(EmailDto emailDto) {
return mailReactiveClient.sendEmail(message ->
createMessage(emailDto, emailDto.getBody(), message))
.doOnNext(saveNotificationLog(emailDto)); //it's not work
}
private void saveNotificationLog(EmailDto emailDto) {
notificationLogReactiveRepository.save(NotificationLog.builder()
...
.build());
}
Method sendEmailreturns Mono<Void>.
So how to call saveNotificationLog?
UPD: Tring to make my question simplier:
#Override
public Mono<Void> sendEmail(EmailDto emailDto) {
return mailReactiveClient.sendEmail(message ->
createMessage(emailDto, emailDto.getBody(), message))
.doOnNext(System.out.print("Hello world!");
}
How to call doOnNextor similar method after sendEmail return Mono<Void>?
The Mono will not emit data, so doOnNext will not be triggered. You should use the doOnSuccess instead.
Also, your Mono need to be consumed. Without the code, we don't know if it is or not.
Some example here: I added subscribe() to consume the mono. Depending on the use of your Mono, you will have to do or not the same thing.
This print nothing:
Mono<String> m=Mono.just("test");
Mono<Void> v=m.then();
v.doOnNext(x->System.out.println("OK")).subscribe();
This print "OK":
Mono<String> m=Mono.just("test");
Mono<Void> v=m.then();
v.doOnSuccess(x->System.out.println("OK")).subscribe();
doOnNext, and in general all doOn* reactor methods are side-effect methods. You're not supposed to call them to do I/O work or chain operations, but rather log things and not do anything that would affect the state of the application.
In your code sample, notificationLogReactiveRepository.save returns Mono<Void>. The saveNotificationLog returns void and does not subscribe to the publisher returned by notificationLogReactiveRepository.save. This means the notification will not be saved, because nothing happens until you subscribe.
In this case, it seems you're trying to chain operations - then operators are just made for that. Your code should look like this:
#Override
public Mono<Void> sendEmail(EmailDto emailDto) {
return mailReactiveClient.sendEmail(message ->
createMessage(emailDto, emailDto.getBody(), message))
.then(saveNotificationLog(emailDto));
}
private Mono<Void> saveNotificationLog(EmailDto emailDto) {
return notificationLogReactiveRepository.save(NotificationLog.builder()
...
.build());
}
Try it this way:
Mono.empty().then()
Related
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.
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));
}
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.
I want to create an action that I can use with the #With annotation style. This action will need to proceed to an RPC call so if I understood correctly the documentation I should rather put this in an async way.
This is what I tried to do until now:
public class GetUserIdAction extends play.mvc.Action.Simple {
#Override
public CompletionStage<Result> call(Http.Context context) {
String token = "";
if (StringUtils.isEmpty(token)) {
return delegate.call(context);
}
CompletionStage<Http.Context> promiseOfUpdatedContext = CompletableFuture.supplyAsync(() -> setUserIdForToken(context, token));
return promiseOfUpdatedContext.thenApply(ctx -> delegate.call(ctx));
}
private Http.Context setUserIdForToken(Http.Context context, String token) {
context.args.put("user_id", authenticationManager.getUserIdForToken(token));
// The AuthenticationManager is issuing an RPC call and thus may take some time to complete.
return context;
}
}
Set aside the fact that token is always empty and authenticationManager is not set, this is just a quick meaningless example, my IDE is complaining on the thenApply part. For what I understand, it is expecting a CompletionStage<Result> and gets something more like a CompletionStage<CompletionStage<Result>>.
What is a way to deal with it? Cause here all I want is to put some information in the Context and then continue the delegate.call chain.
Or maybe I'm trying to do something stupid and composed actions are already asynchronous?
You have a CompletionStage<Something> and want to end with a CompletionStage<Result>. The easiest way to achieve that is using thenCompose.
Here is an example, with a small change: I have a CompletableFuture to get the token and only then I add it to the HttpContext
#Override
public CompletionStage<Result> call(final Http.Context context) {
final String token = "";
if (StringUtils.isEmpty(token)) {
return delegate.call(context);
}
return CompletableFuture.supplyAsync(() -> {
// do something to fetch that token
return "your_new_token";
}).thenCompose(tokenReceived -> {
context.args.put("user_id", tokenReceived);
return delegate.call(context);
});
}
Inside an Action, which is the best way to execute some asynchronous code if you don't need a Promise to produce the actual Result?
E.g I've created a RecordedAction to compose with other Actions. It executes an expensive operation (writes to the DB) but doesn't produce something needed by the annotated Action. In other words, I don't need a Promise but I do need a separate ExecutionContext.
I could do something like this:
public class RecordedAction extends Action<Recorded> {
#Override
public F.Promise<Result> call(Context ctx) throws Throwable {
Promise.promise(() -> {
// do asynchronous stuff
return Promise.pure(null, myExecutionContext);
});
return delegate.call(ctx);
}
}
But I'm just exploiting the Promise to use a separate ExecutionContext.