web-dev-qa-db-fra.com

Spring Webflux et @Cacheable - méthode appropriée de mise en cache des résultats de type Mono/Flux

J'apprends Spring WebFlux et lors de l'écriture d'un exemple d'application, j'ai trouvé un problème lié aux types réactifs (Mono/Flux) associé à Spring Cache.

Considérez l'extrait de code suivant (en Kotlin):

@Repository
interface TaskRepository : ReactiveMongoRepository<Task, String>

@Service
class TaskService(val taskRepository: TaskRepository) {

    @Cacheable("tasks")
    fun get(id: String): Mono<Task> = taskRepository.findById(id)
}

S'agit-il d'un moyen valide et sûr de mettre en cache les appels de méthode renvoyant Mono ou Flux? Peut-être existe-t-il d'autres principes pour le faire?

Le code suivant fonctionne avec SimpleCacheResolver mais échoue par défaut avec Redis car Mono n'est pas sérialisable. Pour les faire fonctionner, par exemple, le sérialiseur Kryo doit être utilisé.

8
Tomek Zaremba

Façon de piratage

Pour l'instant, il n'y a pas d'intégration fluide de @Cacheable avec Reactor 3. Cependant, vous pouvez contourner ce problème en ajoutant l'opérateur .cache() à return 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()
}

Ce pirater le cache et le partage retournés à partir de données taskRepository À son tour, Spring mise en cache permettra de mettre en cache une référence de Mono renvoyé, puis de renvoyer cette référence. En d'autres termes, c'est un cache de mono qui contient le cache :).

Réactifs Addons Way

Il y a un addition au Reactor 3 qui permet une intégration fluide avec les caches en mémoire modernes comme caféine , jcache , etc. En utilisant cette technique, vous serez capable de: cachez facilement vos données:

@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));
} 

Remarque: les addons Reactor mettant en cache leur propre abstraction, qui est Signal<T>, ne vous inquiétez donc pas et suivez cette convention.

15
Oleh Dokuka

J'ai utilisé la solution de hacky d'Oleh Dokuka qui fonctionnait très bien, mais il y a un problème. Vous devez utiliser une durée supérieure dans le cache Flux par rapport à votre valeur de temps de cache des cachables. Si vous n'utilisez pas de durée pour le cache de flux, cela ne l'invalidera pas (la documentation de Flux indique "Transformez ce flux en source chaude et cache les derniers signaux émis pour un autre abonné"). Ainsi, la configuration du cache de flux de 2 minutes et de 30 secondes au maximum peut être une configuration valide. Si le délai d'expiration est dépassé en premier, une nouvelle référence de cache Flux est générée et sera utilisée.

0
ilker Kopan