Spring boot Reactive caching - spring

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.

There are several options to cache reactive publishers.
Use reactive cache API to cache Mono for the defined duration
employeeService.getAllEmployee()
.cache(Duration.ofMinutes(60))
.flatMap(employees -> {
// process data
})
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));
}
Use external cache with Guava and CacheMono 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))
)
);
}

Related

How to get all data from Java Spring Cache

i need toknow how to retrieve or where to see al data stored in my cache.
#Configuration
#EnableCaching
public class CachingConf {
#Bean
public CacheManager cacheManager() {
Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.SECONDS)
.maximumSize(1000);
CaffeineCacheManager cacheManager = new CaffeineCacheManager("hr");
cacheManager.setCaffeine(cacheBuilder);
return cacheManager;
}
}
private final CacheManager cacheManager;
public CacheFilter(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
final var cache = cacheManager.getCache("hr");
......
I want to somehow see all data in my cache stored but the cache does not have get all or something like tht.Any advices guys?
The spring cache abstraction does not provide a method to get all the entries in a cache. But luckily they provide a method to get the underlying native cache abstraction which is Caffeine cache in your case.
The Caffeine cache has a method called asMap() to return a map view containing all the entries stored in the cache.
So combining them together will give you the following :
var cache = cacheManager.getCache("hr");
com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache = (com.github.benmanes.caffeine.cache.Cache<Object, Object>)cache.getNativeCache();
ConcurrentMap<K, V> map = nativeCache.asMap();
//Loop through the map here to access all the entries in the cache
Please note that it is a quick and effective fix but it will make your codes couple to Caffeine . If you mind , you can configure the spring cache to use JCache and configure JCache to use Caffeine cache (see this) . As JCache API implements Iterable<Cache.Entry<K, V>>, it allow you to iterate all of its entries :
var cache = cacheManager.getCache("hr");
javax.cache<Object, Object> nativeCache = (javax.cache<Object, Object>)cache.getNativeCache();
for(Cache.Entry<Object,Object> entry : nativeCache){
//access the entries here.
}

Spring Integration Flow with #Restcontoller Timing issue

A simple #RestController is connected with a #MessagingGateway to an IntegrationFlow.
After a load test we saw within the tracing that we lose "a lot of time" before even starting the processing within the flow:
Tracing result
In this example we can see that over 90ms spend befor sending the message to the flow.
Did anyone have some idea what leads to this behavior?
As far as I understood the documentation, everything is handled in the sender thread and therefore no special worker threads are created.
We use the Restcontroller since we need to create the documentation with springdoc-openapi-ui
ExampleCode:
RestController
#RestController
public class DescriptionEndpoint {
HttpMessageGateway httpMessageGateway;
public Result findData(#Valid dataRequest dataRequest) {
final Map<String, Object> headerParams = new HashMap<>();
return httpMessageGateway.basicDataDescriptionFlow(dataRequest, headerParams);
}
}
Gateway
#MessagingGateway
public interface HttpMessageGateway {
#Gateway(requestChannel = "startDataFlow.input")
Result basicDataDescriptionFlow(#Payload dataRequest prDataRequest, #Headers Map<String, Object> map);
}
IntegrationFlow
public class ExampleFlow {
#Bean
public IntegrationFlow startDataFlow() {
return new FlowExtension()
.handle(someHandler1)
.handle(someHandler2)
.handle(someHandler3)
.get();
}
}
After adding some more traces I realized, that this timing issue is caused by my spring security configuration.
Unfortunatelly, i thought, the span is only representing the time after the start of findData(..). But it seems, the tracing starts already in the proxy methods and security chain.
After improving some implementation on our JWTToken filter, the spend times for these endpoints are OK.

How to consume basic-authentication protected Restful web service via REACTIVE feign client

#ReactiveFeignClient(name = "service.b",configuration = CustomConfiguration.class)
public interface FeingConfiguration {
#PostMapping("/api/students/special")
public Flux<Student> getAllStudents(#RequestBody Flux<SubjectStudent> lista);
}
Help, how can I add a basic authentication to my header that I have in the service: service.b.
I have the CustomConfiguration.class class but it doesn't allow me, I have 401 authorization failed
#Configuration
public class CustomConfiguration {
#Bean
public BasicAuthRequestInterceptor basic() {
return new BasicAuthRequestInterceptor("user","user") ;
}
Looks like you are trying to use feign-reactive (https://github.com/Playtika/feign-reactive) to implement your REST clients. I am also using it for one of my projects and it looks like this library does not have an out-of-the-box way to specify basic auth credentials. At least no way to do this declaratively. So I didn't find a better way to do this than to abandon the auto-configuration via #ReactiveFeignClient and start configuring reactive feign clients manually. This way you can manually add "Authorization" header to all outgoing requests. So, provided this client definition:
public interface FeingClient {
#PostMapping("/api/students/special")
public Flux<Student> getAllStudents(#RequestBody Flux<SubjectStudent> lista);
}
Add the following configuration class to your Spring context, replacing username, password and service-url with your own data:
#Configuration
public class FeignClientConfiguration {
#Bean
FeignClient feignClient() {
WebReactiveFeign
.<FeignClient>builder()
.addRequestInterceptor(request -> {
request.headers().put(
"Authorization",
Collections.singletonList(
"Basic " + Base64.getEncoder().encodeToString(
"username:password".getBytes(StandardCharsets.ISO_8859_1))));
return request;
})
.target(FeignClient.class, "service-url");
}
}
Note, that this API for manual configurftion of reactive feign clients can differ between different versions of the reactive-feign library. Also note that this approach has a major drawback - if you start creating beans for your feign clients manually you lose the main advantage of Feign - ability to write REST-clients declaratively with just a few lines of code. E.g. if you want to use the above client with some sort of client-side load-balancing mechanism, like Ribbon/Eureka or Ribbon/Kubernetes, you will also need to configure that manually.
You can use a direct interceptor:
#Configuration
class FeignClientConfiguration {
#Bean
fun reactiveHttpRequestInterceptor(): ReactiveHttpRequestInterceptor {
return ReactiveHttpRequestInterceptor { request: ReactiveHttpRequest ->
request.headers()["Authorization"] = //insert data from SecurityContextHolder;
Mono.just(request)
}
}
}

Spring Webflux and #Cacheable - proper way of caching result of Mono / Flux type

I'm learning Spring WebFlux and during writing a sample application I found a concern related to Reactive types (Mono/Flux) combined with Spring Cache.
Consider the following code-snippet (in Kotlin):
#Repository
interface TaskRepository : ReactiveMongoRepository<Task, String>
#Service
class TaskService(val taskRepository: TaskRepository) {
#Cacheable("tasks")
fun get(id: String): Mono<Task> = taskRepository.findById(id)
}
Is this valid and safe way of caching method calls returning Mono or Flux? Maybe there are some other principles to do this?
The following code is working with SimpleCacheResolver but by default fails with Redis because of the fact that Mono is not Serializable. In order to make them work e.g Kryo serializer needs to be used.
Hack way
For now, there is no fluent integration of #Cacheable with Reactor 3.
However, you may bypass that thing by adding .cache() operator to returned Mono
#Repository
interface TaskRepository : ReactiveMongoRepository<Task, String>
#Service
class TaskService(val taskRepository: TaskRepository) {
#Cacheable("tasks")
fun get(id: String): Mono<Task> = taskRepository.findById(id).cache()
}
That hack cache and share returned from taskRepository data. In turn, spring cacheable will cache a reference of returned Mono and then, will return that reference. In other words, it is a cache of mono which holds the cache :).
Reactor Addons Way
There is an addition to Reactor 3 which allows fluent integration with modern in-memory caches like caffeine, jcache, etc. Using that technique you will be capable to cache your data easily:
#Repository
interface TaskRepository : ReactiveMongoRepository<Task, String>
#Service
class TaskService(val taskRepository: TaskRepository) {
#Autowire
CacheManager manager;
fun get(id: String): Mono<Task> = CacheMono.lookup(reader(), id)
.onCacheMissResume(() -> taskRepository.findById(id))
.andWriteWith(writer());
fun reader(): CacheMono.MonoCacheReader<String, Task> = key -> Mono.<Signal<Task>>justOrEmpty((Signal) manager.getCache("tasks").get(key).get())
fun writer(): CacheMono.MonoCacheWriter<String, Task> = (key, value) -> Mono.fromRunnable(() -> manager.getCache("tasks").put(key, value));
}
Note: Reactor addons caching own abstraction which is Signal<T>, so, do not worry about that and following that convention
I have used Oleh Dokuka's hacky solution worked great but there is a catch. You must use a greater Duration in Flux cache than your Cachable caches timetolive value. If you dont use a duration for Flux cache it wont invalidate it (Flux documentation says "Turn this Flux into a hot source and cache last emitted signals for further Subscriber.").
So making Flux cache 2 minutes and timetolive 30 seconds can be valid configuration. If ehcahce timeout occurs first, than a new Flux cache reference is generated and it will be used.
// In a Facade:
public Mono<HybrisResponse> getProducts(HybrisRequest request) {
return Mono.just(HybrisResponse.builder().build());
}
// In a service layer:
#Cacheable(cacheNames = "embarkations")
public HybrisResponse cacheable(HybrisRequest request) {
LOGGER.info("executing cacheable");
return null;
}
#CachePut(cacheNames = "embarkations")
public HybrisResponse cachePut(HybrisRequest request) {
LOGGER.info("executing cachePut");
return hybrisFacade.getProducts(request).block();
}
// In a Controller:
HybrisResponse hybrisResponse = null;
try {
// get from cache
hybrisResponse = productFeederService.cacheable(request);
} catch (Throwable e) {
// if not in cache then cache it
hybrisResponse = productFeederService.cachePut(request);
}
return Mono.just(hybrisResponse)
.map(result -> ResponseBody.<HybrisResponse>builder()
.payload(result).build())
.map(ResponseEntity::ok);

Sending System Metrics to Graphite with Spring-Boot

Spring-Boot actuator exposes many useful metrics at /metrics such as uptime, memory usage, GC count.
Only a subset of these are sent to Graphite when using the Dropwizard Metrics integration. In specific, only the counters and gauges
Is there any way to get these other metrics to be published to graphite?
The documentation suggests that it should be possible:
Users of the Dropwizard ‘Metrics’ library will find that Spring Boot metrics are automatically published to com.codahale.metrics.MetricRegistry
System Metrics created by Spring boot are not reported automatically because MetricsRegistry does not know anything about those Metrics.
You should register those metrics manually when your application boots up.
#Autowired
private SystemPublicMetrics systemPublicMetrics;
private void registerSystemMetrics(MetricRegistry metricRegistry) {
systemPublicMetrics.metrics().forEach(m -> {
Gauge<Long> metricGauge = () -> m.getValue().longValue();
metricRegistry.register(m.getName(), metricGauge);
});
}
I have defined Gauge, not all the system metrics should be added as gauge. e.g. the Counter should be used to capture count values.
If you don't want to use Spring boot. Use can include metrics-jvm out of the box to capture JVM level metrics.
Here's a solution that does update DropWizard metrics on Spring metrics change. It also does that without turning #EnableScheduling on:
#EnableMetrics
#Configuration
public class ConsoleMetricsConfig extends MetricsConfigurerAdapter {
#Autowired
private SystemPublicMetrics systemPublicMetrics;
#Override
public void configureReporters(MetricRegistry metricRegistry) {
metricRegistry.register("jvm.memory", new MemoryUsageGaugeSet());
metricRegistry.register("jvm.thread-states", new ThreadStatesGaugeSet());
metricRegistry.register("jvm.garbage-collector", new GarbageCollectorMetricSet());
metricRegistry.register("spring.boot", (MetricSet) () -> {
final Map<String, Metric> gauges = new HashMap<String, Metric>();
for (final org.springframework.boot.actuate.metrics.Metric<?> springMetric :
systemPublicMetrics.metrics()) {
gauges.put(springMetric.getName(), (Gauge<Object>) () -> {
return systemPublicMetrics.metrics().stream()
.filter(m -> StringUtils.equals(m.getName(), springMetric.getName()))
.map(m -> m.getValue())
.findFirst()
.orElse(null);
});
}
return Collections.unmodifiableMap(gauges);
});
registerReporter(ConsoleReporter
.forRegistry(metricRegistry)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build())
.start(intervalSecs, TimeUnit.SECONDS);
}
}
It uses the com.ryantenney.metrics library for enabling additional Spring annotations support and DropWizard reporters:
<dependency>
<groupId>com.ryantenney.metrics</groupId>
<artifactId>metrics-spring</artifactId>
<version>3.1.3</version>
</dependency>
But it is actually not necessary in this particular case.

Resources