Mutiny - Propagate completion to parent multi / polling - java

I am writing a little polling mechanism using Mutiny, part of me learning the library and i am kinda stuck in cancelling the polling when result is found.
I tried using the tick() and what i came up with looks like
Multi.createFrom().ticks().every(Duration.ofSeconds(5))
.onItem().transformToMultiAndMerge(tick -> {
System.out.println("Tick:" + tick);
return Multi.createFrom()
.<Transaction>emitter(
emitter -> {
service.getTransactions().toMulti()
.onItem().transformToMultiAndMerge(
transactions -> Multi.createFrom().iterable(transactions))
.subscribe().with(transaction -> {
if (!verification.isOngoing()) {
emitter.fail(new TransactionVerificationException());
} else {
boolean transactionFound = transaction.getAmount().stream().anyMatch(
amount -> amount.getQuantity()
.equals("test"));
if (transactionFound) {
emitter.emit(transaction);
emitter.complete();
}
}
});
});
})
.subscribe()
.with(transaction -> log.info(transaction),
x -> x.printStackTrace());
Problem here is that the Multi from ticks() is running forever and the only way i think of to cancel it would be to propagate somehow that the emitter has completed.
The case here is that i want to emit, and process only if certain conditions are met.

You approach is almost correct, though,
there is no need to create a custom MultiEmitter out of an existing Multi (or transformed Uni) as you can leverage the different Multi operators on top of your source service#getTransaction result
you missed the EmptyMulti source which will automatically complete downstream subscriber chain and which you can use to signal an absence of valid item (i.e. Transaction)
you need to select the first valid item (being non-null) then transform your Multi to Uni which will result in the upstream subscription being cancelled automatically once an item is received
Here down what the stream pipeline would look like:
Multi.createFrom()
.ticks()
.every(Duration.ofSeconds(5))
.onItem()
// flat map the ticks to the `service#getTransactions` result
.transformToMultiAndMerge(tick -> service.getTransactions()
.toMulti()
.onItem()
// flatten Collection<Transaction> to Multi<Transaction>
.transformToIterable(Function.identity())
.onItem()
.transformToMultiAndMerge(transaction -> {
if (!verification.isOngoing()) {
return Multi.createFrom().failure(new TransactionVerificationException());
} else {
boolean transactionFound = transaction.getAmount()
.stream()
.anyMatch(amount -> amount.getQuantity().equals("test"));
if (transactionFound) {
return Multi.createFrom().item(transaction);
} else {
return Multi.createFrom().empty();
}
}
})
)
.select()
.first(Objects::nonNull)
.toUni()
.subscribe()
.with(transaction -> log.info(transaction), x -> x.printStackTrace());

Related

How can catch MonoError?

I need to catch MonoError and stop an application with ErrorResponse, but the application works as I did not expect.
My code:
return checkText(text)
.then(getWordsFromText(text))
.map(keyWords -> new SuccessfulResponse(keyWords))
.onErrorResume(
throwable -> {
return Mono.just(new ErrorResponse(throwable.getMessage()));
});
public Mono<Void> checkText(String text) {
if (text == null) {
return Mono.error(new Exception("wrong text"));
}
return Mono.empty();
}
my problem is that if text param is null -> I fall into getWordsFromText method. This is an incorrect execution, because if the text parameter is equal to null, then the application must exit with an error (with ErrorResponse).
I fixed it as (replacing 'then' to 'flatMap'):
return checkText(text)
.flatMap(voidParam -> getWordsFromText(text)) //replaced 'then' to 'flatMap'
.map(keyWords -> new SuccessfulResponse(keyWords))
.onErrorResume(
throwable -> {
return Mono.just(new ErrorResponse(throwable.getMessage()));
});
and now it's working correctly. If text param is null I miss the call getWordsFromText method and fall in error handling (onErrorResume).
But I think using flatMap in my case is not a good idea, I don't like how it looks: .flatMap(voidParam -> ...
Can you have any ideas how possible to do better? (without 'flatMap')
In the first snippet, the call to getWordsFromText() is made while building your main reactive pipeline, before it is even subscribed to (i.e. at assembly time). The reason it works as intended in the second snippet is that flatMap only creates the inner publishers (and subsequently subscribes to them) as it receives elements from upstream (i.e. at subscription time).
In this case if you want to replace the flatMap you could try this: .then(Mono.fromCallable(() -> getWordsFromText(text)))

Is it possible to create a dynamic filter/predicate in Mono/Flux?

The app is a simple processing - reading data, filtering it, process and write filtered data.
Here is a simple code which runs without any issue:
void run() {
Flux.interval(Duration.ofMillis(200))
.filter(value -> getPredicate().test(value))
.flatMap(this::writeData)
.subscribe();
}
private Predicate<Long> getPredicate() {
return value -> value % 2 == 0;
}
Is it possible to have dynamic predicate which will be retrieved from remote web service with periodic requests?
If possible - how to use Mono<Predicate> inside .filter() and keep it non-blocking
For example replacing getPredicate() with below:
private Mono<Predicate<Long>> getPredicateFromRemoteServer() {
return webClient.get()
.uri("/filters/1")
.retrieve()
.bodyToMono(Filter.class)
.map(this::mapToPredicate)
.cache(v -> Duration.ofMinutes(10), ex -> Duration.ZERO, () -> Duration.ZERO);
}
private Predicate<Long> mapToPredicate(Filter filter) {
// here will be converting filter object into predicate
return value -> value > 5;
}
Ideally I would like to avoid cache(Duration.ofMinutes(10)) because filter could be updated each minute, or each day... and once filter is updated my service get notified, but I didn't find a way to invalidate cache externally, that's why Duration.ofMinutes(10) is used for some approximate invalidation.
Well, perhaps you could write the pipeline a bit differently. Instead of aspiring to return a new Predicate every time your process an item in your stream by calling getPredicateFromRemoteServer(), you could make the function itself your predicate. Pass the value you are processing from the stream and make it return a Mono<Boolean> with the answer and use that in a filterWhen pipe in your pipeline.
For example, somewhat like this:
private Mono<Boolean> isWithinThreshold(int value) {
return webClient.get()
.uri("/filters/1")
.retrieve()
.bodyToMono(Filter.class)
.map(filter -> filter.threshold <= value)
.cache(v -> Duration.ofMinutes(10), ex -> Duration.ZERO, () -> Duration.ZERO);
}
Then in your main pipeline you can do:
Flux.interval(Duration.ofMillis(200))
.filterWhen(value -> isWithinThreshold(value))
.flatMap(this::writeData)
.subscribe();
}

How to perform operation based on result of multiple RxJava Completable results

Have been banging my head over this for a while, I am lost here in managing a requirement where I have to use Rx in Kotlin.
Let me explain.
There is a set of ids whose equivalent items needs to be deleted from server and eventually in local based on server success.
Basically
Make network call to delete for a single id(Supported network call returns a Completable)
if complete(success) callback is received store the id in a list (memory)
Do step one and two for all id to delete
Once every network call is complete pass the list to delete from local DB
So these functions are available which cannot be modified.
fun deleteId(id: String): Completable { networkCall.deleteId(id) }
fun deleteIds(ids: List<String>): Unit { localDb.deleteId(ids) }
This is what I have tried but obviously incomplete and stuck...
val deleted = CopyOnWriteArrayList<String>()
val error = CopyOnWriteArrayList<String>()
items?.filter { it.isChecked }
?.map { Pair(it.id, dataManager.deleteId(it.id)) }
?.forEach { (Id, deleteOp) ->
deleteOp.subscribeOn(Schedulers.io())
.subscribe(object: CompletableObserver {
override fun onComplete() { deleted.add(Id) }
override fun onSubscribe(d: Disposable) { disposableManager += d }
override fun onError(e: Throwable) { error.add(Id) }
})
}
So now there are multiple problems here, One of them is the requirement where I am unable to find a place to know that all requests are completed so as to initiate a localDb delete.
Is there a way where I can use Flowable.fromIterable() or zip or merge somehow following the chain of commands like above to achieve the above scenario?
If I understood your use case correctly, then this should do:
// ids of items to delete, for illustration lets have some temp set
val ids = setOf<String>("1", "2", "3", "4")
val deleteIdSingles = mutableListOf<Single<String>>()
ids.forEach { id ->
deleteIdSingles.add(
api.deleteId(id)
// when request successfully completes, return its id wrapped in a Single, instead of Completable
.toSingle<String> { id }
// return a flag when this request fails, so that the stream is not closed and other requests would still be executed
.onErrorReturn { "FAILED" }
)
}
Single.merge(deleteIdSingles)
// collect the results of the singles (i.e. the ids of successful deletes), and emit a set of those ids once all the singles has completed
.collect(
{ mutableListOf() },
{ deletedIds: MutableList<String>, id: String -> if (id != "FAILED") deletedIds.add(id) }
)
.observeOn(Schedulers.io())
.subscribe(
{ deletedIds ->
db.deleteIds(deletedIds)
}, { error ->
// todo: onError
})

How do I conditionally chain webclient calls in spring webflux/webclient

I am trying to achieve the following scenario using WebClient. It is trivial using RestTemplate, but I can't do it anymore.
Relevant parts of a Spring controller in pseudo-java code:
Mono<T1> t1 = webClient.get()...retrieve()...;
Mono<T2> t2;
if (t1.getResult().getValue() > 0) {
t2 = webClient.get().buildUsing(t1.getResult().getValue())...retrieve()...);
} else {
t2 = Mono.empty();
}
return(Mono.zip(t1, t2, mergeFunction));
I am not asking how to use Webflux. I can also add error handling myself. My problem is how to pass data to the second call if the first call is successful and where to merge results of both calls one of which may or may not happen. The task is absolutely trivial if I could use RestTemplate.
There is a question with a very similar title, but it was not answered.
I think zipWhen fits well for this purpose. zipWhen waits for the result from first mono and then combines both results into a Tuple2
WebClient.builder().baseUrl("https://jsonplaceholder.typicode.com/todos/1")
.build()
.get()
.retrieve()
.bodyToMono(User.class)
.zipWhen(r -> {
if (r.getId() == 1) {
return WebClient.builder().baseUrl("https://jsonplaceholder.typicode.com/todos/2")
.build()
.get()
.retrieve()
.bodyToMono(User.class);
} else {
return Mono.empty();
}
});
The result is a Mono<Tuple2<T, T2>> holding both values.
as far as I could understand your problem, this is my reactive solution to this:
private static Mono<String> mono() {
Mono<Integer> t1 = Mono.just(0);
return t1.flatMap(outerResult -> outerResult > 0
? Mono.just("VALUE").map(innerResult -> outerResult + "" + innerResult)
: Mono.just(outerResult.toString())
);
}
So what's happening here:
With .flatMap you subscribe to a new Mono and take the result of that.
Inside the lambda of the .flatMap you still have the result of your t1, so you can use .map on t2, if you need to subscribe, or just do whatever you need to do with the result of t1 to bring it to the wanted return value.

Conditional operations in RxJava code

I'm new to RxJava and often find myself resorting to solutions like the ones below for conditional operations. Here I want to chain two calls in sequence, and then return an int depending on what the outcome of the call chain is. Are there any "rxified" way to improve this code? (using blockingSingle here since I'm passing the resulting int to a legacy application to which I cannot push values as of yet)
return restApiAdapter.closePosition(request)
.flatMap(dealReference -> restApiAdapter.getDealConfirmationObservable(dealReference.getValue())
.map(dealConfirmationResponse -> {
if (dealConfirmationResponse.getDealStatus() == DealStatus.ACCEPTED) {
return SUCCESS.getValue();
} else {
return FAIL.getValue();
}
})
)
.onErrorReturn(e -> ZorroReturnValues.BROKER_SELL_FAIL.getValue())
.blockingSingle();
After moving the logic to check for ACCEPTED/REJECTED orders as suggested by #andrei-macarie, the code now looks more like this
return restApiAdapter.closePosition(request)
.flatMap(dealReference -> restApiAdapter.getDealConfirmationObservable(dealReference.getValue())
.map(dealConfirmationResponse -> SUCCESS.getValue()
)
.onErrorReturn(e -> FAIL.getValue())
.blockingSingle();

Categories

Resources