CaffeineをKotlin Coroutinesでも使えるようにする方法 - caffeine-coroutinesの紹介
Motivation
JVMには、キャッシュを実装するときに広く使われているCaffeineというライブラリがあります。
このライブラリはJavaで書かれているため、Coroutines上ではそのままだと使用することができません。
CaffeineにはAsyncサポート(ComletableFutureに対応したインタフェース)があるため、これを駆使することで一応Coroutines上でも動作させることが可能になります。ただし、CaffeineとCoroutinesへの理解が深くないとなかなか敷居が高いのかなと思います。
実際に、Stack OverflowでもCoroutines上での使い方についての質問もあり、苦労している人も一定数いるようです。
そこで、誰もが簡単に使えるようにcaffeine-coroutinesというライブラリを作っています。
※ CaffeineのREADMEでも紹介してもらっています。
基本的な使い方
なにはともあれ、まずは依存にいれましょう。
implementation("dev.hsbrysk:caffeine-coroutines:{{version}}")
使い方ですが、基本的にオリジナルのCaffeineとほとんど差分はありません。新しく覚えることは、buildCoroutine
を呼び出すことでCoroutines上で使えるCoroutineCache
インスタンスを得ることができるということだけです。
suspend fun main() {
val cache: CoroutineCache<String, String> = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofMinutes(5))
.buildCoroutine() // buildCoroutineを使う
val value = cache.get("key") {
delay(1000) // suspend functionが使えるようになる
"value"
}
println(value)
}
もちろん、Loading Cache スタイルもサポートしています。buildCoroutine
にloaderを渡すと、CoroutineLoadingCache
インスタンスを得ることができます。
suspend fun main() {
val cache: CoroutineLoadingCache<String, String> = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofMinutes(5))
.buildCoroutine { // buildCoroutineを使う
delay(1000) // suspend functionが使えるようになる
"value"
}
val value = cache.get("key")
println(value)
}
Philosophy
Caffeineではblocking用途のための実装としてCache
とLoadingCache
があり、async(CompletableFuture)用途のための実装としてAsyncCache
とAsyncLoadingCache
があります。
このライブラリはその名の通り、Coroutines用途の実装としてCoroutineCache
とCoroutineLoadingCache
を提供することだけに焦点を当てています。
独自のAPI/インタフェースを導入することは必要最低限にしています。
独自のAPI/インタフェースを導入すると、利用者を混乱させてしまいます。導入も難しくなりますし、利用を廃止するときにも面倒なことになるでしょう。
Aedileとの比較 (類似実装との比較)
実はAedileという、CaffeineのKotlin Wrapperもあります。
が、自分がcaffeine-coroutinesを実装した時点だと、以下の問題を感じていました。
(もっともこの問題の多くは最近リリースされたversion 2から解決されているので、Aedileを使っても良いでしょう)
-
Coroutine Scopeの取り扱いに関する問題
- 本来、呼び出し元のScopeから派生するScopeで実行すべきなのですが(じゃないと例えばCoroutine Contextなどが引き継がれない)、なぜか別途のScopeを利用する形式となっていました。
- 結果として、MDCなどの引き継ぎに問題がありました。
- Coroutineのキャンセルに関する問題
-
独自のAPIが多かった
- Cacheを作るために、caffeineBuilderという独自のビルダーを使う必要がありました。
- 普段使い慣れている公式のビルダーをそのまま使ったほうがわかりやすいですし、結果として本家の実装にある機能を全て使うことができませんでした。
- この部分については、version 2からcaffeine-coroutinesと同じようなスタイルを採用したことで解決はされているようです。
- また、metricsに関してもCacheMetricsという独自のAPIを使う必要があり、そのあたりも良くないと感じていました。これについてもversion 2からはDeprecatedされているようです。
- Cacheを作るために、caffeineBuilderという独自のビルダーを使う必要がありました。
まとめ
caffeine-coroutinesは、CaffeineをCoroutines上で簡単に使えるようにするライブラリです。buildCoroutine
拡張関数を使うことで、Caffeineの機能をそのままCoroutines上で扱えます。
類似実装のAedileも改善されていますが、過去にはCoroutine Scopeの扱いやキャンセル処理、独自APIの多さといった課題がありました。caffeine-coroutinesは公式APIを活かしたシンプルな設計が特徴です。
Coroutines環境でCaffeineを使いたい方は、ぜひ試してみてください。
Discussion