2

I know how to memoize a single object. However, I'd like to memoize only if some condition is met. I'm calling a service that sometimes returns a response that is not successful. I'd like to memoize only if the service's response if successful.

MyResponse myResponse = myService.call()
boolean success = myResponse.isSuccessful();

And my cache is created like so:

private Supplier<MyResponse> cache;

private void createCache() {
    this.cache = Suppliers
        .memoizeWithExpiration(myService::call, timeout,
            TimeUnit.MINUTES);
}

Question: Is it possible to somehow cache the response only if the response is successful using the Supplier passed to the memoizeWithExpiration method?


The only workaround I found to do this is to, when retrieving the value, call cache.get() first, check if the object stored in cache is successful, and if it's not, call createCache() again to clear it and then get the value again. This way if the subsequent service call returns a valid object, it will get stored, and if not, every subsequent call will clear the cache and call the service again.

 public MyResponse getResponse() {
    MyResponse myResponse = cache.get();
    if (myResponse.isSuccess()) {
      return myResponse;
    } else {
      createCache();
      return cache.get();
    }
  }

However, in this solution, if the cache is empty and the service returns unsuccessful response, it will get called again immediately.

RK1
  • 429
  • 2
  • 7
  • 28
  • You're looking at the wrong place IMO. You'll need to change the logic in `getResponse()` to not use `cache.get()` in all cases because there is no way that this will return the result if you suceed to not cache it conditionally. I.e. in your `else` branch you call `myService::call` and then conditionally create the cache. Not cached? -> call service, success? -> cache, else return error, don't cache. – zapl Oct 09 '19 at 11:32
  • You are right, but I don't know if it's possible to call the service and then choose to cache the response without calling the service again - that's what I'm asking here. The whole point of the `memoizeWithExpiration` method is that the service call is hidden under the cache's `get` method. So the only way I can put the value in the cache is to invoke the `get` method which calls the service underneath. If you know how to implement this using Guava, please write an answer and I'll gladly accept it. – RK1 Oct 09 '19 at 13:20
  • At this point you're deviating from the Guava assumptions. The memoization code is simple, so you might copy it and extend with your additional use-cases. – Ben Manes Oct 09 '19 at 16:58

2 Answers2

2

You can create a method callUntilSuccess in Service class or in any other suitable place (here I'm assuming it is in your Service). You could also define a maximum number of tries in this method and after that it will return null, so you could avoid calling your service indefinitely (this suggestion isn't implemented in the code supplied bellow but it is very easy to do so). As the Guava method expects a Supplier, you can even create a lambda with this logic and pass it directly to the memoizeWithExpiration method.

public MyResponse callUntilSuccess() {

    MyResponse response = myService.call();
    while (!response.isSuccessful()) {
        response = myService.call();
    }
    return response;
}   

Then do the memoization in this way:

private void createCache() {
    this.cache = Suppliers
         .memoizeWithExpiration(myService::callUntilSuccess, timeout,
                TimeUnit.MINUTES);
}   
Diego Marin Santos
  • 1,923
  • 2
  • 15
  • 29
1

Could this be what you are looking for?


    private void createCache() {
        this.cache = Suppliers.memoizeWithExpiration(
           Suppliers.compose(
               response -> (response.isSuccess() ? response : null),
               myService::call
           ),
           timeout,
           TimeUnit.MINUTES
        );
    }

Here, it will cache the response, or null, depending on whether it was successful.

More info on compose here https://github.com/google/guava/blob/master/guava/src/com/google/common/base/Suppliers.java#L45

EDIT: If you need to cache the value on success, and leave the cache empty on failure, while returning the failed request, then you are almost there yourself, just change return logic a bit in getResponse, like this:


    public MyResponse getResponse() {
        final MyResponse myResponse = cache.get();
        if (!myResponse.isSuccess()) {
            this.createCache(); // clear cache
        }
        return myResponse; // don't call .get() again!
    }

  • 1
    Yes, that's it, very simple indeed!. The other person responded first so I'm accepting his answer but I like the idea with `compose`, too. Thank you. – RK1 Oct 11 '19 at 09:17