I am trying to implement a request cache so I can avoid expensive API calls as much as possible.
Currently I have implemented a caching system using Caffeine like so:
@Service
class CacheService {
val playlistCache: Cache<String, Playlist> = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(30, TimeUnit.SECONDS)
.build()
fun queryPlaylistCache(playlistCacheKey: String) =
Mono.justOrEmpty(playlistCache.getIfPresent(playlistCacheKey)).map<Signal<out Playlist>> { Signal.next(it) }
val userSavedSongsCache: Cache<String, List<PlaylistOrUserSavedTrack>> = Caffeine.newBuilder().maximumSize(10_000).expireAfterWrite(30, TimeUnit.SECONDS).build()
}
@Service
class SpotifyRequestService(
val webClients: WebClients,
val cacheService: CacheService
) {
fun getAPlaylist(Authorization: String, playlistId: String, fields: String?): Mono<Playlist> {
return CacheMono.lookup({ key: String -> cacheService.queryPlaylistCache(key) }, "${playlistId}$fields")
.onCacheMissResume(
webClients.spotifyClientServiceClient.get()
.uri { uriBuilder: UriBuilder ->
uriBuilder.path("/playlists/{playlist_id}")
.queryParam("fields", fields ?: "")
.build(playlistId)
}
.header("Authorization", Authorization)
.retrieve()
.bodyToMono(Playlist::class.java)
)
.andWriteWith { key, value ->
Mono.fromRunnable { value?.get()?.let { cacheService.playlistCache.put(key, it) } } }
}
}
However, from what I have read, it seems like implementing caching this way is not optimal because getting/setting the cache is a blocking operation.
However, in this thread: Cache the result of a Mono from a WebClient call in a Spring WebFlux web application the chosen answer mentions reasons why this is an acceptable use case to use a blocking operation.
Can anybody shed some light as to what the correct solution is?