Interrupting the reactive pipeline via exception VS propagating error object - java

What is the best in terms of reactive programming when there is a need of interrupting a reactive pipeline?
The logic is very straightforward.
The web service, web application will accept requests.
Step 1, from the request, make one first HTTP request to a third party API. The first HTTP service will either answer with what we need, in our example, a string starting with good, or something we do not need.
Step 2, only if step 1 responded with what is needed, make a second HTTP request to a second HTTP service, also no control over, to get the ultimate and greatest response.
Note, this is sequential, we cannot call step 2 unless we have the correct value from step 1.
Obviously, making an entire HTTP call to step 2 at this point does not make sense at all.
Therefore, I am thinking of doing:
#PostMapping(path = "/question")
public Mono<ResponseEntity<String>> createDummyMono(String theImportantKey) {
return WebClient.create("http://first-service.com/get" + theImportantKey).get().exchangeToMono(clientResponse -> clientResponse.bodyToMono(String.class))
.flatMap(extractGoodValueFromStepOne -> {
if (extractGoodValueFromStepOne.startsWith("good")) {
System.out.println("Great! Step1 responded with something starting with good! Only with this we should invoke the second API");
return WebClient.create("http://second-service.com/get" + extractGoodValueFromStepOne.substring(4)).get().exchangeToMono(clientResponse -> clientResponse.bodyToMono(String.class));
} else {
System.out.println("This is bad, Step 1 did not return something starting with good, no need to make the second API call then. Let's just propagate an error message all the way to response with a dummy Mono");
return Mono.just("Step 1 did not answer with something good, the ultimate answer is an error");
}
})
.map(ResponseEntity::ok);
}
In this logic, the second step, represented by the flatMap will see if step 1 responded something we need. Only this case, a second HTTP request will be made to step 2. However, if it is not, I am building a dummy Mono to propagate and carry down the reactive pipeline.
A second solution, is to throw an exception, and catch it with #ExceptionHandler for instance
#PostMapping(path = "/question")
public Mono<ResponseEntity<String>> throwRuntimeException(String theImportantKey) {
return WebClient.create("http://first-service.com/get" + theImportantKey).get().exchangeToMono(clientResponse -> clientResponse.bodyToMono(String.class))
.flatMap(extractGoodValueFromStepOne -> {
if (extractGoodValueFromStepOne.startsWith("good")) {
System.out.println("Great! Step1 responded with something starting with good! Only with this we should invoke the second API");
return WebClient.create("http://second-service.com/get" + extractGoodValueFromStepOne.substring(4)).get().exchangeToMono(clientResponse -> clientResponse.bodyToMono(String.class));
} else {
System.out.println("This is bad, Step 1 did not return something starting with good, no need to make the second API call then. Let's just propagate an error message all the way to response with a dummy Mono");
throw new RuntimeException("Step 1 did not answer with something good, the ultimate answer is an error");
}
})
.map(ResponseEntity::ok);
}
#ExceptionHandler
public Mono<ResponseEntity<String>> exception(final RuntimeException runtimeException) {
return Mono.just(ResponseEntity.ok("Step 1 did not answer with something good, the ultimate answer is an error"));
}
Here, the logic is the same. Just if step 1 did not answer with what we need, I interrupt the pipeline by throwing a RuntimeException.
I kinda think, neither the first solution, passing down some dummy Mono or throwing an unchecked RuntimeException sounds the correct way to do in a reactive world.
May I ask which is the correct solution to answer to this problem and why please?

Your dummy Mono solution only works because there is nothing after in the chain that needs to do any additional processing, what if after your flatMap you need to do an additional flatMapon the successful value? then you will be in a pickle when a strange dummy Monocomes flying down the chain.
.flatMap(value -> {
if (value.startsWith("good")) {
System.out.println("good");
return WebClient.create("http://second-service.com/get" + value.substring(4))
.get()
.exchangeToMono(clientResponse -> clientResponse.bodyToMono(String.class));
} else {
System.out.println("Boo");
return Mono.just("some value");
}
}).flatMap(value2 -> {
// what now?
})
When an exception is thrown in an operator the exception will be propagatade through the stream as an onErrorevent. Like when we for instance return a Mono#error.
Some exceptions like for instance OutOfMemoryExceptionwill not be considered as such, but are instead fatal events and will terminate the flow immediately.
But otherwise most commonly the exception will then be transferred through the chain and "regular" operators will se that, that is an error event so they will just skip that event and pass it down the chain either out to the calling client, or until any of the specialized error event handlers see it, or as in your case be snatched up by an Exception handler that you have defined.
The correct way would be in your cases is to return a Mono#error (so you are explicit in your return) saying that if this happens we return an error and then either you recover, drop the value or whatever you want to do, or as you have done, handled the exception using an exception handler.
Your first solution behaves more like a return empty, and then you have switchIfEmpty operator so you change to another publisher (Mono) if last operator returned empty. Or you could use onErrorResume that will, if a specific error comes along, return a fallback Publisher.
There are very, very many ways of handling errors in reactor and i suggest you read up on them and try them all out.
4.6.2. Handling Exceptions in Operators or Functions

Related

Do I have to call join() for a future inside a whenComplete()?

I'm adding some code to an existing endpoint to send an email. We don't need the result of sending an email to return a response to the user, so I'm adding a .whenComplete() at the end of the chain of futures, calling our email service from within. The call to the email service is also async, returning a CompletionStage<Void>.
CompletionStage<SomeResponse> someEndpoint() {
return doThings()
.thenApply(things -> {
return someResponseFormat(things);
})
.whenComplete((someResponse, ex) -> {
if (ex == null) {
emailClient.sendEmail(someResponse); // CompletionStage<Void>
}
});
}
As I understand, that task will be scheduled and executed. Do I need to call join() on sendEmail(...)? Would doing so have a different behavior than not calling them? What is the best practice?
Edit: Originally I asked if I need to call join() or get(), which was misunderstood as "which do I need to call," when I meant, "do I need to call either at all."
The operation associated with emailClient.sendEmail(someResponse) will be scheduled regardless of whether you wait for its completion, so unless the JVM terminates in the meanwhile, it will complete. But
Nobody will notice when the operation completed or be able to wait for its completion.
Nobody will notice when the operation fails with an exception.
So what you probably want to do, is
CompletionStage<SomeResponse> someEndpoint() {
return doThings()
.thenApply(things -> someResponseFormat(things))
.thenCompose(someResponse -> emailClient.sendEmail(someResponse)
.thenApply(_void -> someResponse));
}
Then, when the caller of someEndpoint() invokes join() on it, the join() would wait for the completion of the sendEmail and also report errors when sendEmail fails. Likewise, when the caller of someEndpoint() chains dependent operations, they would start after the completion of sendEmail.

logging an encoded (Bcrypt) password Mono<String> with org.slf4j.Logger from a db call

I'm trying to figure out how to log a Mono<String> password with slf4j but it would always return a Monotype.
logger.info(login.getPassword()+" "+userRepository.findPasswordByUsername(login.getUsername()));
and
logger.info(login.getPassword()+" "+userRepository.findPasswordByUsername(login.getUsername()).toString());
the first 2 logging tries above
return the literal password (from the request) and MonoNext
and ofc you cant use .block()
which just throws
"block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3"
yes i'm aware that i can pass a Subscriber / Consumer for onNext() like:
.subscribe(x -> System.out.println( x.toString()))
to get some output but how would i do that with a logger only, is there even a way ?
login represents a user retrieved from a request.
The password is properly stored and encoded (Bycrypt) beforehand ofc (doesn't seem to be the issue).
Edit: to give more context
userRepository.findPasswordByUsername("username")
will return a Mono which i want to compare to another password as in:
passwordEncoder.matches( "userinputPW", userRepository.findPasswordByUsername("username") )
which is how you use a ByCryptEncoder in Spring and i can't .map() the Mono to a String (will obviously always return an Object and not a String as explained here https://stackoverflow.com/a/47180610/6414816 )
Using spring-boot-starter-webflux, spring-boot-starter-data-r2dbc, spring-boot-starter-security
What am i missing ? Thanks in Advance.
that is correct you should not block in a reactive application, neither should you subscribe in this usercase, as your application is most likely a producer, and the calling client is the consumer that subscribes.
what you are looking for is the doOn operators, that handles side effects. Logging is a side effect, its something you want to do on the side without disturbing the current flow. For instance update something, increment something, or in your case write to a log.
what you want os probably the doOnSuccess operator
example (i have not chacked against a compiler since im on mobile), but something like this.
function Mono<Login> foobar(Login login) {
return userRepository.findPasswordByUsername(login.getUsername)
.doOnSuccess(pwd -> {
logger.info(login.getPassword() + " " + pwd);
}).thenReturn(login);
}
Since nobody came up with an answer yet,
logger.info("userName :" +userMono.map(user -> user.getUsername()).share().block());
.share() will allow to .block()
from the docs:
"Prepare a Mono which shares this Mono result similar to Flux.shareNext().This will effectively turn this Mono into a hot task when the first Subscriber subscribes using subscribe() API. Further Subscriber will share the same Subscription and therefore the same result.It's worth noting this is an un-cancellable Subscription."
works for me (even though it incorporates .block() )
userMono.subscribe(user -> logger.info("username: "+user.getUsername()));
will return a Disposable
Both will output the String value for the logger.
on a sidenote (which i wasn't aware of) there is an operator .log("callerForADefinedLogger").

Retrofit Kotlin - make an API request followed by two more concurrent ones

I want to make an api request, then I need to make two more requests after I receive the data. I found a great SO answer that uses rxjava2 to make two concurrent requests here:
How to make multiple request and wait until data is come from all the requests in retrofit 2.0 - android
I suppose I could just chain the logic for this after the first request, but my intuition tells me thats a bad idea because I'd be duplicating some code (I'd have separate logic for the first request in a function, then some similar logic for the second two requests in a function)
Is there a better way to accomplish this? I'd prefer Kotlin, but Java is ok.
Here is the code for concurrent requests from the SO answer.
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.build()
val backendApi = retrofit.create(MyBackendAPI::class.java)
val requests = ArrayList<Observable<*>>()
requests.add(backendApi.getUser())
requests.add(backendApi.listPhotos())
requests.add(backendApi.listFriends())
Observable
.zip(requests) {
// do something with those results and emit new event
Any() // <-- Here we emit just new empty Object(), but you can emit anything
}
// Will be triggered if all requests will end successfully (4xx and 5xx also are successful requests too)
.subscribe({
//Do something on successful completion of all requests
}) {
//Do something on error completion of requests
}
Thanks

Check for lack of log messages when testing akka actors

When testing an Akka actor with the TestKit, https://doc.akka.io/docs/akka/2.5/testing.html shows how to verify that a given message was logged.
Is there a way to check for the lack of a message?
I have my actors set up to call a method the logs something like "Unexpected message received" when an unhandled message is received. In my test, I would like to verify that that message is never logged, even if the test otherwise seems to succeed. Is there a way to do that?
I am using Akka 2.5 and Java 10.
It depends on your implementation. You could do one of two things:
1) Create a TestKit probe and make it subscribe to your system's eventStream
yourActorSystemInTheTest.eventStream().subscribe(yourProbe.getRef(), UnhandledMessage.class);
And then at the end check to see how many messages the probe received, in your case 0. Use one of the many "expect..." methods at your disposal.
2) The docs tell you how to check for log messages, so just assert that the number of times you get the "Unexpected message received" is 0.
Again, depending on your actors' implementation, the above might not work.
Good Luck!
To provide some details, here is what I needed to do:
import akka.event.Logging;
import akka.testkit.javadsl.EventFilter;
import akka.testkit.javadsl.TestKit;
...
#Test
public void noUnhandledTest() {
new TestKit(system) {{
new EventFilter(Logging.Warning.class, system).
occurrences(0).
matches("unhandled event").
intercept(() -> {
try {
<actual test code>
// Intercept needs a supplier
return 0;
} catch (Exception e) {
// Suppliers can't throw
throw new RuntimeException(e);
}
});
}};
}
In src/test/resources/application.conf:
akka.loggers = [akka.testkit.TestEventListener ]

RxJava error handling for hot observable

I'm pretty new to RxJava and have some questions on patterns etc.
I'm creating an observable using the code below:
public Observable<Volume> getVolumeObservable(Epic epic) {
return Observable.create(event -> {
try {
listeners.add(streamingAPI.subscribeForChartCandles(epic.getName(), MINUTE, new HandyTableListenerAdapter() {
#Override
public void onUpdate(int i, String s, UpdateInfo updateInfo) {
if (updateInfo.getNewValue(CONS_END).equals(ONE)) {
event.onNext(new Volume(Integer.parseInt(updateInfo.getNewValue(LAST_TRADED_VOLUME))));
}
}
}));
} catch (Exception e) {
LOG.error("Error from volume observable", e);
}
});
}
Everything is working as expected, but I have some questions on error handling.
If I understand correctly, this is to be viewed as a "hot observble", i.e. events will happen regardless of there being a subscription or not (onUpdate is a callback used by a remote server which I have no control over).
I've chosen not to call onError here since I don't want the observable to stop emitting events in case of a single exception. Is there a better pattern to be used? .retry() comes to mind, but I'm not sure that it makes sense for a hot observable?
Also, how is the observable represented when the subscriptions is created, but before the first onNext is called? Is it just an Observable.empty()
1) Your observable is not hot. The distinguishing factor is whether multiple subscribers share the same subscription. Observable.create() invokes subscribe function for every subscriber, i.e. it is cold.
It is easy to make it hot though. Just add share() operator. It will subscribe with first subscriber and unsubscribe with last one. Do not forget to implement unsubscribe functionality with something like this:
event.setCancellable(() -> listeners.remove(...));
2) Errors could be recoverable and not recoverable.
In case you consider an error to be self-recoverable (no action required from your side) you should not call onError as this will kill your observable (no further events would be emitted). You can possibly notify your subscribers by emitting special Volume message with error details attached.
In case an error is fatal, e.g. you have failed to add listener, so there could be no further messages, you should not silently ignore this. Emit onError as your observable is not functional anyway.
In case an error requires actions from you, typically retry, or retry with timeout, you can add one of retryXxx() operators. Do this after create() but before share().
3) Observable is an object with subscribe() method. How exactly it is represented depends on the method you created it with. See source code of create() for example.

Categories