10

I have read many java8 completable future tutorials, most of them are basically same. All talking about some basic method "thenAccept"/"thenApply"/thenCombine" to build a pipeline flow.

But when come to a real work problem, I feel hard to organize different completable futures from different Service. For Example:

    interface Cache{
       CompletableFuture<Bean> getAsync(long id);
       CompletableFuture<Boolean> saveAsync(Bean bean);
    }


   interface DB{
       Completable<Bean> getAsync(long id)
    }

the service logic is quite simple, get data from Cache, if exist return to our client, if not, get it from DB, if exist save it back to Cache, and return it to our client, if neither exist in DB, return "error" to client.

using synchronize API, it will be quite straight ahead. But when using asyncnorized API, there are "many pipelines", manny conditional break. I can not figure out how to implement this using CompletableFuture API.

Didier L
  • 18,905
  • 10
  • 61
  • 103
junfei wang
  • 111
  • 1
  • 5

1 Answers1

8

If you don't care about the result of saving into the cache and if you want to throw an exception on bean not found, then it can be e.g.

CompletableFuture<Bean> findBeanAsync(long id, Cache cache, DB db) {
    return cache.getAsync(id).thenCompose(bean -> {
        if (bean != null) {
            return CompletableFuture.completedFuture(bean);
        }
        return db.getAsync(id).thenApply(dbBean -> {
            if (dbBean == null) {
                throw new RuntimeException("bean not found with id " + id);
            }
            cache.saveAsync(dbBean);
            return dbBean;
        });
    });
}
starikoff
  • 1,601
  • 19
  • 23
  • really usefull ! Thanks – junfei wang Mar 28 '17 at 08:03
  • This seems to be very simple use case. I have recently started working on async apis. I am still trying to figure out how to avoid multiple nestings and repeated exception handling. For example, on receiving a request, Service A does request validation which can either fail or succeed > if success then Service B does some db operation > if failure in B then return some particular failure response, if success proceed > Service C calls an external Api > if failure then return a particular failure response, if success proceed > Service D saves some data in db > if failure then return a particula – Mohammed Salman Shaikh Aug 02 '21 at 15:02
  • just a quick comment - if you _do_ care about the saving step, you can change the `thenApply` to a `thenCombine`, and then merge the last two lines of the lambda to `return cache.saveAsync(dbBean).thenApply(saved -> dbBean);`, and there in the inner lambda you can check if the save was successful, and perhaps log a warning if it wasn't. In general it's better to "consume" all futures, otherwise they might get aborted / garbage collected before they complete - however this case is probably safe to leave as is, since if the cache save doesn't work, then... who cares, it'll try again next time – gzak Jul 12 '23 at 14:02