Call a WebService and a REST API using JDK8 Streams and CompletableFuture - java

I have a SOAP call that I need to make and then process the results from the SOAP call in a REST call. Each set of calls is based on a batch of records. I am getting completely lost in trying to get this to run using JDK8 streams as asynchronous as possible. How can I accomplish this?
SOAP Call:
CompletableFuture<Stream<Product>> getProducts(final Set<String> criteria)
{
return supplyAsync(() -> {
...
return service.findProducts(request);
}, EXECUTOR_THREAD_POOL);
}
REST Call:
final CompletableFuture<Stream<Result>> validateProducts(final Stream<Product> products)
{
return supplyAsync(() -> service
.submitProducts(products, false)
.stream(), EXECUTOR_THREAD_POOL);
}
I am trying to invoke the SOAP call, pass the result into the REST call, and collect the results using a JDK8 stream. Each SOAP->REST call is a "set" of records (or batch) similar to paging. (this is totally not working right now but just an example).
#Test
public void should_execute_validations()
{
final Set<String> samples = generateSamples();
//Prepare paging...
final int total = samples.size();
final int pages = getPages(total);
log.debug("Items: {} / Pages: {}", total, pages);
final Stopwatch stopwatch = createStarted();
final Set<Result> results = range(0, pages)
.mapToObj(index -> {
final Set<String> subset = subset(index, samples);
return getProducts(subset)
.thenApply(this::validateProducts);
})
.flatMap(CompletableFuture::join)
.collect(toSet());
log.debug("Executed {} calls in {}", pages, stopwatch.stop());
assertThat(results, notNullValue());
}

I think there are two usage that are incorrect in your example: thenApply and join.
To chain the 1st call (SOAP) and the 2nd call (REST), you need to use thenCompose instead of thenApply. This is because method "validateProducts" returns completable futures, using "thenApply" will create CompletableFuture<CompletableFuture<Stream<Result>>> in your stream mapping. But what you need is probably CompletableFuture<Stream<Result>>. Using thenCompose can resolve this problem, because it is analogous to "Optional.flatMap" or "Stream.flatMap":
.mapToObj(index -> {
final Set<String> subset = subset(index, samples);
return getProducts(subset)
.thenCompose(this::validateProducts);
})
The 2nd incorrect usage is join. Using join blocks the current thread waiting for the result of that CompletableFuture. In your cases, there are N completable futures, where N is the number of pages. Instead of waiting them one by one, the better solution is to wait all the them use CompletableFuture.allOf(...). This method returns a new CompletableFuture that is completed when all of the given CompletableFutures complete. So I suggest that you modify your stream usage and return a list of futures. Then, wait the completion. And finally, retrieve the results:
List<CompletableFuture<Stream<Result>>> futures = range(0, pages)
.mapToObj(index -> {
final Set<String> subset = subset(index, samples);
return getProducts(subset).thenCompose(this::validateProducts);
})
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
for (CompletableFuture<Stream<Result>> cf : futures) {
// TODO Handle the results and exceptions here
}
You can see the complete program on GitHub.

Related

Get result of completablefuture

I am using completablefuture to be returned from an async thread in springboot application. My implementation is below. From my understanding a new thread should be started for each item in list and should process parallely. I understand .get will block the execution but as it is parallely running still i dont see any improvement in performance. Any suggestions on below please to improve the performance?
ServiceA.java
#Autowired
ServiceB serviceb;
public List<String> getNames(List<Item> items) {
List<CompletableFuture<String>> list = new ArrayList<>();
List<String> returnList = new ArrayList<>();
for( Item item: items) {
CompletableFuture<String> getItemName = serviceb.doProcess(item);
list.add(getItemName):
}
for( CompletableFuture name : list) {
returnList.add(name.get());
}
return returnList;
}
ServiceB.java
Class ServiceB {
#Async
Public CompletableFuture<String> doProcess(Item item)
{
//do process
}
You could call allOf to wait for all results. This will wait for all CompletableFuture to complete.
List<String> returnList = new ArrayList<>(items.size());
CompletableFuture<String>[] tasks = items.stream()
.map(value-> serviceb.doProcess(value).thenApply(returnList::add))
.toArray(CompletableFuture[]::new);
// wait for all tasks to finish
CompletableFuture.allOf(tasks).get(50, TimeUnit.SECONDS);
// return the results
return returnList;
Second solution would be to use an reactive approach like publisher/subscriber pattern (Spring WebFlux or JavaRx). This way your application would have little/no waiting operation. But this would affect your application architecture.
One Advice:
In order to create an CompletableFuture use the constructor with ExecutorService in order to keep in check number of threads and have control over running threads or when application shuts down.
You can use thenAccept to add the items to the list.
List<String> list = new CopyOnWriteArrayList<>();
CompletableFuture.allOf(
Stream.of(items).map(
i -> CompletableFuture.supplyAsync(i)
.thenAccept(list::add)
).toArray(CompletableFuture[]::new)
).get(10, SECONDS);
return list;
The get() will definitely not going to work out as it will block the main thread of execution to fetch the result from the async threads. A better option would be to use callback methods(thenApply() etc) accordingly so as to let the main thread continue its work.

Limit for `onErrorContinue(...)` in Flux?

I have a (possibly infinite) Flux source that is supposed to first store each message (e.g. into a database) and then asynchronously forward the messages (e.g. using Spring WebClient).
The forward(s) in case of failure are supposed to log an error, without completing the source Flux.
I however realized that forward(s) wihtin the flow (flatMap(...)) block execution of the source Flux after exactly 256 messages that cause exceptions (e.g. reactor.retry.RetryExhaustedException).
Representative example that fails in the assert since only 256 messages are processed:
#Test
#SneakyThrows
public void sourceBlockAfter256Exceptions() {
int numberOfRequests = 500;
Set<Integer> sink = new HashSet<>();
Flux
.fromStream(IntStream.range(0, numberOfRequests).boxed())
.map(sink::add)
.flatMap(i -> Mono
// normally the forwards are contained here e.g. by means of Mono.when(...).thenReturn(...).retryWhen(...):
.error(new Exception("any"))
)
.onErrorContinue((throwable, o) -> log.error("Error", throwable))
.subscribe();
Thread.sleep(3000);
Assertions.assertEquals(numberOfRequests, sink.size());
}
Doing the forward within the subscribe(...) doesn't block the source Flux but that's certainly no solution, since I don't possibly want to lose messages.
Questions:
What has happened here? (probably related to some state stored in just one bit)
How can I do this correctly?
EDIT:
According to the discussion below I've constructed an example that uses FluxMessageChannel (which up to my understanding is made for infinite streams and definitly not expected to block after 256 Errors) and has exactly the same behaviour:
#Test
#SneakyThrows
public void maxConnectionWithChannelTest() {
int numberOfRequests = 500;
Set<Integer> sink = new HashSet<>();
FluxMessageChannel fluxMessageChannel = MessageChannels.flux().get();
fluxMessageChannel.subscribeTo(
Flux
.fromStream(IntStream
.range(0, numberOfRequests).boxed()
.map(i -> MessageBuilder.withPayload(i).build())
)
.map(Message::getPayload)
.map(sink::add)
.flatMap(i -> Mono.error(new Exception("whatever")))
);
Flux
.from(fluxMessageChannel)
.subscribe();
Thread.sleep(3000);
Assert.assertEquals(numberOfRequests, sink.size());
}
EDIT:
I just raised an issue in the reactor core project: https://github.com/reactor/reactor-core/issues/2011

Combining results of two CompletableFuture with different return types

I have 2 different CompletableFuture that I like to run in parallel (each has a different return type) and then to combine their results:
CompletableFuture<Person> person = personDB.asyncCall(..);
CompletableFuture<Dog> dog = dogDB.asyncCall(...);
Now I like to combine the dog.name & the person name:
return dog.getName() + person.getName()
I was trying to use
CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(perons, dog);
combinedFuture.thenApply(aVoid -> {
// now what?
});
But got suck here.
Use get() method to wait for them to finish.
CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(perons, dog);
combinedFuture.get(); // wait for all of them to complete
return dog.getName() + person.getName();
See https://www.baeldung.com/java-completablefuture
Notice that the return type of the CompletableFuture.allOf() is a
CompletableFuture. The limitation of this method is that it does
not return the combined results of all Futures. Instead you have to
manually get results from Futures. Fortunately,
CompletableFuture.join() method and Java 8 Streams API makes it
simple:

Do I need to control the execution of these two threads with the main threads?

void process(String question){
Callable<ResponseList> callable1 = () -> this.stsCompute question);
Future<ResponseList>task1 = executorService.submit(callable1);
Callable<ResponseList> callable2 = () -> this.dssmCompute(question);
Future<ResponseList>task2 = executorService.submit(callable2);
try{
ResponseList stsResponse = task1.get();
ResponseList dssmResponse = task2.get();
}catch (Exception e){
e.printStackTrace();
}
// Do I need to wait until the first 2 threads complete?
processResponse(stsResponse, dssmResponse);
}
In this "process" method, I have two additional threads 'callable1' & 'callable2' to concurrently execute. I want to make sure only when these two tasks complete, the method in the main thread 'processResponse()' can start to be executed.
In such a case, do I need to add any additional control to ensure the order of the execution, is it already good enough? If not, how to make that control happen?
You should use ExecutorService.invokeAll which will return List of Futures when complete. Besides I would use a shorter syntax, something like
List<Future> futures = executorService.invokeAll(Arrays.asList(()->dssmCompute(), ()->dssmCompute()));
With Java8+ i would suggest use Completable Futures. It supports exactly the use case you are trying to achieve.
Completable Futures: Java 8
Sample Algorithm looks like:
var cf = CompletableFuture.supplyAsync(() -> processQuestion()).runAsync(() -> processResponse)
Note: var is typeInference supports in java 10+
Also, there are plenty of Examples on Completable Futures

Chain CompletableFuture and stop on first success

I'm consuming an API that returns CompletableFutures for querying devices (similar to digitalpetri modbus).
I need to call this API with a couple of options to query a device and figure out what it is - this is basically trial and error until it succeeds. These are embedded device protocols that I cannot change, but you can think of the process as working similar to the following:
Are you an apple?
If not, then are you a pineapple?
If not, then are you a pen?
...
While the API uses futures, in reality, the communications are serial (going over the same physical piece of wire), so they will never be executed synchronously. Once I know what it is, I want to be able to stop trying and let the caller know what it is.
I already know that I can get the result of only one of the futures with any (see below), but that may result in additional attempts that should be avoided.
Is there a pattern for chaining futures where you stop once one of them succeeds?
Similar, but is wasteful of very limited resources.
List<CompletableFuture<String>> futures = Arrays.asList(
CompletableFuture.supplyAsync(() -> "attempt 1"),
CompletableFuture.supplyAsync(() -> "attempt 2"),
CompletableFuture.supplyAsync(() -> "attempt 3"));
CompletableFuture<String>[] futuresArray = (CompletableFuture<String>[]) futures.toArray();
CompletableFuture<Object> c = CompletableFuture.anyOf(futuresArray);
Suppose that you have a method that is "pseudo-asynchronous" as you describe, i.e. it has an asynchronous API but requires some locking to perform:
private final static Object lock = new Object();
private static CompletableFuture<Boolean> pseudoAsyncCall(int input) {
return CompletableFuture.supplyAsync(() -> {
synchronized (lock) {
System.out.println("Executing for " + input);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return input > 3;
}
});
}
And a List<Integer> of inputs that you want to check against this method, you can check each of them in sequence with recursive composition:
public static CompletableFuture<Integer> findMatch(List<Integer> inputs) {
return findMatch(inputs, 0);
}
private static CompletableFuture<Integer> findMatch(List<Integer> inputs, int startIndex) {
if (startIndex >= inputs.size()) {
// no match found -- an exception could be thrown here if preferred
return CompletableFuture.completedFuture(null);
}
return pseudoAsyncCall(inputs.get(startIndex))
.thenCompose(result -> {
if (result) {
return CompletableFuture.completedFuture(inputs.get(startIndex));
} else {
return findMatch(inputs, startIndex + 1);
}
});
}
This would be used like this:
public static void main(String[] args) {
List<Integer> inputs = Arrays.asList(0, 1, 2, 3, 4, 5);
CompletableFuture<Integer> matching = findMatch(inputs);
System.out.println("Found match: " + matching.join());
}
Output:
Executing for 0
Executing for 1
Executing for 2
Executing for 3
Executing for 4
Found match: 4
As you can see, it is not called for input 5, while your API (findMatch()) remains asynchronous.
I think the best you can do is, after your retrieval of the result,
futures.forEach(f -> f.cancel(true));
This will not affect the one having produced the result, and tries its best to stop the others. Since IIUC you get them from an outside source, there's no guarantee it will actually interrupt their work.
However, since
this class has no direct control over the computation that causes it to be completed, cancellation is treated as just another form of exceptional completion
(from CompletableFuture doc), I doubt it will do what you actually want.

Categories

Resources