So I have a method which returns an Vavr Try:
public Try<Result> request() {...}
request comes from a source which I cannot modify. Currently, I flatmap over the result from request and depending if the Result has an error return a Try with an exception or a success with the data from the Result:
public Try<Data> fetchData() {
return request().flatMap(result -> {
if (result.hasError()) {
return Try.failure(new FailedRequestException());
} else {
return Try.success(result.data());
}
});
}
What I want is in some places where fetchData is used first do something with the data if the Try is a success and if it is a failure, log an error if the error is a FailedRequestException, else, do something else with the exception, something like the following:
fetchData().andThen(data -> ...).onFailure(ex -> {
if (ex instanceOf FailedRequestException) {
log.error("Could not fetch data: " + ex.getMessage());
} else {
// Do something with the exception
...
}
});
My problem with this approach is that fetchData returns a Try so the caller cannot know that a FailedRequestException is part of the possible failures. I can let fetchData return a Try<Either<FailedRequestException, Data>> but this doesn't feel right either. Is there any way to do the above in a more elegant way? I also tried using the Match and Case but the Case expects a Function as handler and not a Consumer.
To sum up: you actually have 3 scenarios (success, failure with FailedRequestException, any other failure). This sounds like a job for pattern matching! Let's make the code as visible and expressive as the business requirement :)
Match(fetchData()).of(
Case($Success($()), data -> doStuff(data)),
Case($Failure($(instanceOf(FailedRequestException.class))), fre -> logFreAndReturnValue(fre)),
Case($Failure($()), e -> doSomethingWithOtherException(e))
);
FWIW, you can rewrite your fetchData implementation as such:
Try(request())
.mapFailure(Case($(), ignored -> new FailedRequestException()))
.map(Result::data);
As a rule of thumb, try to stick to using flatMap when the context (Success or Failure) may change. In your current fetchData implementation a success remains a success, a failure remains a failure, so it is a mapping between the input and the output, hence use map family of functions.
Cheers!
Related
It may sound funny or maybe novice, but I don't understand what it means that something "failed" in Reactive Programming. Do you mean a null? An empty object? someone could please give me examples.
Some of the scenarios I wish to realize are:
Assuming I send a query parameter and get either a listing with values or an empty listing; and lastly, I do not send the query parameter.
If an empty listing is issued I want to return an exception and a 404 status code.
If they do not send the query parameter I want to return an exception and some status code.
And of course, if a list with values is found, return it. Will it be possible to make these cases in a single method? how do I do it?
First, a reactor operator can ends in different ways:
Completes successfully after emitting one (Mono) or more (Flux) value(s)
Completes empty: The pipeline sends completion signal, but no value has been emitted
Completes in error: somewhere in the pipeline, an error happened. By default, as in imperative code, it stops the chain of operations, and is propagated.
Cancelled: the pipeline might be interrupted by a manual action or a system shutdown. It then ends in error (a special kind of error, but an error nonetheless)
Secondly, reactive-stream, whatever the implementation (RxJava or Reactor) does not accept null values. It means that trying to produce a null value in/from a reactive stream will either cause an error or an undefined behavior. This is stated in reactive-stream specification, rule 2.13:
Calling [...] onNext [...] MUST return normally except when any provided parameter is null in which case it MUST throw a java.lang.NullPointerException to the caller
Let's try to produce some simple examples first.
This program shows the possible ways a pipeline can complete:
// Reactive streams does not accept null values:
try {
Mono.just(null);
} catch (NullPointerException e) {
System.out.println("NULL VALUE NOT ACCEPTED !");
}
// Mono/Flux operations stop if an error occurs internally, and send it downstream
try {
Mono.just("Something")
.map(it -> { throw new IllegalStateException("Bouh !"); })
.block();
} catch (IllegalStateException e) {
System.out.println("Error propagated: "+e.getMessage());
}
// A mono or a flux can end "empty". It means that no value or error happened.
// The operation just finished without any result
var result = Mono.just("Hello !")
.filter(it -> !it.endsWith("!"))
// Materialize allow to receive the type of signal produced by the pipeline (next value, error, completion, etc.)
.materialize()
.block();
System.out.println("Input value has been filtered out. No 'next' value " +
"received, just 'completion' signal:" + result.getType());
Its output:
NULL VALUE NOT ACCEPTED !
Error propagated: Bouh !
Input value has been filtered out. No 'next' value received, just 'completion' signal:onNext
Then, let's look at a program that intercept empty pipelines and errors, and handle them gracefully:
// Errors can be intercepted and replaced by a value:
var result = Mono.error(new IllegalStateException("No !"))
.onErrorResume(err -> Mono.just("Override error: Hello again !"))
.block();
System.out.println(result);
// Empty pipelines can also be replaced by another one that produce a value:
result = Mono.just("Hello !")
.filter(it -> !it.endsWith("!"))
.switchIfEmpty(Mono.just("Override empty: Hello again !"))
.block();
System.out.println(result);
It produces:
Override error: Hello again !
Override empty: Hello again !
With all this tools, we can solve the problem you describe with your query.
Let's mimic it:
public static Flux<String> processRequest(String queryParam) {
if (queryParam == null || queryParam.isEmpty()) return Flux.error(new IllegalArgumentException("Bad request"));
return Mono.just(queryParam)
.flatMapMany(param -> Flux.fromArray(param.split("_")))
.switchIfEmpty(Mono.error(new IllegalStateException("No data")));
}
public static void main(String[] args) {
String[] inputs = { null, "hello_world", "___" };
for (var input : inputs) {
try {
String result = processRequest(input)
.collect(Collectors.joining(", ", "[", "]"))
.block();
System.out.println("COMPLETED: " + result);
} catch (Exception e) {
System.out.println("ERROR: " + e.getMessage());
}
}
}
It prints:
ERROR: Bad request
COMPLETED: [hello, world]
ERROR: No data
I'm running into an issue with CompletableFutures. I have a JAX RS-based REST endpoint that reaches out to an API, and I need to make 3 sequential calls to this API. Current flow looks like this:
FruitBasket fruitBasket = RestGateway.GetFruitBasket("today");
Fruit chosenFruit = chooseFruitFromBasket(fruitBasket);
Boolean success = RestGateway.RemoveFromBasket(chosenFruit);
if (success) {
RestGateway.WhatWasRemoved(chosenFruit.getName());
} else {
throw RuntimeException("Could not remove fruit from basket.");
}
return chosenFruit
Of course, each of the calls to RestGateway.SomeEndpoint() is blocking because it does not use .async() in building my request.
So now let's add .async() and return a CompletableFuture from each of the RestGateway interactions.
My initial thought is to do this:
Fruit chosenFruit;
RestGateway.GetFruitBasket("today")
.thenCompose(fruitBasket -> {
chosenFruit = chooseFruitFromBasket(fruitBasket);
return RestGateway.RemoveFromBasket(chosenFruit);
})
.thenCompose(success -> {
if(success) {
RestGateway.WhatWasRemoved(chosenFruit);
} else {
throw RuntimeException("Could not remove fruit from basket.");
});
return chosenFruit;
Because this seems to guarantee me that execution will happen sequentially, and if a previous stage fails then the rest will fail.
Unfortunately, this example is simple and has many less stages than my actual use-case. It feels like I'm writing lots of nested conditionals inside of stacked .thenCompose() blocks. Is there any way to write this in a more sectioned/compartmentalized way?
What I'm looking for is something like the original code:
FruitBasket fruitBasket = RestGateway.GetFruitBasket("today").get();
Fruit chosenFruit = chooseFruitFromBasket(fruitBasket);
Boolean success = RestGateway.RemoveFromBasket(chosenFruit).get();
if (success) {
RestGateway.WhatWasRemoved(chosenFruit.getName()).get();
} else {
throw RuntimeException("Could not remove fruit from basket.");
}
return chosenFruit
But the calls to .get() are blocking! So there is absolutely no benefit from the asynchronous re-write of the RestGateway!
TL;DR - Is there any way to preserve the original code flow, while capturing the benefits of asynchronous non-blocking web interactions? The only way I see it is to cascade lots of .thenApply() or .thenCompose methods from the CompletableFuture library, but there must be a better way!
I think this problem is solved by await() in JavaScript, but I don't think Java has anything of that sort.
As the folks in the comments section mentioned, there's not much that can be done.
I will however close this off with a link to another stack that proved tremendously helpful, and solved another problem that I didn't explicitly mention. Namely, how to pass values forward from previous stages while dealing with this warning:
variable used in lambda expression should be final or effectively final.
Using values from previously chained thenCompose lambdas in Java 8
I ended up with something like this:
CompletableFuture<Boolean> stepOne(String input) {
return RestGateway.GetFruitBasket("today")
.thenCompose(fruitBasket -> {
Fruit chosenFruit = chooseFruitFromBasket(fruitBasket);
return stepTwo(chosenFruit);
});
}
CompletableFuture<Boolean> stepTwo(Fruit chosenFruit) {
return RestGateway.RemoveFromBasket(chosenFruit)
.thenCompose(success -> {
if (success) {
return stepThree(chosenFruit.getName());
} else {
throw RuntimeException("Could not remove fruit from basket.");
}
});
}
CompletableFuture<Boolean> stepThree(String fruitName) {
return RestGateway.WhatWasRemoved(fruitName);
}
Assuming that RestGateway.WhatWasRemoved() returns a Boolean.
I have a java 8 functional interface that accepts a list of validators that are applied on an object and returns the validation result. The validation results are accumulated in the reduce phase. The code as follows:
public interface LogicalTableValidator extends Function<LogicalTable, ValidationResult> {
static LogicalTableValidator addAll(LogicalTableValidator... validators) {
// Need to break out of this validator stream, based on the criticality of a particular validation error
return logicalTable -> Arrays.stream(validators).map(v -> v.apply(logicalTable))
.reduce(new ValidationResult(logicalTable.getUid()), (validationResult, currentResult) -> {
validationResult.addValidationMessages(currentResult.getValidationMessages());
return validationResult;
});
}
}
This validation logic gets called from here
LogicalTableValidator logicalTableValidators = LogicalTableValidator.addAll(getValidators());
List<ValidationResult> ltValidationResults = logicalTables.stream()
.parallel()
.map(logicalTableValidators)
.collect(Collectors.toList());
The problem I am facing is that, I am not able to break from the validation logic conditionally. This will be the case when I am applying the validators on the logicalObject, if the validation fails with a critical error, I dont need to run rest of the validators. Instead I need to stop the validation process right there.
A work around would be not to use lambda expression for validation and use the following code instead.
return new LogicalTableValidator() {
#Override
public ValidationResult apply(LogicalTable t) {
ValidationResult result = new ValidationResult(t.getUid());
for (LogicalTableValidator validator : validators) {
ValidationResult currentResult = validator.apply(t);
List<ValidationMessage> messages = currentResult.getValidationMessages();
Boolean exit = false;
for (ValidationMessage message : messages) {
if(StringUtils.equalsIgnoreCase(message.getSeverity(), "1")) {
exit = true;
break;
}
}
result.addValidationMessages(currentResult.getValidationMessages());
if (exit) break;
}
return result;
}
};
It seems, not using lambda expression in functional interface, defeats the purpose of using functional interface, but I couldn't figure out a way to conditionally break out of the validation loop. Is there any alternative I can use? Should this code be structured in a different way?
You can try something like below. In peek it collect ValidationMessages. In filter and findFirst it stop after first error message. It is replacement for takeWhile that was mentioned in comments, you can also check this.
public interface LogicalTableValidator extends Function<LogicalTable, ValidationResult> {
static LogicalTableValidator addAll(LogicalTableValidator... validators) {
logicalTable -> {
ValidationResult result = new ValidationResult(logicalTable.getUid());
Arrays.stream(validators).map(v -> v.apply(logicalTable))
.peek(currentResult -> result.addValidationMessages(currentResult.getValidationMessages()))
.filter(currentResult -> currentResult.getValidationMessages().stream()
.filter(message -> StringUtils.equalsIgnoreCase(message.getSeverity(), "1"))
.count() > 0)
.findFirst()
.orElse(null);
return result;
}
}
}
There are two distinct things: 1. breaking logicalTables stream and 2. breaking validators stream.
For logicalTables, your stream is parallel and even if the break was possible you would obtain potentially different results.
For validators stream, Stream.takeWhile seems to be the closest to pure stream-based solution. Unfortunately, it is in JDK since Java 9 and moreover it doesn't comprise invalid ValidationResult into resulting stream. Alternatives might exist in external libraries though the imperative code seems to me as simplest and readable enough at this moment.
Even though I am using Supplier for my streams and using Supplier.Get() each time I want to retrieve my strem and perform a terminal operation on it, I am still getting the "stream has already been operated upon or closed" exception. Could anyone please look at my code and suggest what I am doing wrong?
Method where exception is being thrown:
private static void printMyDetails(Supplier<Stream<Result>> mySupplier, String myStatus) {
checkNotNull(mySupplier);
checkArgument(isNotEmpty(myStatus), "Invalid My status");
if (mySupplier.get().noneMatch(result -> true)) { //<-This is where the exception is thrown
if (log.isInfoEnabled()) {
log.info("Number of My with status '{}': {}", My, 0);
}
} else {
log.info("Number of My with status '{}': {}", myStatus, mySupplier.get().count());
log.info("Details of My(s) with status: '{}'", myStatus);
mySupplier.get().sorted().forEach(Utils::printMyNameAndDetails);
}
}
Place which is calling the above method:
rb.keySet().stream().filter(key -> containsIgnoreCase(key, "status")).map(rb::getString)
.filter(StringUtils::isNotEmpty).forEach(status -> {
var resultsWithExpectedStatusSupplier = requireNonNull(getResultsWithExpectedStatusSupplier(results, status));
resultsWithExpectedStatusSupplier.ifPresentOrElse(streamSupplier -> printMyDetails(streamSupplier, status), () -> {
if (log.isInfoEnabled())
log.info("0 My with status: {}", status);
});
});
The stream supplier:
private static Optional<Supplier<Stream<Result>>> getResultsWithExpectedStatusSupplier(
#NotNull List<Result> results, #NotNull String expectedStatus) {
checkArgument(!results.isEmpty(), "Results list is empty");
checkArgument(isNotEmpty(expectedStatus), "Invalid expected status");
var resultStreamWithExpectedStatus = requireNonNull(results.stream().filter(result -> ofNullable(result).map(Result::getStatus)
.allMatch(s -> isNotEmpty(s) && s.equalsIgnoreCase(expectedStatus))));
return resultStreamWithExpectedStatus.count() == 0 ? Optional.empty() : Optional.of(() -> resultStreamWithExpectedStatus);
}
The general problem is as Christian Ullenboom said: The stream has already been consumed. The exact location in your code is the call to resultStreamWithExpectedStatus.count() in the method getResultsWithExpectedStatusSupplier, as Stream.count is a reduction/terminal operation which consumes the stream.
As stated in e.g. this answer, you cannot get the streams size without consuming it. Fix it by storing the filtered items in a collection (e.g. Collectors.toList), querying the size there and returning the collection itself rather than the stream?
On a side note, I think you misuse Optional a bit too much. The code could be simpler passing empty streams (or even better: pass an empty, filtered collection).
You can consume a Stream just once. It looks like the Supplier is always giving the same Stream again and again. After the first terminal operation the Stream is drained; the Stream from the Supplier has to be a new Stream all the time.
I am new to Java Rx, I don't know if that is a valid question or not.
I have function
public Single<PayResponse> pay(PayRequest apiRequest) {
return client.initiatePayment(apiRequest)
.doOnSuccess(initiatePaymentResponse -> {
System.out.println("first");
client.confirmPayment(initiatePaymentResponse.getPaymentId())
.doOnSuccess(confirmPaymentResponse -> {System.out.println("second");doConfirmationLogic(confirmPaymentResponse ))}
.doOnError(ex -> {System.out.println("thirs");ex.printStackTrace();logError(ex);});
})
.doOnError(ex -> {ex.printStackTrace();logError(ex);});
}
after executing this method i can find first was printed twice but neither second nor third was printed
It is odd behaviour for me, because i expect to find first and second or third.
Any idea ?
In order to start receiving the emitted value(s) from an observable (like a Single<T>), you must subscribe() to it first.
You are probably only subscribing to the Single returned by pay twice somewhere else, and that's why you see first printed two times. In the code you show, I can see that are not subscribing to any of the observable there, so nothing will happen afterwards.
If you want to chain observables, the most common choice would be to use the flatMap operator (there are other options as well).
In your case, it would look similar to this:
public Single<PayResponse> pay(PayRequest apiRequest) {
return client.initiatePayment(apiRequest)
.flatMap(initiatePaymentResponse -> {
System.out.println("first");
return client.confirmPayment(initiatePaymentResponse.getPaymentId();
})
.flatMap(confirmPaymentResponse -> {
System.out.println("second");
return doConfirmationLogic(confirmPaymentResponse);
})
.doOnSuccess(confirmationLogicResponse -> System.out.println("third"))
.doOnError(ex -> {
ex.printStackTrace();
logError(ex);
});
}
Then, you subscribe to the single returned by pay somewhere else like this:
...
pay(apiRequest)
.subscribe(onSuccesValue -> {
// The whole chain was successful and this is the value returned
// by the last observable in the chain (doConfirmationLogic in your case)
}, onError {
// There was an error at some point during the chain
}
...
I am supposing that all the methods initiatePayment, confirmPayment, doConfirmationLogic return Singles and that doConfirmationLogic ends up returning a Single<PayResponse>. If that's not the case, you will need to make some small changes, but you get the general idea of how chaining observables work.