1. 概要

キャッシングは、高性能アプリケーションにとって非常に重要です。 その結果、多くのキャッシングライブラリが開発されており、あるキャッシングライブラリを別のキャッシングライブラリから選択することは困難な作業となっています。 さらに、あるキャッシュライブラリから別のキャッシュライブラリに切り替えるには、大量のリファクタリングが必要になる場合があります。 ScalaCache は、これらの問題に対する可能な解決策です。

ScalaCacheは、ライブラリをキャッシュするためのファサードです。 はキャッシュ処理を簡素化し、簡単に使用できるように標準化されたAPIも提供します

このチュートリアルでは、ScalaCacheと、それがキャッシュ操作をどのように簡素化するかを見ていきます。

2. message

ScalaCacheは、さまざまなキャッシュライブラリをサポートしています。 そのようなライブラリには、 Redis Memcached Guava Cache Caffeine 、およびEhCacheがあります。 最小限のリファクタリングで、ScalaCacheでこれらのキャッシングライブラリのいずれかを簡単に使用できます

ScalaCacheを使用すると、多くの利点があります。

  • キャッシングライブラリ用の標準化されたAPI
  • 多くのリファクタリングなしで、異なるキャッシングライブラリを簡単に切り替える機能
  • シンプルで慣用的なScalaAPI
  • 同期モードと非同期モード

3. 設定

3.1. SBTの依存関係

ScalaCacheSBT依存関係を追加することから始めましょう。

"com.github.cb372" %% "scalacache-core" % "0.28.0"

次に、必要なキャッシュライブラリラッパーの依存関係を追加する必要があります。 この例では、GuavaCacheを使用します。

"com.github.cb372" %% "scalacache-guava" % "0.28.0"

3.2. 構成

ScalaCacheの使用を開始するには、関連するキャッシュインスタンスをインスタンス化します。 ユーザー情報のキャッシュにGuavaCacheを使用していると仮定します。

case class User(id: Long, name: String)
val underlyingGuavaCache = CacheBuilder.newBuilder().maximumSize(10000L).build[String, Entry[User]]

次に、Guavaキャッシュインスタンスを使用してScalaCacheをインスタンス化する必要があります。

implicit val scalaCacheGuava: Cache[User] = GuavaCache(underlyingGuavaCache)

scalaCacheGuavaimplicit変数として宣言されており、スコープに入るたびに使用できることに注意してください。

4. キャッシング操作

ScalaCacheは、次の3種類のキャッシュをサポートしています。

  • 手動キャッシュ:キャッシュを手動で設定および削除することによるキャッシュ操作
  • メモ化 memoize memoizeSync、memoizeFメソッドを使用してメソッドの結果を自動的にキャッシュする簡単な方法
  • キャッシングブロック:暗記に似ていますが、キャッシングおよびキャッシングF メソッドを使用して、メソッドレベルではなくコードブロックレベルで実行できます。

次のセクションでは、これらのさまざまなタイプについて詳しく説明します。

5. モード

ScalaCache は、キャッシュの同期モードと非同期モードの両方をサポートします Cats IO Monix Task Scalaz Task Tryなどの一般的なエフェクトタイプで結果をラップすることもできます。

5.1. 同期

同期モードは、データをキャッシュする最も簡単な方法を提供します。 この操作は、キャッシュが完了するまで現在のスレッドをブロックし、結果を返します。 インメモリキャッシュを使用している場合は、このモードを使用できます。 これを使用するには、以下をインポートする必要があります。

import scalacache.modes.sync._

5.2. 試す

試行モードでは、キャッシュ結果はscala.util.Tryでラップされます。 キャッシュ操作中にエラーが発生した場合、 ScalaCacheは結果をFailure:でラップします。

import scalacache.modes.try_._
def getUserTry(userId: Long): Try[User] =
  memoize[Try, User](None) {
    User(userId, "try-user")
  }
}

importステートメントでtryの後に、余分なアンダースコアがあることに注意してください。

5.3. 未来

ScalaCacheは、キャッシュ用にFutureベースの非同期APIもサポートしています。 キャッシュ操作は別のスレッドで実行され、Futureの結果を返します。 これには、スコープ内にExecutionContextが必要です。

import scalacache.modes.scalaFuture._
def getUserFuture(userId: Long): Future[User] = {
  memoize[Future, User](Some(10.seconds)) {
    User(userId, "future")
  }
}

5.4. エフェクトタイプAPI

前述のように、ScalaCacheは一般的なエフェクトタイプのライブラリもサポートしています。 これらはコアライブラリの一部ではなく、これらのEffect-Typeベースのキャッシュを使用するには、特定の依存関係を追加する必要があります。 たとえば、 CatsIO を使用するには、依存関係を追加する必要があります。

"com.github.cb372" %% "scalacache-cats-effect" % "0.28.0"

その後、関連するモードを使用して、CatsIOでデータをキャッシュできます。

implicit val mode: Mode[IO] = scalacache.CatsEffect.modes.async
def getUserCatsIO(id: Long): IO[User] = {
  memoize[IO, User](None) {
    User(id, "io-user")
  }
}

6. 手動キャッシュ操作

他のキャッシングライブラリと同様に、ScalaCache APIを使用して、基になるキャッシュから get put 、およびremoveデータを取得できます。

def getUser(userId: Long): User = {
  val cacheResult = get(buildUserKey(userId))
  if (cacheResult.isEmpty) {
    val fromDB = queryUserFromDB(userId)
    put(buildUserKey(userId))(fromDB)
    fromDB
  } else cacheResult.get
}

上記のコードは、最初にキャッシュからユーザーの詳細を取得しようとします。 使用できない場合は、データベースから読み取り、値をキャッシュに設定してから返します。

7. メモ化

ScalaCacheは、メソッド結果のキャッシュを管理するための非常に簡単な方法を提供します。 メモ化はメソッドの結果をキャッシュし、同じメソッドが同じ引数で呼び出されると、メソッドを実行する代わりに、キャッシュされた結果を使用します。 メソッド名と引数の値は、キャッシュキーを作成するために使用されます。

7.1. 同期メモ化

メソッドmemoizeSync()を使用して、同期メソッドの結果をメモ化できます。 これを機能させるには、ScalaCacheをスコープに暗黙的に設定し、同期メソッドをインポートする必要があります。

package com.baeldung.cache.service
import scalacache.modes.sync._
class SyncQueryMemoizeService {
  def getUser(userId: Long): User ={
    memoizeSync(Some(10.seconds)) {
      queryUserFromDB(userId)
    }
  }
}

メモ化されたメソッドにTTLを提供することもできます。 結果は指定された期間キャッシュされ、期間を超えるとキャッシュがクリアされます TTL 値を指定しない場合、ScalaCacheは結果を永久にキャッシュします。

上記のメソッドのキーは自動的に生成されます。 デフォルトのジェネレーターは、完全なクラスパス、メソッド名、およびパラメーター値を使用してキャッシュキーを作成します。

7.2. 効果のあるメモ化

メソッドがFutureやCatsIO などのエフェクトコンテナを返す場合は、 memoizeF()メソッドを使用します。 memoizeF()には2つのタイプパラメーターを指定する必要があります。 1つ目はコンテナタイプで、2つ目は実際の結果タイプです。

Futureを返すメソッドをキャッチしているとしましょう。

def getUser(userId: Long): Future[User] =
  memoizeF[Future, User](Some(10.seconds)) {
    queryUserFromDB(userId)
  }

ScalaCache は、結果が失敗したFutureの場合、データをキャッシュしません。 次の呼び出しでは、 Future が成功した場合でも、メソッドを実行してキャッシュします。

memoizeF の代わりに、memoizeメソッドを使用することもできます。 その場合、提供された ExecutionContext を使用して、別のスレッドでコードを実行します。

8. キャッシングブロック

特定のコードブロックのキャッシュを有効にすることもできますメモ化とまったく同じように機能します。 caching()メソッドを使用してキャッシングブロックを有効にできます。

def getUser(id: Long) = {
  caching("id", id)(None) {
    queryResult(id)
  }
}

上記のメソッドを値22で呼び出すと、ScalaCacheはキャッシュキーを id:22として自動的に生成します。 キー生成のキャッシュ方法には、任意の数のキーを与えることができます。

caching("id", id, "cache", "key")(None) {
  queryResult(id)
}

これにより、値 22のキャッシュキーがid:22:cache:keyとして生成されます。

memoizeF と同様に、 cachingF を使用して、タイプコンテナーで結果をキャッシュすることもできます。

def getUserFuture(id: Long) = {
  cachingF("keyF", id)(None) {
    queryResultFuture(id)
  }
}

9. キャッシュキーのカスタマイズ

メモ化操作用のキャッシュキーをカスタマイズできます。 デフォルトでは、ScalaCacheはextractClassConstructorParamsメソッドを使用してキャッシュキーを生成します。 その代わりに、カスタム実装を提供し、CacheConfigでジェネレーターを設定できます。

カスタムジェネレーターを作成するには、トレイト MethodCallToStringConverter を拡張し、 toString()メソッドをオーバーライドする必要があります。

object CustomKeyGenerator extends MethodCallToStringConverter {
  override def toString(
    fullClassName: String,
    constructorParamss: IndexedSeq[IndexedSeq[Any]],
    methodName: String,
    paramss: IndexedSeq[IndexedSeq[Any]]
  ): String = {
    val keyPart = paramss.map { methParams =>
      methParams.map(_.toString).mkString("_")
    }.mkString("-")
    methodName + "#" + keyPart
  }
}

次に、このカスタムキャッシュジェネレーターをキャッシュ構成で設定します。

val cache = CacheBuilder.newBuilder().maximumSize(10000L).build[String, Entry[User]]
implicit val customKeyCacheConfig = CacheConfig(memoization =
  MemoizationConfig(toStringConverter = CustomKeyGenerator)
)
implicit val guavaCache: Cache[User] = GuavaCache(cache)

customKeyCacheConfigが暗黙のパラメーターとして提供されていることに注意してください。 GuavaCacheインスタンスの作成時に使用されます。 この暗黙のインスタンスを提供しない場合、scalaCacheはデフォルトのキー生成を使用します。

10. 別のキャッシュへの切り替え

Guava以外の別のキャッシュを使用する必要がある場合は、非常に簡単です。 必要なキャッシングライブラリをインスタンス化し、ScalaCacheに提供する必要があります。 使い方をあまり変える必要はありません。 必要な唯一の変更は、基になるキャッシュに関連するインポートステートメントを提供することです。 汎用キャッシュサービスクラスを作成できます。

class GenericCacheService(implicit val cache: Cache[User]) {
  def getUser(userId: Long): User =
    memoizeSync(Some(10.seconds)) {
      //DB Querying logic goes here
      queryUserFromDB(userId)
    }
  }
}

これで、必要なキャッシュをスコープに暗黙的に含めることで、GenericCacheServiceのインスタンスを作成できます。 Guava キャッシュを使用するには:

import com.baeldung.cache.service.GuavaCacheMemoizationConfig.guavaCache
val guavaCacheService = new GenericCacheService()

GuavaからCaffeineに切り替えるには、ScalaCache-CaffeineにSBT依存関係を追加する必要があります。 次に、 a CaffeineCache 構成を作成し、暗黙の値として提供します。

import com.baeldung.cache.service.CaffeineCacheConfig.caffeineCache
val caffeineCacheService = new GenericCacheService()

11. 結論

この記事では、ScalaCacheと、それを使用してキャッシュ要件を簡単に処理する方法について説明しました。 また、ScalaCacheを使用したさまざまなタイプのキャッシュ操作や、他のキャッシュライブラリを使用する方法も確認しました。

いつものように、使用されるコードサンプルはGitHubから入手できます。