Sync two asynchronous API call with RxJava - java

In what way can we sync two asynchronous calls using RxJava? In the example below, the method contentService.listContents which is a API call must first finish before the processSchema method to take place for each schema.
schemaService.listSchema()
.toObservable()
.flatMapIterable(schemas -> {
schemas.forEach(schema -> {
// async call
contentService.listContents(schema.getName()).subscribe(contents -> {
doSomethingWithThe(contents);
});
});
// contentService.listContents` must complete first before
// processSchema should be called for each schema
return schemas;
}).subscribe(schema -> { processSchema(schema); },
error -> { Console.error(error.getMessage()); });
The problem with the code above the processSchema would not wait for the contentService.listContents since it is async not not synchronized with each other.

You have to use flatMap to process the schemas and since it is a list, you have to unroll it and flatMap again:
schemaService.listSchema()
.toObservable()
.flatMap(schemas ->
Observable.fromIterable(schemas)
.flatMap(schema ->
contentService.listContents(schema.getName())
.doOnNext(contents -> doSomethingWith(contents))
)
// probably you don't care about the inner contents
.ignoreElements()
// andThen will switch to this only when the sequence above completes
.andThen(Observable.just(schemas))
)
.subscribe(
schema -> processSchema(schema),
error -> Console.error(error.getMessage())
);
Note that you haven't defined the return types of the service calls so you may have to use flatMapSingle and doOnSuccess for example.

You are probably looking for flatMap.
From the docs
Continuations
Sometimes, when an item has become available, one would
like to perform some dependent computations on it. This is sometimes
called continuations and, depending on what should happen and what
types are involved, may involve various operators to accomplish.
Dependent
The most typical scenario is to given a value, invoke
another service, await and continue with its result:
service.apiCall()
.flatMap(value -> service.anotherApiCall(value))
.flatMap(next -> service.finalCall(next))
It is often the case also that later sequences would require values
from earlier mappings. This can be achieved by moving the outer
flatMap into the inner parts of the previous flatMap for example:
service.apiCall()
.flatMap(value ->
service.anotherApiCall(value)
.flatMap(next -> service.finalCallBoth(value, next))
)

Related

.block() within reactive flow (Spring Webflux) for object that holds state

I've created a reactive flow at my controller Endpoint addEntry where one object inside should be created only once per request since it holds a state.
#Override
public Mono<FileResultDto> addEntry(final Flux<byte[]> body,
final String fileId) {
return keyVaultRepository.findByFiletId(fileId)
.switchIfEmpty(Mono.defer(() -> {
final KeyVault keyVault = KeyVault.of(fileId);
return keyVaultRepository.save(keyVault);
}))
.map(keyVault -> Mono
.just(encryption.createEncryption(keyVault.getKey(), ENCRYPT_MODE)) // createEncryption obj. that holds a state
.cache())
.map(encryption -> Flux
.from(body)
.map(bytes -> encryption
.share()
.block()
.update(bytes) // works with the state and changes it per byte[] going through this flux
)
)
.flatMap(flux -> persistenceService.addEntry(flux, fileId));
}
before I asked this question I used
encryption.block() which was failing.
I found this one and updated my code accordingly (added .share()).
The test itself is working. But I am wondering if this is the proper way to go to work with an object that should be created and used only once in the reactive flow, provided by
encryptionService.createEncryption(keyVault.getKey(), ENCRYPT_MODE)
Happy to hear your opinion
Mono.just is only a wrapper around a pre-computed value, so there is no need to cache or share it, because it is only just giving back a cached variable on subscription.
But, in your example, there is something I do not understand.
If we simplify / decompose it, it gives the following:
Mono<KeyVault> vault = keyVaultRepository.findByFiletId(fileId)
.switchIfEmpty(Mono.defer(() -> keyVaultRepository.save(KeyVault.of(fileId));
));
Mono<Mono<Encryption>> fileEncryption = vault
.map(it -> Mono.just(createEncryption(it.getKey)).cache()); // <1>
Mono<Flux<Encryption>> encryptedContent = fileEncryption.map(encryption -> Flux
.from(body)
.map(bytes -> encryption
.share()
.block()
.update(bytes))); // <2>
Mono<FileResultDto> file = encryptedContent.map(flux -> persistenceService.addEntry(flux, fileId));
Why are you trying to wrap your encryption object ? The result is already part of a reactive pipeline. Doing Mono.just() is redundant because you are already in a map operation, and doing cache() over just() is also redundant, because a "Mono.just" is essentially a permanent cache.
What does your "update(bytes)" method do ? Does it mutate the same object every time ? because if it does, you might have a problem here. Reactive streams cannot ensure thread-safety and proper ordering of actions on internal mutated states, that is out of its reach. You might bypass the problem by using scan operator, though.
Without additional details, I would start refactoring the code like this:
Mono<KeyVault> vault = keyVaultRepository.findByFileId(fileId)
.switchIfEmpty(Mono.defer(() -> keyVaultRepository.save(KeyVault.of(fileId));
Mono<Encryption> = vault.map(it -> createEncryption(it.getKey()));
Flux<Encryption> encryptedContent = fileEncryption
.flatMapMany(encryption -> body.scan(encryption, (it, block) -> it.update(block)));
Mono<FileResultDto> result = persistenceService.addEntry(encryptedContent, fileId);

How can we compose 3 or more completablefutures while maintaining all the results

I have 3 services which I want to chain
CompletableFuture<String> serviceA
CompletableFuture<String> serviceB(String resultFromA)
CompletableFuture<String> serviceC(String resultFromA, String resultFromB)
If I use thenCompose, I can't seem to maintain the first result
i.e.
serviceA.thenCompose(a -> serviceB(a))
.thenCompose(b -> serviceC(a, b)); // a is lost
If I use CompletableFuture.allOf(), I don't see that it allows chaining - running in sequence and passing results.
I am going to modify serviceB, so that it returns a Pair, or some composite object, but is there a better way ?
serviceA.thenCompose(a -> serviceB(a).thenCompose(b -> serviceC(a, b)));

Calling two functions in parallel in Java 8

I have a use case in my Spring boot application as follows:
I would like to fetch the id field value from the response with the following function:
String id = getIdFromResponse(response);
If I don't get any id in the response, then I check if the id field is present in the request argument with the following function:
String id = getIdFromRequest(request);
As of now, I am invoking them sequentially. But I would like to make these two functions run parallelly, I would like to stop as soon as I get an id from either of them.
I am wondering if there is any way to implement this using streams in Java 8.
You can use something like this:
String id = Stream.<Supplier<String>>of(
() -> getIdFromResponse(response),
() -> getIdFromRequest(request)
)
.parallel()
.map(Supplier::get)
.filter(Objects::nonNull)
.findFirst()
.orElseThrow():
The suppliers are needed, because when you don't use them, then both requests are still executed sequentially.
I also assumed that your methods return null when nothing is found, so I had to filter these values out with .filter(Objects::nonNull).
Depending on your use case, you can replace .orElseThrow() with something different, like .orElse(null)
There is no need to use Stream API as long as there exists a method exactly for this.
ExecutorService::invokeAny(Collection<? extends Callable<T>>)
Executes the given tasks, returning the result of one that has completed successfully (i.e., without throwing an exception), if any do. Upon normal or exceptional return, tasks that have not completed are cancelled.
List<Callable<String>> collection = Arrays.asList(
() -> getIdFromResponse(response),
() -> getIdFromRequest(request)
);
// you want the same number of threads as the size of the collection
ExecutorService executorService = Executors.newFixedThreadPool(collection.size());
String id = executorService.invokeAny(collection);
Three notes:
There is also an overloaded method with timeout throwing TimeoutException if no result is available in time: invokeAny(Collection<? extends Callable<T>>, long, TimeUnit)
You need to handle ExecutionException and InterruptedException from the invokeAny method.
Don't forget to close the service once you are done
If you want to be in full control over when to enable the alternative evaluation, you may use CompletableFuture:
CompletableFuture<String> job
= CompletableFuture.supplyAsync(() -> getIdFromResponse(response));
String id;
try {
id = job.get(300, TimeUnit.MILLISECONDS);
}
catch(TimeoutException ex) {
// did not respond within the specified time, set up alternative
id = job.applyToEither(
CompletableFuture.supplyAsync(() -> getIdFromRequest(request)), s -> s).join();
}
catch(InterruptedException|ExecutionException ex) {
// handle error
}
The second job is only submitted when the first did not complete within the specified time. Then, whichever job responds first will provide the result value.

Exit Observable.zip in rxjava based on condition

In RXJava, I have a 2 observables which are responses from 2 downstream calls.One downstream call is a long poll call, other is a short one and returns right away.
I am using the Observable.zip to combine the responses of both the responses.The below code works fine.
Observable
.zip(observable1, observable2)
.flatMap(update -> foo(update));
Now what I want to implement is that if the output of the short downstream call (observable1) does not content a specific value, then skip the zip i.e dont wait for the output of the longer downstream call (observable2).
I tried to implement it in the below way, but if the condition is true it doesn't zip with the observable2, but it does not even emit observable1 response.
Observable finalresponse = observable1
.takeWhile(obsResponse1 -> checkIfValueExist(obsResponse1))
.zipWith(observable2, (observable1, observable2) -> execute(observable1, observable2))
.flatMap(update -> main.execute(update));
In zip there is a rule it will return only if both of streams will emit an item so what you need to do is to filter or return Observable.empty() in observable if your object is not what you expect or you can use filter
Observable
.zip(Observable.just(1).filter(integer -> integer==1), Observable.just(2).filter(integer -> integer==3),(integer, integer2) -> integer)
.flatMap(update -> foo(update));

RxJava get result of previous observable in chained network request

I have 2 service endpoints in my application a and b (Both are "Singles"). The request to service b depends on the response of a. After the the response of b I need access to both responses in my subscriber. I plan to do the call like this:
services.a()
.flatMap(a -> services.b(a))
.subscribe(b ->
// Access a and b here
)
But in this way, I only can access the result of b in my subscriber. How can I pass also the response of a to it?
My first attempt was to use something like this:
// Note: Code will not compile... Just to show the concept
services.a()
.flatMap(
a -> Observable.combineLatest(
Observable.just(a)
services.b(a).toObservable()
Bifunction((a, b) -> {return Pair<ResponseA, ResponseB>(a, b)}))
)
.toSingle()
.subscribe(pair -> {
ResponseA a = pair.first();
ResponseB b = pair.second();
})
But as the use case gets a bit more complex, the code will evolve to an ugly monster.
You can use a variant of flatMap() with resultSelector, resultSelector method will gather both input (a) and output (result of Observable b) of the flatMap and you can combine them together to whatever you need. See here.

Categories

Resources