How can catch MonoError? - java

I need to catch MonoError and stop an application with ErrorResponse, but the application works as I did not expect.
My code:
return checkText(text)
.then(getWordsFromText(text))
.map(keyWords -> new SuccessfulResponse(keyWords))
.onErrorResume(
throwable -> {
return Mono.just(new ErrorResponse(throwable.getMessage()));
});
public Mono<Void> checkText(String text) {
if (text == null) {
return Mono.error(new Exception("wrong text"));
}
return Mono.empty();
}
my problem is that if text param is null -> I fall into getWordsFromText method. This is an incorrect execution, because if the text parameter is equal to null, then the application must exit with an error (with ErrorResponse).
I fixed it as (replacing 'then' to 'flatMap'):
return checkText(text)
.flatMap(voidParam -> getWordsFromText(text)) //replaced 'then' to 'flatMap'
.map(keyWords -> new SuccessfulResponse(keyWords))
.onErrorResume(
throwable -> {
return Mono.just(new ErrorResponse(throwable.getMessage()));
});
and now it's working correctly. If text param is null I miss the call getWordsFromText method and fall in error handling (onErrorResume).
But I think using flatMap in my case is not a good idea, I don't like how it looks: .flatMap(voidParam -> ...
Can you have any ideas how possible to do better? (without 'flatMap')

In the first snippet, the call to getWordsFromText() is made while building your main reactive pipeline, before it is even subscribed to (i.e. at assembly time). The reason it works as intended in the second snippet is that flatMap only creates the inner publishers (and subsequently subscribes to them) as it receives elements from upstream (i.e. at subscription time).
In this case if you want to replace the flatMap you could try this: .then(Mono.fromCallable(() -> getWordsFromText(text)))

Related

How to get an element that caused an exception in Flux?

Let's say I have an array of ids: [9, 8, 7, 6].
I do some processing and one element causes to throw an exception. I want to handle this situation on my own way (let's say log it) and let the other elements go with the flow.
How can I know which one was it? I need to have this element in my onError processing.
Flux.fromArray(myArray)
.flatMap(element -> {
var foo = processMyEl(element);
return anotherProcess(foo); // this returns Mono
})
.onErrorOperator(element -> handleMyError(element)) // this line is what I need
So, what I saw, there's this almost nice .onErrorContinue((error, obj) -> that emits an error and an object.
But this obj is not the element that caused the exception but the object that did so. It happens inside of my processing methods and it doesn't have to be the same type of object every time.
.onErrorReturn(...) - not really what I want
.doOnError(error -> - no information of my element
.onErrorResume(error -> - same as above
there were suggestions that I can create my own Exception and pass there the element and then retrieve it from the exception. But how should I throw the exception?
Should I go with an old way of try catch:
Flux.fromArray(myArray)
.flatMap(el -> {
try {
var foo = processMyEl(el);
return anotherProcess(foo); // this returns Mono
} catch (Exception e) {
return Mono.error(new MyException(el));
}
})
.onErrorOperator(error -> handleMyError(error.getElement()))
It doesn't look well
Edit:
Not only it looks bad, but also doesn't work. The exception is not caught at all and triggers directly doOnTerminate() and stops the whole stream
Update:
Thanks to #JEY I used .onErrorResume() inside flatMap.
I also transformed first method to be a reactive stream by Mono.defer(() -> Mono.just(processMyEl(el))).
Just as a note: using Mono.defer() allows me to use onErrorResume since Mono.just() cannot signal errors.
Final code looks like this:
Flux.fromArray(myArray)
.flatMap(element -> Mono.defer(() -> Mono.just(processMyEl(element)))
.onErrorResume(th -> handleMyError(element, th))
)
.flatMap(foo -> anotherProcess(foo)
.onErrorResume(th -> handleMyError(foo, th)
)
Where:
private Mono<> handleMyError(el, th) {
// handling code
return Mono.empty()
}
As requested by #Kamil I'll add my comments as an answer:
You should just handle the error in the flatMap and return a Mono.empty() to discard it do something like:
Flux.fromArray(myArray)
.flatMap(el -> anotherProcess(processMyEl(el)).onErrorResume(th -> handleError(th, el))
With handle error like:
Mono<Void> handleError(Throwable th, Object element) {
LOG.error("An error occurred on {}", element, th);
return Mono.empty()
}
Or if you want to do something more complex that require async:
Mono<Void> handleError(Throwable th, Object element) {
return doSomethingThaReturnFluxOrMono(element).then();
}
} catch (Exception e) {
throw new MyException(el, e);
}

How to wait until List<Mono<List<Object>>> finishes?

It's my first time working with webClient and I am wondering how to wait until List<Mono<>> finishes. I have the following code:
List<Address> addresses = collectAllAddresses(someObject);
List<Mono<List<AnotherAddress>>> monoResponses =
addresses.stream()
.map(address -> webClientGateway.findAddresses(userData, address.getFullAddress()))
.collect(Collectors.toList());
Mono.when(monoResponses).block();
log.info("mono responses");
monoResponses.stream()
.flatMap(it -> Objects.requireNonNull(it.block()).stream()).forEach(it -> log.info("mono responses + {}", it));
and the following findAddresses method:
public Mono<List<AnotherAddress>> findAddresses(UserData userData, String fullAddress) {
if (StringUtils.isEmpty(fullAddress)) {
log.info("Address is empty that why we return Mono.just(Collections.emptyList()");
return Mono.just(Collections.emptyList());
}
return webClient.get()
.uri(path, uri -> uri.queryParam("query", fullAddress).queryParam("count", 1).build())
.header("someHeader", someHeader)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<AnotherAddress>>() {
})
.doOnError(e -> log.error("Error occurred!", e));
}
but every time I execute it I always get list of empty objects, I mean I get List but every object in that list is empty (every field of class AnotherAddress is null). What can be wrong?
UDP: more explanations:
I have two microservices. In one microservice (that return another address) there is RestController that sends anotherAddress. In another microservice I want to use WebClient (instead of using threadPool with many threads) to call the RestController from previous microservice. When I have previous implementation for function webClientGateway.findAddresses(userData, address.getFullAddress()) and it returns Mono<List> I tested it and immediately after calling function I call block on result and it works. But now I have following situation, I have many addresses (maybe 5 or 10) and I want send async request for every address and wait until latest finishes and after that I want to do another operation, but instead of getting fullfielded AnotherAddress instance, I am getting 5 empty AnotherAddress instances (every field is null)
Use a Flux instead of a Mono, e.g. something like (untested):
public Flux<AnotherAddress> findAddresses(UserData userData, String fullAddress) {
if (StringUtils.isEmpty(fullAddress)) {
log.info("Address is empty that why we return Mono.just(Collections.emptyList()");
return Flux.empty();
}
return webClient.get()
.uri(path, uri -> uri.queryParam("query", fullAddress).queryParam("count", 1).build())
.header("someHeader", someHeader)
.retrieve()
.bodyToFlux(AnotherAddress.class)
.doOnError(e -> log.error("Error occurred!", e));
}
If you don't need the AnotherAddress list grouped by address the you could use something like (untested):
Flux<AnotherAddress> anotherAddressFlux= Flux.fromIterable(addresses)
.flatMap(address -> webClientGateway.findAddresses(userData, address.getFullAddress()));
If you want to block you can use:
List<AnotherAddress> anotherAddressList = anotherAddressFlux.collectList().block();

Preventing RxJava from wrapping my custom exception into composite one

I was given a task to make implementation using RxJava, and safeguard in such way, that if any error happens, it gets wrapped into custom exception.
Problem is, that regardless of what I do RxJavaPlugin decides to wrap my exception into CompositeException even when there is only one. Which fails tests.
I've tried everything I could find on the google all the way up to actually overwriting RxJavaPlugin's global error handler, but it ignores attempted changes.
implementation of function that is supposed to throw it at the moment of writing this post
Single<BigDecimal> summarizeConfirmedTransactions() throws SummarizationException {
try{
Observable<Transaction> observableTransactions = transactions.get()
.doOnError(throwable -> {
Exceptions.propagate(throwable);
});
Observable<Confirmation> observableConfirmations = confirmations.get()
.doOnError(throwable -> {
Exceptions.propagate(throwable);
});
return Observable.zip(observableTransactions, observableConfirmations, (t, c) -> new ConfirmableTransaction(t.transactionId, c.isConfirmed, t.value))
.filter(confirmableTransaction -> confirmableTransaction.isConfirmed)
.map(t -> t.value)
.reduce(BigDecimal::add)
.toSingle()
.doOnError(throwable -> {
Exceptions.propagate(throwable);
});
}catch(Exception e)
{
throw new SummarizationException(e.getMessage());
}
Assertion in test fails because exception ultimately thrown is CompositeException with single exception inside of it. and I am required to have it be of SummarizationException.class
big thanks in advance.
Edit:
On request, here is code used to test the solution. I am not allowed to modify this one.
#Test
public void shouldWrapErrorIntoException() {
final ConfirmedTransactionSummarizer summarizer =
new ConfirmedTransactionSummarizer(ALL_CONFIRMED::transactions, () -> Observable.error(new RuntimeException("Booom")));
summarizer
.summarizeConfirmedTransactions()
.subscribe(testObserver);
testObserver.assertError(SummarizationException.class);
testObserver.assertErrorMessage("Booom");
}
PS. I've asked the giver of the task, he said that "I'm the only one with such problem" and that I should not overcomplicate things and go for easiest solution.... which results in composite exception of 3 exceptions - one of which is my exception wrap and other two are instances of RuntimeException inserted by test code.
Ok, so after a little bit of more digging, and with helpful tip from a friend I've managed to nail down intention of the task:
What I was supposed to do there was:
Single<BigDecimal> summarizeConfirmedTransactions() throws SummarizationException {
Observable<Transaction> observableTransactions = transactions.get();
Observable<Confirmation> observableConfirmations = confirmations.get();
return Observable.zip(observableTransactions, observableConfirmations,
(t, c) -> new ConfirmableTransaction(c.isConfirmed, t.value))
.filter(confirmableTransaction -> confirmableTransaction.isConfirmed)
.map(t -> t.value)
.reduce(BigDecimal::add)
.toSingle()
.onErrorResumeNext(th -> Single.error(new SummarizationException(th.getMessage())));
}
TL:DR I was not supposed to "wrap" errors into thrown exception but wrap them into error response containing exception.
One way to handle this is by using try-catch block in your test, unwraping CompositeException and then asserting caught exception.
fun testSummarizationException() {
try {
summarizeConfirmedTransactions()
} catch (ex: Exception) {
val customException = (ex as? CompositeException)?.exceptions?.get(0)
// assert to make sure thrown exception is of custom type
assert(customException is SummarizationException)
}
}
This is where CompositeException is unwrapped to get custom exception.
val customException = (ex as? CompositeException)?.exceptions?.get(0)
Exception is type-casted to CompositeException if it's permissible. If casting is not allowed for this type, this will return null and fails the test.

Project Reactor: Designing a reactive API

I have a map function which defined as follows: Mono<OUT> map(IN in)
Here's a concrete example:
public Mono<Integer> map(String s) {
return Mono.fromCallable(() -> {
try {
Thread.sleep(1_000); // simulate HTTP request
return 1;
} catch (Exception e) {}
return -1; // need to return something.
});
}
The problem is that in case of an error (i.e. IOException), we still need to return some output. There's also a possibility that there might no be an answer (but no error occurred)
One solution could be an Optional::empty but I think it's cumbersome. Preferably, I'd like to return Mono::empty if an error occurred.
The reason is, Mono::empty gets consumed by the subscriber without any further handling. Here's an example:
Flux.just(
Mono.just("123"),
Mono.empty(),
Mono.just("456")
).flatMap(s -> s)
.subscribe(System.out::println);
The output would be:
123
456
How can achieve the same behaviour?
What should map look like?
EDIT:
Rethinking it, maybe I better off return some container (like Optional) or a custom one (Result) which can be empty.
If I understand correctly, here's what you need:
return Mono.fromCallable(() -> {
Thread.sleep(1_000); // simulate HTTP request
return 1;
}).onErrorResume(_ -> Mono.empty())

rxjava2 - if else on Maybe

I am looking for what is the recommended practice in rxjava2 to handle a case where one flowable leads to conditional behaviors.
More concretely, I have a Maybe<String> for which I want to Update the String on the database if the String exists or, if it doesn't exists I want to create a new String and save it on the database.
I thought of the below but obviously it is not what I am looking for:
Maybe<String> source = Maybe.just(new String("foo")); //oversimplified source
source.switchIfEmpty(Maybe.just(new String("bar"))).subscribe(result ->
System.out.println("save to database "+result));
source.subscribe(result -> System.out.println("update result "+result));
The above obviously produces
save to database foo
update result foo
I tried also the below which gives the expected result but still feel it's... weird.
Maybe<String> source = Maybe.just(new String("foo")); //oversimplified source
source.switchIfEmpty(Maybe.just(new String("bar")).doOnSuccess(result ->
System.out.println("save to database "+result))).subscribe();
source.doOnSuccess(result -> System.out.println("update result "+result)).subscribe();
How can I have an action for when the result exists and when it doesn't exists? How is that use case supposed to be handled in rxjava2?
Update 01
I tried the below and it looks cleaner than what I came up with above. Note sure it is recommended rxjava2 practice however...
Maybe.just(new String("foo"))
.map(value -> Optional.of(value))
.defaultIfEmpty(Optional.empty())
.subscribe(result -> {
if(result.isPresent()) {
System.out.println("update result "+result);
}
else {
System.out.println("save to database "+"bar");
}
});
You have the isEmpty() operator that will return you Boolean if the Maybe source is empty or not, and then you can flatMap it and write a if else statement depending on that Boolean
This is a common pattern in our code as well, though in our case the choices are themselves async. You can't get quite the right semantic by simply composing flatMapX and switchIfEmpty (in either order), so I am curious why this isn't part of the API.
Here's what we're doing for now (this example for when the 2 options are both Completables, we have similar things for the other types as well):
public static <T> Completable flatMapCompletable(Maybe<T> target,
#ClosureParams(FirstParam.FirstGenericType.class)
Closure<? extends CompletableSource> completableSupplier,
Supplier<CompletableSource> emptySupplier) {
Maybe<T> result = target.cache();
return result.isEmpty().flatMapCompletable(empty -> {
if (empty) {
return emptySupplier.get();
} else {
return result.flatMapCompletable(completableSupplier::call);
}
});
}
We're using Groovy, so we package these up as extension methods. I'm not thrilled with the need to use cache() so I'm wondering if there is a better alternative. From looking at the code, an operator which basically combines flatMapX and switch looks like it wouldn't be too hard (but I feel like I'm missing something).
Try something like this. checkDB can return a Maybe or Single or whatever which emits either an optional or a wrapper Object.
checkDB(String)
.flatMap(s -> {
if (s.isPresent()) {
return updateDB(s.get());
} else {
return insertDB("new String");
}
})
There is an solution using the flatMap call with 3 params
fun addOrUpdate(message: LocalMessage): Single<LocalMessage> {
return getById(message.id) // returns Maybe
.flatMap(
Function {
update(message) // onSuccess update call returns Single
},
Function {
Single.error(it) // onError
},
Callable {
add(message) // onComplete add call returns Single
}
)
}
}
Or shorter version
fun addOrUpdate(message: LocalMessage): Single<LocalMessage> {
return getById(message.id) // returns Maybe
.flatMap(
{
update(message) // onSuccess update call returns Single
},
{
Single.error(it) // onError
},
{
add(message) // onComplete add call returns Single
}
)
}
}

Categories

Resources