Project Reactor provides a Tuple data structure that can hold about 8 different types.
Tuples are very useful, however one of the issues in using a Tuple is that it is difficult to make out what they hold without de-structuring them at every place they get used.
Problem
Consider a simple operator, which zips together a String and an integer this way:Mono<Tuple2<String, Integer>> tup = Mono.zip(Mono.just("a"), Mono.just(2))
The output is a Tuple holding 2 elements.
Now, the problem that I have is this. I prefer the tuple to be dereferenced into its component elements before doing anything with it.
Say with the previous tuple, I want to generate as many of the strings(first element of the tuple) as the count(the second element of the tuple), expressed the following way:
Mono.zip(Mono.just("a"), Mono.just(2)) .flatMapMany(tup -> { return Flux.range(1, tup.getT2()).map(i -> tup.getT1() + i); })
The problem here is when referring to "tup.getT1()" and "tup.getT2()", it is not entirely clear what the tuple holds. Though more verbose I would prefer doing something like this:
Mono.zip(Mono.just("a"), Mono.just(2)) .flatMapMany(tup -> { String s = tup.getT1(); int count = tup.getT2(); return Flux.range(1, count).map(i -> s + i); });
Solution
The approach of de-structuring into explicit variables with meaningful name works, but it is a little verbose. A far better approach is provided using a utility set of functions that Project reactor comes with called TupleUtilsI feel it is best explained using a sample, with TupleUtils the previous de-structuring looks like this:
Mono.zip(Mono.just("a"), Mono.just(2)) .flatMapMany(TupleUtils.function((s, count) -> Flux.range(1, count).map(i -> s + i)))
This looks far more concise than explicit de-structuring. There is a bit of cleverness that needs some getting used to though:
The signature of flatMapMany is -
public final <R> Flux<R> flatMapMany(Function<? super T,? extends Publisher<? extends R>> mapper)
TupleUtils provides another indirection which returns the Function required above, through another function:
public static <T1, T2, R> Function<Tuple2<T1, T2>, R> function(BiFunction<T1, T2, R> function) { return tuple -> function.apply(tuple.getT1(), tuple.getT2()); }
If you are using Kotlin, there is a simpler approach possible. This is based on the concept of "Destructuring Declarations". Project reactor provides a set of Kotlin helper utilities using an additional gradle dependency:
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
With this dependency in place, an equivalent Kotlin code looks like this:
Mono.zip(Mono.just("a"), Mono.just(2)) .flatMapMany { (s: String, count: Int) -> Flux.range(1, count).map { i: Int -> s + i } }
See how a tuple has been de-structured directly into variables. This looks like this in isolation:
val (s: String, count: Int) = tup
Conclusion
It is important to de-structure a tuple into more meaningful variables to improve readability of the code and the useful functions provided by the TupleUtils as well as the Kotlin extensions helps keep the code concise but readable.The Java samples are here and Kotlin samples here
No comments:
Post a Comment