WebFlux and null value - spring-webflux

I have simple dto, with a field that can be null
public ResponseDto{
...
#Nullable
public List<ListDto> getListDto() {
return this.listDto;
}
...
}
How to correctly implement the check that disappeared remove the warning
#NotNull
public Flux<ListDto> getApplicationList(String applicationSubsidiesId) {
return Mono.fromCallable(() -> mapper.toRq(applicationSubsidiesId))
.subscribeOn(Schedulers.boundedElastic())
.flatMap(subsidiesClient::getResponseById)
.filter(responseDto -> Objects.nonNull(responseDto.getListDto()))
.map(ResponseDto::getListDto) <- Return null or something nullable from a lambda in transformation method
.flatMapMany(Flux::fromIterable);
}
One of my decisions - rewrite map
.map(responseDto -> Objects.requireNonNull(responseDto .getListDto()))
Are there any other options on how to correctly implement this check?

null should be Empty in a reactive context. You cannot return null from a mapper, at least not in Reactor/WebFlux.
If you need to further process all values, even if they are null, I would suggest using an optional.
The idiomatic approach in WebFlux would be to filter out unwanted values completely and the react to an empty Mono with defaultIfEmpty() or switchIfEmpty():
#NotNull
public Flux<ListDto> getApplicationList(String applicationSubsidiesId) {
final var defaultResponseDto = new ResponseDto();
return Mono.fromCallable(() -> mapper.toRq(applicationSubsidiesId))
.subscribeOn(Schedulers.boundedElastic())
.flatMap(subsidiesClient::getResponseById)
.filter(responseDto -> Objects.nonNull(responseDto.getListDto()))
// filter may cause an empty flux, in which case the next line
// will not be executed.
.flatMapMany(Flux::fromIterable)
// in case of an empty flux, this line will kick in:
.defaultIfEmpty(Flux.fromIterable(defaultResponseDto.getListDto()));
// as an alternative, you can call for a fallback:
// .switchIfEmpty(getAnotherFluxFromSomewhereElse());
}

Related

Spring Cloud Stream deserialization error handling for Batch processing

I have a question about handling deserialization exceptions in Spring Cloud Stream while processing batches (i.e. batch-mode: true).
Per the documentation here, https://docs.spring.io/spring-kafka/docs/2.5.12.RELEASE/reference/html/#error-handling-deserializer, (looking at the implementation of FailedFooProvider), it looks like this function should return a subclass of the original message.
Is the intent here that a list of both Foo's and BadFoo's will end up at the original #StreamListener method, and then it will be up to the code (i.e. me) to sort them out and handle separately? I suspect this is the case, as I've read that the automated DLQ sending isn't desirable for batch error handling, as it would resubmit the whole batch.
And if this is the case, what if there is more than one message type received by the app via different #StreamListener's, say Foo's and Bar's. What type should the value function return in that case? Below is the pseudo code to illustrate the second question?
#StreamListener
public void readFoos(List<Foo> foos) {
List<> badFoos = foos.stream()
.filter(f -> f instanceof BadFoo)
.map(f -> (BadFoo) f)
.collect(Collectors.toList());
// logic
}
#StreamListener
public void readBars(List<Bar> bars) {
// logic
}
// Updated to return Object and let apply() determine subclass
public class FailedFooProvider implements Function<FailedDeserializationInfo, Object> {
#Override
public Object apply(FailedDeserializationInfo info) {
if (info.getTopics().equals("foo-topic") {
return new BadFoo(info);
}
else if (info.getTopics().equals("bar-topic") {
return new BadBar(info);
}
}
}
Yes, the list will contain the function result for failed deserializations; the application needs to handle them.
The function needs to return the same type that would have been returned by a successful deserialization.
You can't use conditions with batch listeners. If the list has a mixture of Foos and Bars, they all go to the same listener.

How can I find the first element's method result that is not null?

So I have parsers and want to use the first that does return a non-null value. How would I do that most elegantly?
return parsers.map { it.parse(content) }.firstOrNull { it != null }
would map all (million?) parsers before picking the first.
return parsers.firstOrNull { it.parse(content) != null }?.parse(content)
would run the (expensive?) parse() once again.
I know I can
for (parser in parsers) {
val result = parser.parse(content)
if (result != null) {
return result
}
}
return null
parsers.forEach { it.parse(content)?.run { return this } }
return null
is the shortest I can get but it's not nice to read.
I'm pretty sure there is a shortcut here that I don't see.
Use a sequence. It makes your computation lazy, so that you will only compute parse as many times as you need.
return parsers.asSequence()
.map { it.parse(content) }
.find { it != null }
As an alternative to the overhead of a Sequence, or mapping lots of values unnecessarily, you could use an extension method such as:
inline fun <T, R> List<T>.firstMappedNotNull(transform: (T) -> R): R? {
for (e in this)
return transform(e) ?: continue
return null
}
This uses the minimum of mapping function calls and temporary objects.  It's necessarily written in an imperative way, but it's quite short, and makes your own code short, clear, and functional.
(This version returns null if the list was empty or every mapping returned null.  You could of course change the signature and last line to throw an exception instead.)
It's a shame this function isn't already in the standard library.  But it's easy to add your own!
Also, you can use the following code:
parsers.asSequence()
.mapNotNull { it.parse(content) }
.first()

Custom retry in Reactor

I was trying to implement a retry logic in Kotlin and Reactor based on the Reactor extra package's features. What I'm trying to do is pass a list of durations, and on each context.iteration I'm getting the (iteration-1)th element of the list. It works partly, I'm always getting an IndexOutOfBoundsException on the last iteration, which is more than I wanted, although I've provided a max number of retries - the size of the list. The retries are running though, in the given duration and "correct" number of times (surely because IndexOutOfBoundsException prevents more), only this exception (and it's root cause) bothers me.
This is my custom BackOff interface:
interface MyCustomBackoff : Backoff {
companion object {
fun getBackoffDelay(backoffList: List<Duration>): (IterationContext<*>) -> BackoffDelay {
return { context -> BackoffDelay(backoffList[(context.iteration() - 1).toInt()]) }
}
}
}
And my Kotlin extension is:
fun <T> Mono<T>.retryCustomBackoffs(backoffList: List<Duration>, doOnRetry: ((RetryContext<T>) -> Unit)? = null): Mono<T> {
val retry = Retry.any<T>().retryMax(backoffList.size.toLong()).backoff(MyCustomBackoff.getBackoffDelay(backoffList))
return if (doOnRetry == null) {
this.retryWhen(retry)
}
else {
this.retryWhen(retry.doOnRetry(doOnRetry))
}
}
What am I missing here?
If you look at reactor.retry.AbstractRetry#calculateBackoff, you could find that there is special BackoffDelay named RETRY_EXHAUSTED. And it returns when retryContext.iteration() > maxIterations (not >=) after backoff.apply(retryContext)
if (retryContext.iteration() > maxIterations || Instant.now(clock).plus(jitteredBackoff).isAfter(timeoutInstant))
return RETRY_EXHAUSTED;
So, if you have 2 custom backoff delays in the list, there will be 3 backoff delays generated by calculateBackoff.
You could change your MyCustomBackoff like so (excuse me for Java, I'm not familiar with Kotlin):
public interface MyCustomBackoff extends Backoff {
static Backoff getBackoffDelay(List<Duration> backoffList) {
return context -> context.iteration() <= backoffList.size() ?
new BackoffDelay(backoffList.get(Long.valueOf(context.iteration() - 1).intValue())) :
new BackoffDelay(Duration.ZERO);
}
}

How to asynchronously map over sequence

I want to iterate over a sequence of objects and return the first non-null of an async call.
The point is to perform some kind of async operation that might fail, and I have a series of fallbacks that I want to try in order, one after the other (i.e. lazily / not in parallel).
I've tried to do something similar to what I'd do if it were a sync call:
// ccs: List<CurrencyConverter>
override suspend fun getExchangeRateAsync(from: String, to: String) =
ccs.asSequence()
.map { it.getExchangeRateAsync(from, to) }
.firstOrNull { it != null }
?: throw CurrencyConverterException()
IntelliJ complains:
Suspension functions can only be called within coroutine body
Edit: To clarify, this works as expected if mapping on a List, but I want to see how I'd do this on a sequence.
So I guess this is because the map lambda isn't suspended? But I'm not sure how to actually do that. I tried a bunch of different ways but none seemed to work. I couldn't find any examples.
If I re-write this in a more procedural style using a for loop with an async block, I can get it working:
override suspend fun getExchangeRateAsync(from: String, to: String) {
for (cc in ccs) {
var res: BigDecimal? = async {
cc.getExchangeRateAsync(from, to)
}.await()
if (res != null) {
return res
}
}
throw CurrencyConverterException()
}
You are getting an error, because Sequence is lazy by default and it's map isn't an inline function, so it's scope isn't defined
You can avoid using Sequence by creating a list of lazy coroutines
// ccs: List<CurrencyConverter>
suspend fun getExchangeRateAsync(from: String, to: String) =
ccs
.map { async(start = CoroutineStart.LAZY) { it.getExchangeRateAsync(from, to) } }
.firstOrNull { it.await() != null }
?.getCompleted() ?: throw Exception()
This doesn't give any errors and seems to be working. But I'm not sure it's an idiomatic way
I would suggest replacing Sequence with Flow. Flow api and behavior is pretty much same as for Sequence, but with suspending options.
https://kotlinlang.org/docs/reference/coroutines/flow.html
Code:
override suspend fun getExchangeRateAsync(from: String, to: String) =
ccs.asFlow()
.map { it.getExchangeRateAsync(from, to) }
.firstOrNull { it != null }
?: throw CurrencyConverterException()
FWIW, I found the suggestion in How to asynchronously map over sequence to be very intuitive. The code at https://github.com/Kotlin/kotlin-coroutines-examples/blob/master/examples/suspendingSequence/suspendingSequence.kt defines SuspendingIterator which allows next() to suspend, then builds SuspendingSequence on top of it. Unfortunately, you need to duplicate extension functions like flatMap(), filter(), etc. since SuspendingSequence can't be related to Sequence, but I did this and am much happier with the result than using a Channel.

How to tell IDEA/Studio that the null check has been done?

I'm developing with Android Studio/IntelliJ IDEA.
I have enabled the inspection check called "Constant conditions & exceptions" that shows a warning if I am risking a NPE, such as:
String foo = foo.bar(); // Foo#bar() is #nullable
if (foo.contains("bar")) { // I'm living dangerously
...
}
I have the following in my code:
String encoding = contentEncoding == null ? null : contentEncoding.getValue();
if (!TextUtils.isEmpty(encoding) && encoding.equalsIgnoreCase("gzip")) {
inputStream = new GZIPInputStream(entity.getContent());
} else {
inputStream = entity.getContent();
}
Here's the source code of TextUtils#isEmpty(String):
/**
* Returns true if the string is null or 0-length.
* #param str the string to be examined
* #return true if str is null or zero length
*/
public static boolean isEmpty(CharSequence str) {
if (str == null || str.length() == 0)
return true;
else
return false;
}
I'm not risking any NPE because TextUtils#isEmpty(String) would return true to a null pointer.
However I'm still getting the little Method invocation 'encoding.equalsIgnoreCase("gzip")' may produce 'java.lang.NullPointerException' warning, which can be annoying.
Is it possible to make this check smarter and ignore the NPE warning if there's already a null-check done?
You can look into the link that Peter Gromov mention in his answer.
Created some simple classes that resemble your setup:
A class with a method annotated with #Nullable:
The TextUtil class with it's isEmpty method:
And finally the main class calling the TextUtil#isEmpty:
Now if you enter the File -> Settings... and go to Inspections ->Constant conditions & exceptions part you can change the Configure Assert/Check Methods to cater for your isEmpty method:
Add a new IsNull check method:
Enter the TextUtil class, isEmpty method and CharSequence parameter:
This gives this Assert/Check Method Configuration window:
Press Ok and then Ok again to go back to the editor view and you'll see that the inspection disappeared:
You are actually telling IntelliJ that the isEmpty method is doing a null check on the str parameter.
You could use //noinspection ConstantConditions that will remove the NPE warning for the following line, like this:
String encoding = contentEncoding == null ? null : contentEncoding.getValue();
//noinspection ConstantConditions
if (!TextUtils.isEmpty(encoding) && encoding.equalsIgnoreCase("gzip")) {
inputStream = new GZIPInputStream(entity.getContent());
} else {
inputStream = entity.getContent();
}
You can use #SuppressWarnings("ConstantConditions") annotation.
#SuppressWarnings("ConstantConditions")
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int indexViewType) {
if (inflater == null) {
inflater = LayoutInflater.from(parent.getContext());
}
ItemViewProvider provider = getProviderByIndex(indexViewType);
provider.adapter = MultiTypeAdapter.this;
return provider.onCreateViewHolder(inflater, parent);
}
Select "TextUtils.isEmpty".
Right Click -> Show Context Actions -> Add Method Contract.
Enter "null -> true".
Save the configuration xml.
Please check the details here
See http://www.jetbrains.com/idea/webhelp/configuring-check-assert-methods.html for IDEA 12.
In IDEA 13 EAP, you can add method contract: http://youtrack.jetbrains.com/issue/IDEA-93372
Unfortunately marked as "right answer" solution is of date. But I found equivalent for me solution.
The new versions of IDE work correctly with static methods. So the example from the question won't throw warning anymore.
TextUtils#isEmpty(String);
public static boolean isEmpty(CharSequence str) {
// your checks
}