Enable Ehcache caching for Axon Sagas - ehcache

My goal is to create 2 caches with the newCacheManagerBuilder() API and store them off-heap
My understanding is that i need one cache for the Saga instances and one for the Associations. First I'm initializing the cache manager.
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public CacheManager cacheManager() {
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build();
cacheManager.init();
return cacheManager;
}
#Bean
public org.ehcache.Cache<String, MySaga> sagaEhcache(CacheManager cacheManager) {
return cacheManager.createCache("sagaEhcache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
String.class,
MySaga.class,
ResourcePoolsBuilder.newResourcePoolsBuilder().offheap(sagaCacheOffHeapMb, MemoryUnit.MB)
).build()
);
}
#Bean
public org.ehcache.Cache<String, AssociationValue> sagaAssocEhcache(CacheManager cacheManager) {
return cacheManager.createCache("sagaAssocEhcache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
String.class,
AssociationValue.class,
ResourcePoolsBuilder.newResourcePoolsBuilder().offheap(sagaAssocCacheOffHeapMb, MemoryUnit.MB)
).build()
);
}
Are the mentioned mappings correct: String/MySaga and String/AssociationValue ?

Axon Framework uses the notion of a cache adapters to utilize caches for things like the CachingSagaStore or CachingEventSourcingRepository.
Both require an Axon implementation of Cache, which can be traversed back to allowing you to use either a JCacheAdapter or EhCacheAdapter.
As you are using EhCache, you would be required to use the EhCacheAdapter in your set up.
There is however one drawback with your setup.
You are using org.ehcache, thus version 3. Axon Framework however uses net.sf.ehcache, thus version 2.
So, unless there is some mechanism to revert EhCache version 3 to version 2, you will be required to actually downgrade the EhCache version in your application for now.
Update
As shown in the comments, you've implemented your own EhCacheAdapter to support the latest version of EhCache with Axon Framework. This thus leaves you with the following question:
Are the mentioned mappings correct: String/MySaga and String/AssociationValue?
Checking the CachingSagaStore implementation, you can see an CacheEntry<S> is used for the Saga and a Set<String> for the associations. The generic S stands for the Saga implementation, thus MySaga in your example.
Update 2
For a sample project leveraging Axon Framework together with version 2 of EhCache, you can check out the Axon Trader application.

Related

More than 1 caching storage in spring boot app

I am facing a strange issue - I have hazelcast and redis in my project. Suddenly all #Cacheable annotations are putting entries only to hazelcast cache, even if the particular cache name is configured via redis cache builder:
#Bean
fun redisCacheManagerBuilderCustomizer(): RedisCacheManagerBuilderCustomizer? {
return RedisCacheManagerBuilderCustomizer { builder: RedisCacheManagerBuilder ->
builder
.withCacheConfiguration(
MY_CACHE,
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofDays(3))
)
}
}
Using cache:
#Cacheable(cacheNames = [CacheConfig.MY_CACHE])
#Cacheable(value= [CacheConfig.MY_CACHE])
Both does not work and forwards requests to hazelcast only. How to solve this? Using different cacheManager?
Typically, only 1 caching provider is in use to cache data, such as in the service or data access tier of your Spring [Boot] application using Spring's Cache Abstraction and infrastructure components, such as the CacheManager and caching annotations.
When multiple caching providers (e.g. Hazelcast and Redis) are on the classpath of your Spring Boot application, then it might be necessary to declare which caching provider (e.g. Redis) you want to [solely] use for caching purposes. With this arrangement, Spring Boot allows you to declare your intentions using the spring.cache.type property as explained in the ref doc, here (see first Tip). Valid values of this property are defined by the enumerated values in the CacheType enum.
However, if you want to cache data using multiple caching providers at once, then you need to explicitly declare your intentions using this approach as well.
DISCLAIMER: It has been awhile since I have traced through Spring Boot auto-configuration where caching is concerned, and how it specifically handles multiple caching providers on the application classpath, especially when a specific caching provider has not been declared, such as by explicitly declaring the spring.cache-type property. However, and again, this may actually be your intention, to use multiple caching providers in a single #Cacheable (or #CachePut) service or data access operation. If so, continue reading...
To do so, you typically use 1 of 2 approaches. These approaches are loosely described in the core Spring Framework's ref doc, here.
1 approach is to declare the cacheNames of the caches from each caching provider along with the CacheManager, like so:
#Service
class CustomerService {
#Cacheable(cacheNames = { "cacheOne", "cacheTwo" }, cacheManager="compositeCacheManager")
public Customer findBy(String name) {
// ...
}
}
In this case, "cacheOne" would be the name of the Cache managed by caching provider one (e.g. Redis), and "cacheTwo" would be the name of the Cache managed by caching provider two (i.e. "Hazelcast").
DISCLAIMER: You'd have to play around, but it might be possible to simply declare a single Cache name here (e.g. "Customers"), where the caches (or cache data structures in each caching provider) are named the same, and it would still work. I am not certain, but it seems logical this would work as well.
The key (no pun intended) to this example, however, is the declaration of the CacheManager using the cacheManager attribute of the #Cacheable annotation. As you know, the CacheManager is the Spring SPI infrastructure component used to find and manage Cache objects (caches from the caching providers) used for caching purposes in your Spring managed beans (such as CustomerService).
I named this CacheManager deliberately, "compositeCacheManager". Spring's Cache Abstraction provides the CompositeCacheManager implementation, which as the name suggests, composes multiple CacheManagers for use in single cache operation.
Therefore, you could do the following in you Spring [Boot] application configuration:
#Configuration
class MyCachingConfiguration {
#Bean
RedisCacheManager cacheManager() {
// ...
}
#Bean
HazelcastCacheManager hazelcastCacheManager() {
// ...
}
#Bean
CompositeCacheManager compositeCacheManager(RedisCacheManager redis, HazelcastCacheManager hazelcast) {
return new CompositeCacheManager(redis, hazelcast);
}
}
NOTE: Notice the RedisCacheManager is the "default" CacheManager declaration and cache provider (implementation) used when no cache provider is explicitly declared in a caching operation, since the bean name is "cacheManager".
Alternatively, and perhaps more easily, you can choose to implement the CacheResolver interface instead. The Javadoc is rather self-explanatory. Be aware of the Thread-safety concerns.
In this case, you would simply declare a CacheResolver implementation in your configuration, like so:
#Configuration
class MyCachingConfiguration {
#Bean
CacheResolver customCacheResolver() {
// return your custom CacheResolver implementation
}
}
Then in your application service components (beans), you would do:
#Service
class CustomerService {
#Cacheable(cacheNames = "Customers", cacheResolver="customCacheResolver")
public Customer findBy(String name) {
// ...
}
}
DISCLAIMER: I have not tested either approach I presented above here, but I feel reasonably confident this should work as expected. It may need some slight modifications, but should generally be the approach(es) you should follow.
If you have any troubles, please post back in the comments and I will try to follow up.

Is it possible to #CacheEvict keys that match a pattern?

Is there something along the lines of #CacheEvict(value = "FOO", key = "baz*") so that when the cache FOO contains keys baz_1 and baz_2 they get evicted?
Assuming that you have spring-boot-starter-cache as dependency, spring boot auto-configures a CacheManager bean named cacheManager.
Also, assuming you have spring-boot-starter-data-redis as a dependency, RedisCacheManager is picked as the CacheManager implementation.
#CacheEvict (and the caching abstraction API) doesn't let you the option to evict by prefix, but using an AOP advice (or elsewhere where fits), you can take advantage of the underlying implementation:
RedisCache redisCache = (RedisCache) cacheManager.getCache("FOO");
redisCache.getNativeCache().clean("FOO", "baz*".getBytes());
Didn't try it actually, but I think this should work.
Likewise, you can adapt to other caching implementation.
The shortcoming of this approach, is that you'll have to change your code upon changing the cache implementation.

refreshAfterWrite requires a LoadingCache in spring boot caffeine application

I am trying to write an application to cache which reloads every few seconds. I decided to use spring boot Caffeine and got a sample application too. But when I am specifying refreshAfterWrite property, it throws exception: refreshAfterWrite requires a LoadingCache
spring:
cache:
cache-names: instruments, directory
caffeine:
spec: maximumSize=500, expireAfterAccess=30s, refreshAfterWrite=30s
To resolve this I provide Loading Cache Bean, but cache stopped working altogether:
#Bean
public CacheLoader<Object, Object> cacheLoader() {
return string -> {
System.out.println("string = " + string);
return string;
};
}
#Bean
public LoadingCache<Object, Object> loader(CacheLoader<Object, Object> cacheLoader) {
return Caffeine.newBuilder()
.refreshAfterWrite(1, TimeUnit.SECONDS)
.build(cacheLoader);
}
Do we have some simple way for reload to work?
To conclude here, using the LoadingCache feature of Caffeine with Spring's cache abstraction does not make much sense since they share a lot of features.
#Cacheable typically provide a way to mark a method to retrieve an element that is not present in the cache yet. LoadingCache achieves the same scenario, requiring you to provide a handle that can load a missing element by id.
If you absolutely need to use a LoadingCache, I'd inject the Cache in your code and interact with it programmatically.

Difference between net.sf.ehcache and org.ehcache?

What is the difference between net.sf.ehcache and org.ehcache?
The current version of net.sf.ehcache is 2.10.5 whereas same for org.ehcache is 3.5.2.
Spring uses net.sf.ehcache's CacheManager, and org.ehcache's CacheManager isn't compatible for same.
Is there any specific reason for this? Please explain.
As you can verify on the page http://www.ehcache.org/downloads/, Ehcache 3 is using the package prefix org.ehcache and Ehcache 2 is using the package prefix net.sf.ehcache. That's it.
There are different in many levels. With ehcache 3.x, Element is not there anymore. One should directly put the key and value in the Cache therefore you can provide types when you create cache:
Cache<Long, String> myCache = cacheManager.getCache("myCache", Long.class, String.class);
And consequently when retrieving the value, you avoid the hassle of getObjectValue instead you just treat Cache like a ConcurrentMap. So you won't get NullPointerException if the key doesn't exist, so you won't need check for cache.get(cacheKey) != null
cache.get(cacheKey);
The way to instantiate CacheManager has also changed. You won't getInstance so it is not singleton anymore. Instead you get a builder, which is way nicer, especially that you can provide it with configuration parameters inline:
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("preConfigured",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
ResourcePoolsBuilder.heap(100))
.build())
.build(true);

EhCache: #CacheEvict on Multiple Objects Using Annotations

I understand that using Spring's (3.1) built in CacheManager using the EhCache implementation, there are certain limitations when in proxy mode (the default) as per this post:
Spring 3.1 #Cacheable - method still executed
Consider the scenario I have:
#CacheEvict(value = "tacos", key = "#tacoId", beforeInvocation = true)
removeTaco(String tacoId) {
// Code to remove taco
}
removeTacos(Set<String> tacoIds) {
for (String tacoId : tacoIds) {
removeTaco(tacoId);
}
}
In this repository method, calling removeTacos(tacoIds) will not actually Evict anything from the Cache because of the limitation described above. My workaround, is that on a service layer above, if I wanted to delete multiple tacos, I'd be looping through each taco Id and passing it into removeTaco(), and never using removeTacos()
However, I'm wondering if there's another way to accomplish this.
1) Is there an SpEL expression that I could pass into the key that would tell EhCache to expire every id in the Set?
e.g. #CacheEvict(value = "tacos", key = "#ids.?[*]") // I know this isn't valid, just can't find the expression.
Or is there a way I can have removeTacos() call removeTaco and actually expire the Cached objects?
The #Caching annotation can be used to combine multiple annotations of the same type such as #CacheEvict or #CachePut, this is the example from the Spring documentation
#Caching(evict = { #CacheEvict("primary"), #CacheEvict(value="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)
You can do one of two things
#CacheEvict(value = "tacos", allEntries = true)
removeTacos(Set<String> tacoIds)
which is not so bad if tacos are read a lot more than they are removed
OR
removeTacos(Set<String> tacoIds) {
for (String tacoId : tacoIds) {
getTacoService().removeTaco(tacoId);
}
}
by calling the service (proxy) you invoke the cache eviction.
AFAIK #CacheEvict supports only removing single entry (by key) or all entries in given cache, there's no way to remove at once multiple entries. If you want to put, update or remove multiple objects from cache (using annotations) and you may switch to memcached take a look at my project Simple Spring Memcached (SSM).
Self invocations don't go through the proxy so one of the solution is to switch to other mode than proxy. Anther solution (I'm not recommending it) may be keeping reference to the service in service (as an autowired field) and use it to invoke removeTaco.
Several months ago I had similar issue in one of my projects. It didn't use Spring Cache but SSM which also requires proxy. To made it work I moved caching (annotations) from service to DAO (repositories) layer. It solved problem with self invocation.

Resources