-1

I am using completable future to invoke multiple service calls.

CompletableFuture<Response1> future1 = CompletableFuture.supplyAsync(() -> webServiceCall1());
CompletableFuture<Response2> future2 = CompletableFuture.supplyAsync(() -> webServiceCall2());
CompletableFuture<Response3> future3 = CompletableFuture.supplyAsync(() -> webServiceCall3());

And then I am using :

Response1 response1 = future1.get();
Response2 response2 = future2.get();
Response3 response3 = future3.get();
// do further processing on the response

Well why am i using this kind of code ??? by doing that I am able to improve performance. If service1 succeeds with 200 & takes 3 sec of time , service2 succeeds with 200 & takes 3 sec of time , service3 succeeds with 200 & takes 3 sec of time , so calling them one after other will take overall 9 secs of time. But if I use what I have written above, all of them will be executing parallel & will finish around in 3 secs of time.

now issue is that if webservice1 fails, then hit to webservice2 & webservice3 will not be needed. but in my scenario, since the code is written in such a way that it calls webservice2 & webservice3, even if webservice1 fails.

How we can avoid this & still keep webservice calls parallel & still fail safe at the same time ??

Let me rephrase myself -- How we can keep the overall performance of this code high (considering benchmark of somewhere around 3 secs) & still fail safe at the same time ??

ASharma7
  • 726
  • 3
  • 8
  • 27
  • What do you mean by "fails"? Exception? Or successful invocation with a response considered failure? – ernest_k Oct 16 '21 at 17:25
  • @ernest_k to be more specific , by fail I mean 500 internal server error – ASharma7 Oct 16 '21 at 17:33
  • As said in my other comment, you can't : if you don't want to call service2 and service3 if service1 fails, that means you have the result of service1 first. Which means a clear relation : you call service1 then service2 then service3. – NoDataFound Oct 16 '21 at 17:42
  • @NoDataFound agree, but lets see if we can have some workaround this issue – ASharma7 Oct 16 '21 at 17:45

3 Answers3

1

You could use the thenApplyAsync function:

ResponseHolder holder = CompletableFuture.supplyAsync(ResponseHolder::new)
  .thenApplyAsync(h -> h.setResponse1(webServiceCall1()))
  .thenApplyAsync(h -> h.setResponse2(webServiceCall2()))
  .thenApplyAsync(h -> h.setResponse3(webServiceCall3()))
  .get() // may fail
;

And the ResponseHolder class:

public class ResponseHolder {
  private Response1 response1;
  private Response2 response2;
  private Response3 response3;
  // for each responseX:
  public ResponseHolder setResponse1(Response1 response1) {
    this.response1 = response1;
    return this;
  }
}
NoDataFound
  • 11,381
  • 33
  • 59
  • by doing that I would be compromising with performance. If service1 succeeds with 200 & takes 3 sec of time , service2 succeeds with 200 & takes 3 sec of time , service3 succeeds with 200 & takes 3 sec of time , so by this code it will take overall 9 secs of time. But if I use what I have done , all of them will be executing parallel & will finish around in 3 secs of time – ASharma7 Oct 16 '21 at 17:38
  • You understand that - with what your comment entails - if service1 fails, you will call service2 and service3 which you asked us to avoid? If you don't want to call service2/3 if service1 fails, then you are forced to get the answer from service1 first - be it a failure or a success. – NoDataFound Oct 16 '21 at 17:41
  • Agree. & this is why i started discussion on this , so that we can discuss & get some workaround this problem, get best of both world (parallel & still fail safe) – ASharma7 Oct 16 '21 at 17:43
  • let me rephrase my Question -- How we can keep the overall performance of this code high (considering benchmark of somewhere around 3 secs) & still fail safe at the same time ?? – ASharma7 Oct 16 '21 at 18:11
1

How we can avoid this & still keep webservice calls parallel & still fail safe at the same time ??

You can't.

You've already started the processing of web service calls 2 and 3 by the time that you hear of the failure of service call 1 (for example). It's too late to not call them.

If you must call service 2 only after service call 1 succeeds, then you have to wait until you know that service call 1 has succeeded.

You're otherwise asking for a fortune-teller: call services 2 and 3 now, if service 1 will succeed at some future time.

The best you can do is to provide a way to cancel the service calls while they're in progress (i.e., tell them to quit at the next convenient point) but that might be more trouble than it's worth to get right.

user16632363
  • 1,050
  • 3
  • 6
  • let me rephrase my Question -- How we can keep the overall performance of this code high (considering benchmark of somewhere around 3 secs) & still fail safe at the same time ?? – ASharma7 Oct 16 '21 at 18:09
  • 1
    What do you mean by 'safe'? What is unsafe about letting all 3 calls complete and then responding, with success iff all 3 calls succeeded? – user16632363 Oct 16 '21 at 18:43
0
  • webServiceCall1() will fire off no matter what
  • webServiceCall2() will check webServiceCall1()
    • Check to see if a response status has been returned yet for 1
      • If the response status is 200, go ahead and fire
      • If the response status is not 200, do not make a call
      • If the response status has not been returned yet, go ahead and make the call anyways - essentially, we are assuming it will succeed
  • webServiceCall3 will check both webServiceCall1() and webServiceCall2()

I want to highlight though - if webServiceCall1() has not completed by the time we attempt to do webServiceCall2(), then we are assuming that webServiceCall1() has returned and is successful with a 200 response code. If it ends up that webServiceCall1() is not successful even after assuming it was, then we will have made a pointless call to webServiceCall2() (and potentially webServiceCall3()), so be aware of that.

My solution ONLY covers the edge case of webServiceCall1() firing off and failing so quickly that we get our response back before we have a chance to check it in the 2nd CompletableFuture. Same for webServiceCall2() and the 3rd CompletableFuture. In both of these situations, we will have optimized out 1 or 2 calls from occurring. But don't assume that that will happen EVERY time webServiceCall1() or webServiceCall2() fails.

CompletableFuture<Response1> future1 = CompletableFuture.supplyAsync(() -> webServiceCall1());

CompletableFuture<Response2> future2 = CompletableFuture.supplyAsync(() -> (performServiceCheck(future1) ? webServiceCall2() : null));

CompletableFuture<Response3> future3 = CompletableFuture.supplyAsync(() -> (performServiceCheck(future1, future2) ? webServiceCall3() : null));
private boolean performServiceCheck(CompletableFuture<?> ...futures) {
    
    boolean result = true;
    
    for (CompletableFuture<?> future : futures) {
        if (future.isDone() && is200(future.get())) {
            result = result && true;
        } else if (future.isDone() && !is200(future.get())) {
            result = result && false;
        } else if (!future.isDone()) {
            result = result && true;
        }
    }
    
    return result;
    
}
davidalayachew
  • 1,279
  • 1
  • 11
  • 22
  • 1
    All these three Webservice calls are GET calls rather than POST calls, so I don't think transaction will help me in this case. Essentially what i am doing is hitting 3 GET calls & using the response data to do some further processing. – ASharma7 Oct 18 '21 at 05:56
  • I have reworked the solution @ASharma7 . Does this meet your need? – davidalayachew Oct 19 '21 at 09:28
  • @ASharma7 it seems like it did, so I have added a warning to better explain how to use this, as well as the limitations of the solution. Please consider that if you end up using this solution. – davidalayachew Oct 19 '21 at 14:56