how to render an object with two flux fields without blocking? - spring

I want to render an object composed of two mono or flux elements (below a code snippet):
Mono<List<NodeDTO>> nodeDTOFlux = this.webClient
.get()
.uri(NODES_WITH_LIMIT + limit)
.retrieve()
.onStatus(HttpStatus::isError,
response -> response.bodyToMono(String.class).flatMap(
msg -> Mono.error(new ApiCallException(msg, response.statusCode())))
)
.bodyToFlux(new ParameterizedTypeReference<Node>() {
}).map(node -> nodeMapper.toNodeDTO(node))
.collectList();
Mono<List<EdgeDTO>> edgeDTOFlux = this.webClient
.get()
.uri(EDGES_WITH_LIMIT + limit)
.retrieve()
.onStatus(HttpStatus::isError,
response -> response.bodyToMono(String.class).flatMap(
msg -> Mono.error(new ApiCallException(msg, response.statusCode())))
)
.bodyToFlux(new ParameterizedTypeReference<Edge>() {
}).map(edge -> edgeMapper.toEdgeDTO(edge))
.collectList();
I tried with zip() method but it's not what I aim to do
I tried to return an object like this
GraphDataDTO graphDataDTO = new GraphDataDTO();
graphDataDTO.setEdgeDTOS(edgeDTOFlux);
graphDataDTO.setNodeDTOS(nodeDTOFlux);
I have a result in my console but the object returned
{
"nodeDTOS": {
"scanAvailable": true
},
"edgeDTOS": {
"scanAvailable": true
}
}
the return is done before getting all the flux.. is there any solution without blocking !
thanks in advance.

This should work:
return Mono.zip(nodeDTOFlux, edgeDTOFlux)
.map(tuple2 -> GraphDataDTO.builder().nodeDTO(tuple2.getT1()).edgeDTO(tuple2.getT2()).build())
It creates a Tuple of NodeDTO and EdgeDTO and maps it into GraphDataDTO.

Related

Subscriber context not found in doOnError

I was wondering if someone could eyeball the following code snippet and tell me why the SubscriberContext inside the doOnError is not trigerred
public Mono<ServerResponse> handlePlatformAuthenticationResponse(final ServerRequest serverRequest) {
Mono<MultiValueMap<String, String>> formData = serverRequest.body(BodyExtractors.toFormData());
return formData
.flatMap(this::provisionUserAndClass)
.flatMap(tuple -> Mono.subscriberContext()
.map(context -> {
// this is invoked if provisionUserAndClass completes successfully
TelemetryData telemetryData = context.get(TELEMETRY_DATA);
LTILaunchRequest<LTILaunchRequestSettings> launchRequest = tuple.getT2();
this.addLaunchDetailsToTelemetryContext(launchRequest, telemetryData);
return tuple;
}))
.doOnError(error -> Mono.subscriberContext()
.map(context -> {
// this is never invoked if provisionUserAndClass returns a Mono.error
TelemetryData telemetryData = context.get(TELEMETRY_DATA);
// log telemetryData + error message
}))
.subscriberContext(context -> context.put(TELEMETRY_DATA, new TelemetryData()));
}
private Mono<Tuple2<ClassAndUserProvisioningResponse, LTILaunchRequest<LTILaunchRequestSettings>>> provisionUserAndClass(
LTILaunchRequest<LTILaunchRequestSettings> ltiLaunchRequest) {
// returning a Mono.error just to see behavior of Mono.subscriberContext() when error occurs. Actual code will call a service method
return Mono.error(new ProvisioningException("fake"));
}
To access context in case of error you could use doOnEach operator:
.doOnEach(signal -> {
if (signal.isOnError())
{
TelemetryData telemetryData = signal.getContext().get(TELEMETRY_DATA);
Throwable error = signal.getThrowable();
// ...
}
})
Mono.subscriberContext() can only be used meaningfully in operators where you have to return a Mono, like flatMap, concatMap, etc, but not in side-effect operators where there is nothing that would subscribe to the Mono<Context>.
.doOnError(error -> Mono.subscriberContext()
.map(context -> {
// this is never invoked if provisionUserAndClass returns a Mono.error
TelemetryData telemetryData = context.get(TELEMETRY_DATA);
// log telemetryData + error message
}).subscribe())
You forgot to subscribe to the Mono.subscriberContext().

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)
}
}

CompletableFuture List wait for all future issue

I have the below list of CompletableFuture pipeline
for (Integer unit : getIds()) {
futureList.add(CompletableFuture.completedFuture(unit)
.thenApply(id -> CompletableFuture
.completedFuture(service.get1(id))
.thenCombineAsync(CompletableFuture.completedFuture(b), (a, df1) ->
CompletableFuture.completedFuture(service.validate(Optional.ofNullable(a),c,
b, dealerCode))
.thenApplyAsync(status -> CompletableFuture.completedFuture(b))
.thenCombineAsync(CompletableFuture.completedFuture(a), (b1, a1) ->
service.mapToTrans(Optional.ofNullable(a), b, c))
.handle((response, exception) -> {
if(exception == null) {
return response;
}
else {
handler.handleException(exception, results);
return null;
}
})
.thenApplyAsync(t -> service.submitRequest(Optional.ofNullable(a), c))
.thenApplyAsync((t) -> service.saveTransaction(Optional.ofNullable(t)))
.thenAccept(result1 -> results.add(result1))
)
.handle((response, exception) -> handleStage(response, exception, results))
)
.handle((response, exception) -> handleStage(response, exception, results))
);
}
The results array holds the result of all the futures. In the main method this is how I am waiting for all the futures to get completed
CompletableFuture<Void> allFutures = CompletableFuture
.allOf(futureList.toArray(new CompletableFuture[futureList.size()]));
CompletableFuture<List<Object>> allFutureExec = allFutures.thenApply(v -> {
return futureList.stream().map(pg-> pg.join()).collect(Collectors.toList());
});
allFutureExec.get();
The "results" list is supposed to contain the result of all future chain But I can see that main thread is not waiting for all the stages to get executed. The result list has the result of only few futures, though I can see from the logs that all the futures are getting executed. Please let me know if this is the correct way to wait for all the futures to get completed.
UPDATE-1
for (Integer unit : getIds()) {
futureList.add(CompletableFuture
.completedFuture(service.get1(unit))
.thenCombine(CompletableFuture.completedFuture(td), (vd, dealerInfo1) ->
CompletableFuture.completedFuture(service.validate(Optional.ofNullable(vd),sd,
td))
.thenApply(status -> CompletableFuture.completedFuture(service.mapToTrans
(Optional.ofNullable(vd), td,trade, userCredential))
.thenApplyAsync(transaction -> CompletableFuture.completedFuture(service.submit(Optional.ofNullable(vd),transaction))
.thenApply((transaction1) -> service.saveTransaction(Optional.ofNullable(transaction1)))
.thenApply(result1 -> results.add(result1)))))
);
}

Problem with testing virtual time with spring-webflux WebClient

The test below passes when I use monoFromSupplier as selectedMono.
However, when I switch to monoFromWebClient it doesn't advance time properly. What am I doing wrong here?
StepVerifier.withVirtualTime(() -> {
Mono<String> monoFromSupplier = Mono.fromSupplier(() -> "AA")
.doOnNext(po -> {
System.out.println("monoFromSupplier:onNext " + Thread.currentThread().getName());
});
Mono<String> monoFromWebClient = WebClient.create("http://...")
.get()
.retrieve()
.bodyToMono(String.class)
.doOnNext(po -> {
System.out.println("monoFromWebClient:onNext " + Thread.currentThread().getName());
});
Mono<?> selectedMono = monoFromSupplier;
return selectedMono.repeatWhen(companion -> companion.take(3)
.delayUntil(r -> {
Duration dur = Duration.ofSeconds(500);
System.out.println("delay... " + dur);
return Mono.delay(dur);
}))
.last()
.log();
})
.thenAwait(Duration.ofDays(1))
.expectNextCount(1)
.expectComplete()
.verify();
Reactor virtual time support only works within a single JVM - it works by changing the Scheduler's clock (often making it tick faster). WebClient here crosses a network boundary and sends a real HTTP request - Reactor can't manipulate the real, physical time.
TL;DR; this is not supported.

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

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.

Resources