2

I'm new to Webflux and I'm trying to implement this scenario:

  • client ask for data
  • if data is already present in redis cache => return cached data
  • otherwise query remote service for data

I've written this code:

ReactiveRedisOperations<String, Foo> redisOps;

private Mono<Foo> getFoo(String k) {

    return this.redisOperations.opsForValue().get(k)
                .map(f -> this.logCache(k, f))
                .switchIfEmpty(this.queryRemoteService(k));
}

private void logCache(String k, Foo f) {
        this.logger.info("Foo # {} # {} present in cache. {}",
                k,
                null != f ? "" : "NOT",
                null != f ? "" : "Querying remote");
}

private Mono<Foo> queryRemoteService(String k) {

    this.logger.info("Querying remote service");

    // query code
}

It prints:

"Querying remote service"
"Foo # test_key # present in cache"

How can I ensure that switchIfEmpty is called only if cached data is not present?

Edit

Accordingly to Michael Berry's answer I refactored my code like follows:

private Mono<Foo> getFoo(String k) {

    this.logger.info("Trying to get cached {} data", k);

    this.logger.info(this.redisOps.hasKey(k).block() ? "PRESENT" : "NOT present");

    return this.redisOperations.opsForValue().get(k)
                .switchIfEmpty(this.queryRemoteService(k));
}

private Mono<Foo> queryRemoteService(String k) {

    this.logger.info("Querying remote service");

    // query code
}

Now my output is this:

Trying to get cached test_key data
PRESENT
Querying provider

So it seems that is executed only one time, but still can't avoid switchIfEmpty is executed. I'm sure that redis contains data for that key

batboy
  • 93
  • 1
  • 9
  • Possible duplicate of [What's the point of .switchIfEmpty() getting evaluated eagerly?](https://stackoverflow.com/questions/57870706/whats-the-point-of-switchifempty-getting-evaluated-eagerly) – Martin Tarjányi Nov 21 '19 at 20:34

1 Answers1

0

This line:

.map(f -> this.logCache(k, f))

...is rather odd, as you're not mapping anything to anything, you're instead performing a side effect (logging the value.) doOnNext() (rather than map()) would be a much more sensible choice here.

However, I digress, that's not the primary issue here.

How can I ensure that switchIfEmpty is called only if cached data is not present?

It's that way already, but your logging isn't doing what you think. The key concept likely causing you an issue here is that null can never be propagated through a reactive stream. If the stream is empty as it is in this example, then nothing is propagated at all.

Therefore, in your code, neither map() (nor doOnNext() if that were used) will be called, so your "present in cache" line won't be written to the logs (since there's no value to map, and no value to call a side effect against.) Checking whether the value is null in logCache() is therefore pointless - it will never be null.

As far as I see it, your log output here therefore must be the result of two invocations of getFoo():

  • The first was not present in the cache, so map() wasn't called, switchIfEmpty() switched, and "Querying remote service" was printed;
  • The second was present in the cache, so your "present in cache" line was printed, switchIfEmpty() wasn't called, and therefore "Querying remote service" wasn't printed.

To make your logging make sense, you should remove the conditional logic from logCache(), and add a "not present in cache" line to the queryRemoteService() method.

Michael Berry
  • 70,193
  • 21
  • 157
  • 216
  • thank you for your precious advice, still no luck. I've edited my question, can you look at that please? – batboy Nov 21 '19 at 13:11
  • Can you call [hasKey()](https://docs.spring.io/spring-data/redis/docs/current/api/org/springframework/data/redis/core/ReactiveRedisOperations.html#hasKey-K-) with the same value and check that definitely returns true? I don't see how there's any way `switchIfEmpty()` can be called there if a value is returned. – Michael Berry Nov 21 '19 at 13:50
  • yeah, it returns true. one thing: i'm developing reactive rest controllers, I never subscribe to mono, I return directly to the client, could be this the problem? – batboy Nov 21 '19 at 13:57
  • @batboy Hmm, odd. Can you also block on `.opsForValue().get(k)` and confirm that definitely returns a value? – Michael Berry Nov 21 '19 at 18:17