Project reactor - not able to use fallback method - java

I'm new to reactive programming. I was playing around with fallback methods in case of an error scenario. For this, I referred to this doc https://projectreactor.io/docs/core/release/reference/index.html#_fallback_method and created a sample code.
public static void main(String[] args) {
Flux.just("key1", "key2")
.flatMap(k -> callExternalService(k)
.doOnError(e -> System.out.println("Error scenario"))
.onErrorResume(e -> getFromCache(k)))
.subscribe(value -> System.out.println("value = " + value),
error -> System.out.println("error = " + error));
}
private static Mono<String> callExternalService(String input) {
if(input.equals("key2")) throw new RuntimeException("Mocking the exception");
return Mono.just(input + " - " + LocalDateTime.now());
}
private static Mono<String> getFromCache(String input) {
return Mono.just(input + " ^^ " + LocalDateTime.now());
}
Based on whatever I referred so far, doOnError should print the message in case of the ERROR scenario and onErrorResume should fall back to the other method. But I didn't see the expected outcome->
value = key1 - 2022-05-18T15:58:36.364949
error = java.lang.RuntimeException: Mocking the exception
Please correct me if I'm missing anything.

Related

Reactor fireAndForget Mono pass context

I am trying to pass subscriber Context to the fireAndForget method which is called inside the doOnNext. The fireAndForget is run also async non-blocking. How this context might be passed so the value for "key" is present? When I run the following test it passes. However, in the logs I can see that for both doOnNext I get:
reactor.core.Exceptions$ErrorCallbackNotImplemented: java.util.NoSuchElementException: Context is empty
#Test
void shouldPassContextToFireAndForget() {
final Mono<String> helloWorldMono = Mono.just("hello")
.doOnNext(this::fireAndForget)
.doOnNext(name -> Mono.deferContextual(contextView -> fireAndForget(contextView, name)).subscribe())
.flatMap(name -> Mono.deferContextual(contextView -> Mono.just(name + " " + contextView.get("key"))))
.contextWrite(Context.of("key", "world"));
StepVerifier.create(helloWorldMono)
.expectNext("hello world")
.verifyComplete();
}
private Mono<String> fireAndForget(ContextView context, String name) {
return Mono.just(name)
.flatMap(value -> Mono.deferContextual(contextView -> Mono.just(value + contextView.get("key"))))
.contextWrite(context);
}
private void fireAndForget(String name) {
Mono.just(name)
.flatMap(value -> Mono.deferContextual(contextView -> Mono.just(value + contextView.get("key"))))
.subscribe();
}
Context is a subscribe-time concept. There are two possible approaches.
You can expose the ContextView at the middle of the chain using transformDeferredContextual:
final Mono<String> helloWorldMono = Mono.just("hello")
.transformDeferredContextual((original, cntx) -> original.doOnNext(name-> fireAndForget(cntx, name).subscribe()))
.flatMap(name -> Mono.deferContextual(contextView -> Mono.just(name + " " + contextView.get("key"))))
.contextWrite(Context.of("key", "world"));
Alternatively, you could take advantage of Mono.deferContextual in order to expose the ContextView at the start of the chain like this:
final Mono<String> helloWorldMono = Mono.deferContextual(context ->
Mono.just("hello")
.doOnNext(name -> fireAndForget(context, name).subscribe())
.flatMap(name -> Mono.just(name + " " + context.get("key")))
).contextWrite(Context.of("key", "world"));

Getting the line number of the Mono/Flux that returned Mono.empty()

Let's say I have a long chain of Monos. Some monos in the chain might return Mono.empty().
I can recover with switchIfEmpty, but I'd like to know which mono raised the empty (maybe so I can know where to add smarter empty handling).
Is there a way to programmatically get this information?
Silly example. In cases where I return how did I get here?, how I can know if the first flatMap or the second flatMap triggered the empty handler?
Mono.just("data")
.flatMap(t -> {
if (System.currentTimeMillis() % 2 == 0) {
return Mono.empty();
}
return Mono.just("happy1");
})
.flatMap(t -> {
if (System.currentTimeMillis() % 2 == 0) {
return Mono.empty();
}
return Mono.just("happy2");
})
.map(s -> {
return "successful complete: " + s;
})
.switchIfEmpty(Mono.fromCallable(() -> {
return "how did I get here?";
}))
.block();
Due to the dynamic nature of Flux and Mono, and to the fact that the onComplete signal is considered neutral enough that it is usually just passed through, there is no generic solution for this.
In your particular example, you could replace the Mono.empty() with something like Mono.empty().doOnComplete(() -> /* log something */).
You could even directly perform the logging in the if block, but the decorated empty trick is probably adaptable to more situations.
Another possibility is to turn emptiness into an error, rather than a switch on onComplete signal.
Errors are less neutral, so there are ways to enrich them for debugging purposes. For instance, with a .checkpoint("flatMapX") statement after each flatMap, you'd get additional stacktrace parts that would point to the flatMap which failed due to emptyness.
A way of turning emptiness to error in Mono is .single(), which will enforce exactly one onNext() or propagate onError(NoSuchElementException).
One thing to keep in mind with this trick is that the placement of checkpoint matters: it MUST be AFTER the single() so that the error raised from the single() gets detected and enriched.
So if I build on your snippet:
static final String PARSEABLE_MARKER = "PARSEABLE MARKER: <";
static final char MARKER_END = '>';
String parseLocation(Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
String trace = sw.toString();
int start = trace.indexOf(PARSEABLE_MARKER);
if (start > 0) {
trace = trace.substring(start + PARSEABLE_MARKER.length());
trace = trace.substring(0, trace.indexOf(MARKER_END));
return trace;
}
return "I don't know";
}
String testInner() {
Random random = new Random();
final boolean first = random.nextBoolean();
return Mono.just("data")
.flatMap(t -> {
if (System.currentTimeMillis() % 2 == 0 && first) {
return Mono.empty();
}
return Mono.just("happy1");
})
.single()
.checkpoint(PARSEABLE_MARKER + "the first flatMap" + MARKER_END)
.flatMap(t -> {
if (System.currentTimeMillis() % 2 == 0 && !first) {
return Mono.empty();
}
return Mono.just("happy2");
})
.single()
.checkpoint(PARSEABLE_MARKER + "the second flatMap" + MARKER_END)
.map(s -> {
return "successful complete: " + s;
})
.onErrorResume(NoSuchElementException.class, e ->
Mono.just("how did I get here? " + parseLocation(e)))
.block();
}
This can be run in a loop in a test for instance:
#Test
void test() {
int successCount = 0;
int firstCount = 0;
int secondCount = 0;
for (int i = 0; i < 100; i++) {
String message = testInner();
if (message.startsWith("how")) {
if (message.contains("first")) {
firstCount++;
}
else if (message.contains("second")) {
secondCount++;
}
else {
System.out.println(message);
}
}
else {
successCount++;
}
}
System.out.printf("Stats: %d successful, %d detected first, %d detected second", successCount, firstCount, secondCount);
}
Which prints something like:
Stats: 85 successful, 5 detected first, 10 detected second

Understanding Mono.when

I'm new to Spring WebFlux and do not fully understand the Mono.when(). The following code does not work as expected:
List<Mono<Void>> results= ....;
String textVar = "my text";
processors.forEach(p -> {
Mono<Object> restResponseMono = client.getSomething();
results.add(restResponseMono.doOnNext(resp -> {
textVar = textVar + resp.getText();
}).then());
});
Mono.when(results).then(
//here it would expect modification of 'textVar'
Mono.just(textVar);
)
After calling the Mono.when(results).then(...) I would expect all my changes to be applied to the textVar because in the docu it is written:
[...Aggregate given publishers into a new Mono that will befulfilled when all of the given Publishers have completed....]
And the restResponseMono.then() should also wait until everything is completed. So I do not know exactly where is my lack of understanding.
Publishers in the when parameter,When subscribe automatically subscribes.
The way it works is simply this.
Flux<Integer> m1 = Flux.just(1,2).doOnNext(e -> System.out.println("M1 doOnNext: " + e));
Mono<Integer> m2 = Mono.just(12).doOnSuccess(e -> System.out.println("M2 doOnSuccess: " + e));
Mono<String> mono = Mono.when(m1,m2).then(Mono.just("STR")).doOnSuccess(e -> System.out.println("_________when doOnSuccess: " + e));
mono.log().subscribe(System.out::println, Exception::new, () -> System.out.println("Completed2."));

Parse response from lambda

I want to use this code to make Rest API requests and parse the response:
Mono<PaymentTransaction> result = client.execute(transaction)
.doOnSuccess(
response -> System.out.println(response.transactionResponse
+ ", code = " + response.responseCode))
.doOnError(e -> System.out.println("Error " + e.getMessage()));
result.block(Duration.ofSeconds(30));
private void parseRawResponse(PaymentTransaction result) {
// do something
}
How I can send the result to parseRawResponse(PaymentTransaction result) on doOnSuccess? I'm not aware in this case how to use the lambda expression.

Unable to write Mockito test case for my void method

I need to test this code with Mockito (JUnit):
public class Calculation {
public void logTimeTaken(String label, long estimatedTime, int size, boolean isDebug) {
String out = label + " took " + TimeUnit.MILLISECONDS.convert(estimatedTime, TimeUnit.NANOSECONDS) + " milliseconds for " + size + " events!";
if (isDebug) {
System.out.println(out);
} else {
System.out.println(out);
}
}
}
I search so many examples google but still not getting any idea.
You can configure System with an instance of PrintStream which you can then assert against after invoking Calculation.logTimeTaken.
Here's an example:
#Test
public void canLogTimeTaken() {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
PrintStream out = new PrintStream(bout);
System.setOut(out);
Calculation sut = new Calculation();
sut.logTimeTaken("label", 20 , 2, false);
assertEquals("if isDebug is false label took 0 milliseconds for 2 events!\n", bout.toString());
}
Note: there is no need for Mockito here, this is just vanilla JUnit, no mocking.
But, it might be a better design to refactor logTimeTaken into two distinct aspects:
Deriving the log message
Logging that message
For example:
public String createTimeTakenMessage(String label, long estimatedTime, int size, boolean isDebug) {
return label + " took " + TimeUnit.MILLISECONDS.convert(estimatedTime, TimeUnit.NANOSECONDS) + " milliseconds for " + size + " events!";
}
public void logTimeTaken(String message) {
System.out.println(message);
}
Then testing createTimeTakenMessage is trivial and you might even choose not to test logTimeTaken at all since all it does is invoke a System method. Or, perhaps you would hide the 'log action' behind an interface with an implementation using System.out now and perhaps, later, other implementations using a formal logging framework such as Logback.

Categories

Resources