Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring boot Reactive caching

In my application I am using spring webflux and I am using webclient to retrieve details from some 3rd party API. Now, I want to store the first time webClient response in some in memory cache so that for 2nd time I can have those response directly from the cache. I am trying to use Spring boot in memory caching mechanism and also "caffine". But none is working as expected. application.yml:

spring:
 cache:
  cache-names: employee
 caffiene:
  spec: maximumSize=200, expireAfterAccess=5m

EmployeeApplication.java:

@SpringBootApplication
@EnableCaching
public class EmployeeApplication{
   public static void main(String[] args){
    
}
}

EmployeeController.java: It has a rest endpoint employee/all which fetch all employee from the 3rd party Api. EmployeeService.java:

@Service
@Slf4j
public class EmployeeService{
  @Autowired
  private WebClient webClient;
  @Autowired
  private CacheManager cacheManager;
  @Cacheable("employee")
  public Mono<List<Employee>> getAllEmployee(){
    log.info("inside employee service {}");
    return webClient.get()
        .uri("/employees/")
        .retrieve()
        .bodyToMono(Employee.class);
}
}

Although I have configured the cache name , 2nd time when I hit the url it is calling the service method. What cache mechanism need to be used to cache Mono response? Please suggest.

like image 658
stumbler Avatar asked Feb 11 '26 22:02

stumbler


2 Answers

There are several options to cache reactive publishers.

1. Reactive .cache()

Use reactive cache API to cache Mono for the defined duration

employeeService.getAllEmployee()
    .cache(Duration.ofMinutes(60))
    .flatMap(employees -> {
        // process data
    })

in many cases it would mean to create a private field with the defined cache and then use it as a part of the reactive flow.

Flux<> cachedEmployee = employeeService.getAllEmployee()
    .cache(Duration.ofMinutes(60))
    
....

cachedEmployee.flatMap(employees -> {
    // process data
})

2. Use external cache with Caffeine.

Caffeine supports async cache based on CompletableFuture that could be easily adapted to Reactive API.

AsyncLoadingCache<String, List<Employee>> cache = Caffeine.newBuilder()
    .buildAsync((tenant, executor) ->
            employeeService.getAllEmployee(tenant).toFuture()
    );


Mono<List<Employee>> getEmployee(String tenant) {
    return Mono.fromCompletionStage(clientCache.get(tenant));
}

3. Use external cache with Guava and CacheMono [DEPRECATED]

(https://projectreactor.io/docs/extra/snapshot/api/reactor/cache/CacheMono.html) from reactor-extra. This option is more suitable if you need to cache results based on different input (e.g. multi tenant environment)

UPDATE: CacheMono has been deprecated since reactor-extra 3.4.7. Better use #2 Use external cache with Caffeine.

Here is an example for Guava but you could adapt it for CacheManager

Cache<String, List<Employee>> cache = CacheBuilder.newBuilder()
        .expireAfterWrite(cacheTtl)
        .build();


Mono<List<Employee>> getEmployee(String tenant) {
    return CacheMono.lookup(key -> Mono.justOrEmpty(cache.getIfPresent(key)).map(Signal::next), tenant)
            .onCacheMissResume(() -> employeeService.getAllEmployee(tenant))
            .andWriteWith((key, signal) -> Mono.fromRunnable(() ->
                            Optional.ofNullable(signal.get())
                                    .ifPresent(value -> cache.put(key, value))
                    )
            );
}
like image 178
Alex Avatar answered Feb 14 '26 12:02

Alex


We can use Mono.cache() and still avoid caching exceptions/errors and thus avoiding memory leaks. I found this article that shows how to do it.

like image 36
Arun Subramanian Avatar answered Feb 14 '26 11:02

Arun Subramanian



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!