GuavaのLoadingCacheの実装サンプル

業務でguavaのLoadingCacheの実装をしたので、そのときのメモ。

## 3行
* Javaでキャッシュ実装を簡単に行うことができる
* `LoadingCache<K,V>`というKey-Valueでデータを保持することができる
* JVMでデータを保持しておくため、メモリの使いすぎには注意

## サンプル実装
SpringBootでローカル環境にREST APIサーバを構築します。

```terminal
$ curl http://localhost:8080/pokemon\?id\=150
{"id":150,"name":"mewtwo","weight":1220,"height":20}
```

上記でSpringBootにGETリクエストすると、SpringBootがpokeapiにアクセスして、指定されたidのポケモンデータを取得します。1度データを取りに行くと、SpringBootはそのデータをキャッシュとして保持します。

https://pokeapi.co/api/v2/pokemon/${id}

### Controllerの実装
`CacheBuilder`を使うことで、一度pokeapiにアクセスしたデータをキャッシュしておきます。キャッシュの保持期間やデータ数は`expireAfterAccess`や`maximumSize`で設定することができます。下記は`maximumSize=1`なので1つのデータしかキャッシュしません。

```kotlin
@RestController
class PokemonController() {
    private val log = LoggerFactory.getLogger(PokemonController::class.java)
    private val restTemplate = RestTemplate()

    // キャッシュ設定
    private val pokemonCache = CacheBuilder.newBuilder()
        .maximumSize(1)                                  // キャッシュするデータ数
        .expireAfterAccess(10, TimeUnit.SECONDS)         // キャッシュする期間
        .build(object: CacheLoader<Int, Pokemon>() {    // idがKeyでPokemonがValue
            @Throws(Exception::class)
            override fun load(id: Int): Pokemon {
                return getPokemon(id)                    // このメソッドの戻り値をキャッシュ
            }
        })!!

    @RequestMapping("/pokemon")
    fun findPokemon(id: Int): Pokemon {
        val stopwatch = Stopwatch.createStarted()
        val pokemon = pokemonCache.get(id)
        stopwatch.stop()
        log.info("Elapsed time: $stopwatch")
        return pokemon
    }

    fun getPokemon(@RequestParam id: Int): Pokemon {
        return restTemplate.getForObject(
            "https://pokeapi.co/api/v2/pokemon/$id", Pokemon::class.java)
    }
}
```

### 実行結果
strestを使って、本当にキャッシュが効いているか確認します。

1. id=151にアクセス（遅い。初回のアクセスのため）
2. id=151にアクセス（速い。151はキャッシュされている）
3. id=150にアクセス（遅い。150は初回アクセスのため）
4. id=151にアクセス（遅い。キャッシュは1データだけなので、151のキャッシュは効いてないから遅い）

```terminal           
$ yarn strest test.yml                                                    
                                                                          
✔ Testing request_151_1st succeeded (0.195s)                              
✔ Testing request_151_2nd succeeded (0.007s)                              
✔ Testing request_150 succeeded (0.129s)                                  
✔ Testing request_151_3rd succeeded (0.111s)                              
                                                                          
[ Strest ] ✨  Done in 0.462s                                             
                                                                          
✨  Done in 2.24s.                                                                                                                               
```

Kotlinの方のログはこんな感じ。

```
2019-02-23 21:41:51.117  INFO 16733 --- [nio-8080-exec-2] c.e.j.demo.controller.PokemonController  : Elapsed time: 162.0 ms
2019-02-23 21:41:51.138  INFO 16733 --- [nio-8080-exec-3] c.e.j.demo.controller.PokemonController  : Elapsed time: 40.15 μs
2019-02-23 21:41:51.268  INFO 16733 --- [nio-8080-exec-4] c.e.j.demo.controller.PokemonController  : Elapsed time: 122.7 ms
2019-02-23 21:41:51.379  INFO 16733 --- [nio-8080-exec-5] c.e.j.demo.controller.PokemonController  : Elapsed time: 104.9 ms
```

## キャッシュはJVMに保持される
なので調子に乗ってキャッシュ化しすぎると、メモリ容量を溢れる可能性があります。そういった場合は`maximumWeight`でキャッシュのデータサイズを制限できるみたい。単位はbyteかな。

## APIドキュメントはここ
`Interface LoadingCache<K,V>`
https://google.github.io/guava/releases/27.0.1-jre/api/docs/com/google/common/cache/LoadingCache.html

`Class CacheBuilder<K,V>`
https://google.github.io/guava/releases/27.0.1-jre/api/docs/com/google/common/cache/CacheBuilder.html


# 参考
https://www.baeldung.com/guava-cacheloader
https://github.com/google/guava/wiki/CachesExplained
http://www.codingpedia.org/ama/java-cache-example-with-guava