Cache
Module for creating caches based on Caffeine or Redis using both declarative-style annotations and using their imperative style.
Caffeine¶
Library-based implementation of Caffeine for in-memory caches within the application.
Dependency¶
Configuration¶
Example of complete configuration for mycache.config
cache, parameters are described in the CaffeineCacheConfig
class (default or example values are specified):
mycache {
config {
expireAfterWrite = "10s" //(1)!
expireAfterAccess = "10s" //(2)!
initialSize = 10 //(3)!
maximumSize = 10 //(4)!
}
}
- Time after which the value for the key will be deleted is reported after the value is added (optional)
- Time after which the value for the key will be deleted, counted after a read operation (optional)
- Initial cache size (helps to avoid cache expansion in case of active swelling) (optional)
- Maximum cache size (When the boundary is reached or slightly earlier will exclude the least relevant values from the cache) (default is
100000
)
mycache:
config:
expireAfterWrite: "10s" #(1)!
expireAfterAccess: "10s" #(2)!
initialSize: 10 #(3)!
maximumSize: 10 #(4)!
- Time after which the value for the key will be expired is reported after the value is added (optional)
- Time after which the value for the key will be deleted is counted after a read operation (optional)
- Initial cache size (helps to avoid cache expansion in case of active swelling) (optional)
- Maximum cache size (When the boundary is reached or slightly earlier will exclude the least relevant values from the cache) (default is
100000
)
Redis¶
Implementation based on in-memory database Redis and connection driver Lettuce.
Dependency¶
Dependency build.gradle
:
Module:
Configuration¶
It is required to separately configure the Lettuce driver to connect to Redis. A single connection is used for all caches.
Example of a complete configuration for lettuce driver, parameters are described in the LettuceConfig
class (default or example values are specified):
lettuce {
uri = "redis://locahost:6379" //(1)!
user = "admin" //(2)!
password = "12345" //(3)!
database = 1 //(4)!
protocol = "REP3" //(5)!
socketTimeout = "15s" //(6)!
commandTimeout = "15s" //(7)!
}
- URI to connect to Redis (required)
- Username for connection (optional)
- Password for connection (optional)
- Database number for connection (optional)
- Protocol for connection
- Connection timeout
- Command execution timeout
lettuce:
uri: "redis://locahost:6379" #(1)!
user: "admin" #(2)!
password: "12345" #(3)!
database: 1 #(4)!
protocol: "REP3" #(5)!
socketTimeout: "15s" #(6)!
commandTimeout: "15s" #(7)!
- URI to connect to Redis (required)
- Username for connection (optional)
- Password for connection (optional)
- Database number for connection (optional)
- Protocol for connection
- Connection timeout
- Command execution timeout
Redis cache configurations configure the behavior of a particular cache.
Example of a complete configuration for mycache.config
cache, parameters are described in the RedisCacheConfig
class (example values are specified):
mycache {
config {
expireAfterWrite = "10s" //(1)!
expireAfterAccess = "10s" //(2)!
keyPrefix = "mykey" //(3)!
}
}
- When writing, sets the expiration time (optional)
- When reading, sets the time expiration (optional)
- Prefix a key in a particular cache to avoid key collisions within a Redis database, can be an empty string then keys will be without prefixes (required)
mycache:
config:
expireAfterWrite: "10s" #(1)!
expireAfterAccess: "10s" #(2)!
keyPrefix: "mykey" //(3)!
- Sets the expiration time when writing (optional)
- When reading, sets the time expiration (optional)
- Prefix a key in a specific cache to avoid key collisions within a Redis database, can be an empty string then keys will be without prefixes (required)
Usage¶
Creating a cache will require registering a typed @Cache
contract.
The contract interface should only be inherited from Kora's provided implementations: CaffeineCache
/ RedisCache
.
For such @Cache
an implementation will be created and added to the graph, it can be used to enforce dependencies.
To register @Cache
and specify the config, it is required to annotate with the @Cache
annotation where the value
argument means the full path to the config.
Imperative¶
Caches are available for injection as dependencies on the interface and can be used in conjunction with declarative operations.
The CaffeineCache
implementation provides basic Cache
interface contracts for synchronous operations,
and RedisCache
provides both Cache
and AsyncCache
for asynchronous operations with CompletionStage
signatures.
The interfaces provide get, delete, update, batch, etc. operations. Cache implementations can also provide self-specific contracts.
Declarative¶
All aspect use cases will assume the cache implementation above.
Get¶
To cache and retrieve a value from the cache for the get() method, annotate it with the @Cacheable
annotation.
The key for the cache is compiled from the method arguments, the order of the arguments matters, in this case it will be compiled from the value arg1
.
Put¶
To add values to the cache via the put() method, annotate it with the @CachePut
annotation.
The method annotated with @CachePut
will be called and its value put into the cache defined in value.
The key for the cache is compiled from the method arguments, the order of the arguments matters, in this case it will be compiled from the value arg1
.
Invalidate¶
To remove a keyed value from the cache via the evict() method, annotate it with the @CacheInvalidate
annotation.
The method annotated with @CacheInvalidate
will be called and then the keyed values for the cache defined in value will be deleted by key.
The key for the cache is compiled from the method arguments, the order of the arguments matters, in this case it will be compiled from the value arg1
.
Invalidate all¶
To remove all values from the cache via the evictAll() method, annotate it with the @CacheInvalidate
annotation and specify the invalidateAll = true parameter.
The method annotated with @CacheInvalidate
will be called and then all of the cache values defined in value will be removed.
Composite cache¶
In case you have multiple caches, you need to connect both modules and specify the appropriate number of annotations over the method.
And the annotated class itself is like this:
The order of aspect calls corresponds to the order of annotations above the method, top to bottom.
Key¶
In case the cache key represents 1 argument, it is required to register Cache
with a signature corresponding to the key and value types.
Conversion¶
In case an argument cannot be converted to a cache key, the cache implementation will require an appropriate converter
with the CacheKeyMapper
interface, in case there are 2 arguments for the key then CacheKeyMapper2
will be required, and so on.
Such a converter can also be provided manually using the @Mapping
annotation,
example of converting a complex object into a simple cache key:
@Component
public class SomeService {
public record UserContext(String userId, String traceId) { }
public static final class UserContextMapping implements CacheKeyMapper<String, UserContext> {
@Nonnull
@Override
public String map(UserContext arg) {
return arg.userId();
}
}
@Mapping(UserContextMapping.class)
@Cacheable(MyCache.class)
public String get(UserContext context) {
// do something
}
}
Composite key¶
In case the cache key represents N arguments, it is required to register Cache
using an
class to describe such a key.
Example for Cache
where the composite key consists of 2 elements:
It is supposed to create its own record
class that would describe the composite key.
If RedisCache
is used, it is assumed that all composite key arguments will default to non null
,
or a custom key resolver will need to be used.
Argument ordering¶
If the method accepts arguments that you want to exclude from the composite key,
or the order of the arguments does not match the order of the arguments of the composite key constructor,
you should use the parameters
annotation attribute and define which method arguments to use and in what order.
Loadable Cache¶
The library provides a component for building an entity that combines GET and PUT operations without using aspects - LoadableCache
@Cache("mycache.config")
public interface MyCache extends CaffeineCache<String, String> { }
@KoraApp
public interface Application : CaffeineCacheModule {
default LoadableCache<String, String> loadableCache(MyCache cache, SomeService someService) {
return cache.asLoadable(someService::loadEntity);
}
}
Signatures¶
Available signatures for repository methods out of the box:
Class must be non final
in order for aspects to work.
The T
refers to the type of the return value.
T myMethod()
@Nullable T myMethod()
Optional<T> myMethod()
Mono<T> myMethod()
Project Reactor (require dependency)
Class must be open
in order for aspects to work.
By T
we mean the type of the return value, either T?
, or Unit
.
myMethod(): T
suspend myMethod(): T
Kotlin Coroutine (require dependency asimplementation
)