WebFlux functional: How to detect an empty Flux and return 404? - spring

I'm having the following simplified handler function (Spring WebFlux and the functional API using Kotlin). However, I need a hint how to detect an empty Flux and then use noContent() for 404, when the Flux is empty.
fun findByLastname(request: ServerRequest): Mono<ServerResponse> {
val lastnameOpt = request.queryParam("lastname")
val customerFlux = if (lastnameOpt.isPresent) {
service.findByLastname(lastnameOpt.get())
} else {
service.findAll()
}
// How can I detect an empty Flux and then invoke noContent() ?
return ok().body(customerFlux, Customer::class.java)
}

From a Mono:
return customerMono
.flatMap(c -> ok().body(BodyInserters.fromObject(c)))
.switchIfEmpty(notFound().build());
From a Flux:
return customerFlux
.collectList()
.flatMap(l -> {
if(l.isEmpty()) {
return notFound().build();
}
else {
return ok().body(BodyInserters.fromObject(l)));
}
});
Note that collectList buffers data in memory, so this might not be the best choice for big lists. There might be a better way to solve this.

Use Flux.hasElements() : Mono<Boolean> function:
return customerFlux.hasElements()
.flatMap {
if (it) ok().body(customerFlux)
else noContent().build()
}

In addition to the solution of Brian, if you are not want to do an empty check of the list all the time, you could create a extension function:
fun <R> Flux<R>.collectListOrEmpty(): Mono<List<R>> = this.collectList().flatMap {
val result = if (it.isEmpty()) {
Mono.empty()
} else {
Mono.just(it)
}
result
}
And call it like you do it for the Mono:
return customerFlux().collectListOrEmpty()
.switchIfEmpty(notFound().build())
.flatMap(c -> ok().body(BodyInserters.fromObject(c)))

I'm not sure why no one is talking about using the hasElements() function of Flux.java which would return a Mono.

Related

Skip chained function call on certain result(s)

I'm trying to implement a chain of function calls that could be expensive on their own, and I would like to only call the subsequent functions if the result of the previous satisfied the condition(s). For instance, I have these "models":
data class In(val a: Int, val c: String, val f: String, val t: String)
data class Out(val passed: Boolean, val code: String?)
...then this is the logic (never mind the variables/method names):
class Filter : SomeFilter {
override fun filter(input: In): Out {
return Stream.of(chML(input), chC(input), chA(input))
.filter { !it.passed }
.findFirst()
.orElseGet { Out(true, "SUCCESS") }
}
private fun chC(input: In): Out {
if (input.c !== "ADA") {
return Out(false, "INVALID_C")
}
return Out(true, "SUCCESS")
}
private fun chA(input: In): Out {
if (input.a >= 100) {
return Out(false, "INVALID_A")
}
return Out(true, "SUCCESS")
}
private fun chML(input: In): Out {
if (context.contains(input.f)) {
// context.add(input.to)
return Out(false, "INVALID_ML")
}
return Out(true, "SUCCESS")
}
}
The problem with these functions is that they should be expensive, so if the output from any of those is Out.passed === false, then I wouldn't like to call the next one because it could be interpreted as a terminal operation at that point.
Is there a way to implement this in such way without going the if/else-if route? The approach with streams is cleaner, but it does execute all the functions, regardless.
You can use sequence and yield
fun filter(input: In): Out {
return sequence {
yield(chML(input))
yield(chC(input))
yield(chA(input))
}
.firstOrNull { !it.passed }
?: Out(true, "SUCCESS")
}
You can use function references and invoke them in a map operation. Kotlin Sequences short-circuit based on any filters in the chain, including firstOrNull.
override fun filter(input: In): Out {
return sequenceOf(::chML, ::chC, ::chA)
.map { it(input) }
.firstOrNull { !it.passed }
?: Out(true, "SUCCESS")
}
By the way, I advise against using === or !== with Strings. That is checking reference equality which is very hard to use reliably unless all your source strings are private to this class so you can carefully make sure you know where/when they were instantiated and whether they were string literals. You should probably use != instead.

Return BehaviourSubject in RXSwift

I get a HTML page from my NodeJS instance with the signature:
public func requestHTMLPage(_ page: String) -> Observable<String?>
but I've got some cached HTML pages so want to return them.
I'd like to return my cached page
if let cachedPage = cachedPage {
return BehaviorSubject<String?>(value: data)
}
but unfortunately it does not tell my subscribers that the data is returned.
I've used the following awful solution:
if let cachedPage = cachedPage {
observer.onNext(data)
return BehaviorSubject<String?>(value: data)
}
as I want to tell my observer we have the data, but also I want to return as we are at the end of this path of execution.
How can I return and give the type Observable after I have made my observer.onNext, or how can I return with just observer.onNext?
I'm not sure why you try to use a Subject here, you probably simple need to use Observable.create:
public func requestHTMLPage(_ page: String) -> Observable<String?> {
return Observable.create { observer in
if cachedPage = self.cache[page] {
observer.onNext(cachedPage)
observer.onCompleted()
return Disposables.create()
} else {
/* whatever you use to fetch the data in a reactive way */
return self.service.rx.request(page).subscribe(observer)
}
}
}
Depending on where this code is, pay attention to retain cycles.

How to place a conditional check inside springboot project reactor Mono stream (written in Kotlin)?

Pretty new to project reactor here, I am struggling to put a conditional check inside my Mono stream. This part of my application is receiving an object from Kafka. Let's say the object is like this.
data class SomeEvent(val id: String, val type: String)
I have a function that handles this object like this.
fun process(someEvent: SomeEvent): Mono<String> {
val id = someEvent.id
val checkCondition = someEvent.type == "thisType"
return repoOne.getItem(id)
.map {item ->
// WHAT DO I DO HERE TO PUT A CONDITIONAL CHECK
createEntryForItem(item)
}
.flatMap {entry ->
apiService.sendEntry(entry)
}
.flatMap {
it.bodyToMono(String::class.java)
}
.flatMap {body ->
Mono.just(body)
}
}
So, what I want to do is check whether checkCondition is true and if it is, I want to call a function repoTwo.getDetails(id) that returns a Mono<Details>.
createEntryForItem returns an object of type Entry
apiService.sendEntry(entry) returns a Mono<ClientResponse>
It'd be something like this (in my mind).
fun process(someEvent: SomeEvent): Mono<String> {
val id = someEvent.id
val checkCondition = someEvent.type == "thisType"
return repoOne.getItem(id)
.map {item ->
if (checkCondition) {
repoTwo.getDetails(id).map {details ->
createEntryForItem(item, details)
}
} else {
createEntryForItem(item)
}
}
.flatMap {entry ->
apiService.sendEntry(entry)
}
.flatMap {
it.bodyToMono(String::class.java)
}
.flatMap {body ->
Mono.just(body)
}
}
But, obviously, this does not work because the expression inside the if statement is cast to Any.
How should I write it to achieve what I want to achieve?
UPDATED: The location of where I like to have the conditional check.
You should use flatMap() and not map() after getItem().
return repoOne.getItem(id)
.flatMap {item ->
if (checkCondition) {
repoTwo.getDetails(id).map {details ->
createEntryForItem(item, details)
}
} else {
Mono.just(createEntryForItem(item))
}
}
In a map{} you can transform the value. Because you want to call getDetails() (which returns a reactive type and not a value) to do that you have to use flatMap{}. And that's why you need to wrap your item in a Mono by calling Mono.just(createEntryForItem(item)) on the else branch.
Just split it to another function. Your code will be cleaner too.
repoOne.getItem(id)
.map { createEntry(it, checkCondition) }.
.flatMap.....
private fun createEntry(item, checkCondition): Item {
return if (checkCondition) {
repoTwo.getDetails(id).map { createEntryForItem(item, it) }
} else {
createEntryForItem(item)
}
}

Combine multiple Monos into a flux

I am appending a Flux with a flatMap, but if I add additional flatMaps, only the last one gets returned.
// Here is an example of the Mono function
private Mono<MyType> appendFirstMono(Group group) {
return Mono.just(group)
.map(MyType::new)
.flatMap(g -> functionZ(group)
.map(g::setField));
}
//This works as expected
public Flux<MyType> function1() {
return returnData(id)
.thenMany(service.getData(id))
.flatMap(this::appendFirstMono);
}
//This does not and only returns the last mono (3rd)
public Flux<MyType> function1() {
return returnData(id)
.thenMany(service.getData(id))
.flatMap(this::appendFirstMono)
.flatMap(this::appendSecondMono)
.flatMap(this::appendThirdMono);
}
//I've attempted to fix with this... Doesn't work as expected.
public Flux<MyType> function1() {
return returnData(id)
.thenMany(service.getData(id))
.flatMap(x -> {
return Flux.merge(
appendFirstMono(x),
appendSecondMono(x),
appendThirdMono(x)
);
});
}
I need to process each Mono function on the flux but I can't seem to get each to execute and return properly.
You can try concat to process the mono one by one check out my example
Flux.concat(getMono(0),getMono(1),getMono(2))
.map(integer -> {
System.out.println(integer);
return integer;
})
.subscribe();
}
private Mono<Integer> getMono(Integer a) {
return Mono.just(a)
;
}
this will print 0,1,2

correct use of retryWhen operator with RxSwift 4.0.0

with RxSwift 3.6.1 I made this extension to ObservableType to get a new token after an error request:
public extension ObservableType where E == Response {
public func retryWithToken() -> Observable<E> {
return retryWhen { error -> Observable<Response> in
return error.flatMap({ (error) -> Observable<Response> in
if let myApiError: MyApiError = error as? MyApiError {
if (myApiError == MyApiError.tokenError) {
return Session.shared.myProvider.request(.generateToken)
} else {
return Observable.error(myApiError)
}
}
return Observable.error(error)
})
}
}
}
and then I can use it:
Session.shared.myProvider.rx
.request(.mySampleRequest)
.filterSuccessfulStatusCodes()
.retryWithToken()
.subscribe { event in
....
}.disposed(by: self.disposeBag)
but with RxSwift 4.0.0 now the sequence expect a
PrimitiveSequence<SingleTrait, Response>
someone can explain to me how to do the same with RxSwift 4.0.0? I try with an extension to PrimitiveSequence but I've some compilation errors.
I believe that has nothing to do with RxSwift but is a Moya change. MoyaProvider.rx.request returns Single which is a typealias for PrimitiveSequence which is not an ObservableType.
You declare your function upon the ObservableType.
So just do asObservable() before retryWithToken()

Resources