momento cache sdkを触ってみる。

## はじめに

札幌で開催されたserverless meetup sapporoにて、momentoを触ったので、それ元に自身の知識を整理する記事です。

https://serverless.connpass.com/event/332724/

今回はTypeScriptを使って簡単にキャッシュの操作をしてみます。

前回はこちら。

https://qiita.com/s_moriyama/items/6c6060dbe74b83bd6f59

## 触ったみた！

### まずは完成形

キャッシュクライアントを作成し、キャッシュ保存・取得を速度測定と共に行なっているコードです。

なお、この記事用に書いたソースは以下にも格納しています。

https://github.com/SatoshiMoriyama/momento_handson_sdk/

```typescript
import {
  CacheClient,
  Configurations,
  CredentialProvider,
  CreateCache,
  CacheSet,
  CacheGet,
  CacheDictionaryFetch,
  CacheDictionaryGetField,
} from "@gomomento/sdk";

const DEFAULT_TTL = 600;

/**
 * 処理速度を計測するデコレータ
 */
function measureProcessingTime() {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: any[]) {
      const start = performance.now();
      const result = await originalMethod.apply(this, args);
      const end = performance.now();
      const processingTime = end - start;
      console.log(
        `${propertyKey}の処理時間: ${processingTime.toFixed(2)}ミリ秒`
      );
      return result;
    };

    return descriptor;
  };
}

class CacheManager {
  private client: CacheClient | null = null;

  /**
   * 処理速度を計測するデコレータ
   */
  @measureProcessingTime()
  async createCacheClient() {
    this.client = await CacheClient.create({
      configuration: Configurations.Laptop.v1(),
      credentialProvider: CredentialProvider.fromEnvironmentVariable({
        environmentVariableName: "MOMENTO_API_KEY",
      }),
      defaultTtlSeconds: DEFAULT_TTL,
    });
  }

  @measureProcessingTime()
  async createCache(cacheName: string) {
    if (!this.client) throw new Error("Client not initialized");

    const response: CreateCache.Response = await this.client.createCache(
      cacheName
    );
    console.log(`createCache:${response.type}`);
  }

  @measureProcessingTime()
  async writeString(cacheName: string, key: string, value: string) {
    if (!this.client) throw new Error("Client not initialized");

    const response: CacheSet.Response = await this.client.set(
      cacheName,
      key,
      value
    );

    console.log(`writeString:${response.type}`);
  }

  @measureProcessingTime()
  async writeDictionary(
    cacheName: string,
    key: string,
    value: Map<string, string>
  ) {
    if (!this.client) throw new Error("Client not initialized");

    const response = await this.client.dictionarySetFields(
      cacheName,
      key,
      value
    );
    console.log(`writeDictionary:${response.type}`);
  }

  @measureProcessingTime()
  async writeDictionaryItem(
    cacheName: string,
    directoryName: string,
    key: string,
    value: string
  ) {
    if (!this.client) throw new Error("Client not initialized");

    const response = await this.client.dictionarySetField(
      cacheName,
      directoryName,
      key,
      value
    );
    console.log(`writeDictionaryItem:${response.type}`);
  }

  @measureProcessingTime()
  async readFromCache(cacheName: string, key: string) {
    if (!this.client) throw new Error("Client not initialized");

    const response: CacheGet.Response = await this.client.get(cacheName, key);
    if (response instanceof CacheGet.Hit) {
      console.log("readFromCache: ", response.value());
    } else {
      console.log("readFromCache: ", response.type);
    }
  }

  @measureProcessingTime()
  async featchFromCache(cacheName: string, key: string) {
    if (!this.client) throw new Error("Client not initialized");

    const response = await this.client.dictionaryFetch(cacheName, key);
    if (response instanceof CacheDictionaryFetch.Hit) {
      console.log("featchFromCache: ", response.value());
    } else {
      console.log("featchFromCache: ", response.type);
    }
  }

  @measureProcessingTime()
  async getDirectoryItem(
    cacheName: string,
    directoryName: string,
    key: string
  ) {
    if (!this.client) throw new Error("Client not initialized");

    const response = await this.client.dictionaryGetField(
      cacheName,
      directoryName,
      key
    );
    if (response instanceof CacheDictionaryGetField.Hit) {
      console.log("getDirectoryItem: ", response.value());
    } else {
      console.log("getDirectoryItem: ", response.type);
    }
  }
}

async function main() {
  const cacheManager = new CacheManager();
  const cacheName = "sample-cache";

  // Cacheの作成
  await cacheManager.createCacheClient();
  await cacheManager.createCache(cacheName);

  // string型のValueのWrite/Read
  await cacheManager.writeString(cacheName, "string-key", "hoge");
  await cacheManager.readFromCache(cacheName, "string-key");

  // Dictionary型のValueのWrite/Read
  await cacheManager.writeDictionary(
    cacheName,
    "dic-key",
    new Map([
      ["key1", "value1"],
      ["key2", "value2"],
    ])
  );

  // valueのDirectoryに一つアイテムを追加
  await cacheManager.writeDictionaryItem(
    cacheName,
    "dic-key",
    "key3",
    "value3"
  );

  // Key-Valueの順序は不同
  await cacheManager.featchFromCache(cacheName, "dic-key");

  // valueのDirectoryの中のキーを指定して取得
  await cacheManager.getDirectoryItem(cacheName, "dic-key", "key2");
}

main().catch(console.error);

```

実行すると以下のような結果が得られます。
インターネット経由の確認ですが、レイテンシも少ないと思います。

```
createCacheClientの処理時間: 174.08ミリ秒
createCache:AlreadyExists
createCacheの処理時間: 88.47ミリ秒
writeString:Success
writeStringの処理時間: 26.49ミリ秒
readFromCache:  hoge
readFromCacheの処理時間: 20.75ミリ秒
writeDictionary:Success
writeDictionaryの処理時間: 27.44ミリ秒
writeDictionaryItem:Success
writeDictionaryItemの処理時間: 21.93ミリ秒
featchFromCache:  { key2: 'value2', key1: 'value1', key3: 'value3' }
featchFromCacheの処理時間: 27.01ミリ秒
getDirectoryItem:  value2
getDirectoryItemの処理時間: 20.55ミリ秒
```

では、各処理ごとにみていきます。

### 事前準備

今回はAPIキーを環境変数に定義するので、`MOMENTO_API_KEY`に自分の環境変数を設定

```console
export MOMENTO_API_KEY=[APIキーの内容]
```

### キャッシュクライアントの作成

credentialに環境変数`MOMENTO_API_KEY`を利用することを指定しています。

データの保持期間はTTLで指定します。

指定した名前のキャッシュクライアントが既に存在する場合は、`CreateCacheResponse.AlreadyExists`が返却されるみたいです。

```typescript
  @measureProcessingTime()
  async createCacheClient() {
    this.client = await CacheClient.create({
      configuration: Configurations.Laptop.v1(),
      credentialProvider: CredentialProvider.fromEnvironmentVariable({
        environmentVariableName: "MOMENTO_API_KEY",
      }),
      defaultTtlSeconds: DEFAULT_TTL,
    });
  }
```

### キャッシュの作成

次にキャッシュを作ります。
以降で紹介する各種キャッシュを保存する領域みたいなものを作るイメージです。

```typescript
  @measureProcessingTime()
  async createCache(cacheName: string) {
    if (!this.client) throw new Error("Client not initialized");

    const response: CreateCache.Response = await this.client.createCache(
      cacheName
    );
    console.log(`createCache:${response.type}`);
  }
```
### キャッシュへの登録（String)

momento chacheで保持できるデータの型は以下の定義の通りです。
今回は、簡単なStringとdictionaryを試してみました。

https://www.gomomento.com/faqs/what-type-of-data-can-i-store-in-a-cache/

まずは、stringです。

```typescript
  @measureProcessingTime()
  async writeString(cacheName: string, key: string, value: string) {
    if (!this.client) throw new Error("Client not initialized");

    const response: CacheSet.Response = await this.client.set(
      cacheName,
      key,
      value
    );

    console.log(`writeString:${response.type}`);
  }
```
```typescript
 await cacheManager.writeString(cacheName, "string-key", "hoge");
```

### キャッシュから取得（String)

キーの取得です。
（直感的に記載できるので、あまり書くこともないです。。）

```typescript
  @measureProcessingTime()
  async readFromCache(cacheName: string, key: string) {
    if (!this.client) throw new Error("Client not initialized");

    const response: CacheGet.Response = await this.client.get(cacheName, key);
    if (response instanceof CacheGet.Hit) {
      console.log("readFromCache: ", response.value());
    } else {
      console.log("readFromCache: ", response.type);
    }
  }

```
```typescript
await cacheManager.readFromCache(cacheName, "string-key");
// hoge
```

### キャッシュへの登録（dictionary)

dictionary型は`[["key1", "value1"],["key2", "value2"]]`みたいな、Key-Value型のストレージです。

ストレージを丸ごと保存することもできるし、ストレージの中に新しいキーを保存することもできます。

```typescript
  // 丸ごと保存
  @measureProcessingTime()
  async writeDictionary(
    cacheName: string,
    key: string,
    value: Map<string, string>
  ) {
    if (!this.client) throw new Error("Client not initialized");

    const response = await this.client.dictionarySetFields(
      cacheName,
      key,
      value
    );
    console.log(`writeDictionary:${response.type}`);
  }

  // 特定のキーのみに取得
  @measureProcessingTime()
  async writeDictionaryItem(
    cacheName: string,
    directoryName: string,
    key: string,
    value: string
  ) {
    if (!this.client) throw new Error("Client not initialized");

    const response = await this.client.dictionarySetField(
      cacheName,
      directoryName,
      key,
      value
    );
    console.log(`writeDictionaryItem:${response.type}`);
  }

```
```typescript
  await cacheManager.writeDictionary(
    cacheName,
    "dic-key",
    new Map([
      ["key1", "value1"],
      ["key2", "value2"],
    ])
  );

  await cacheManager.writeDictionaryItem(
    cacheName,
    "dic-key",
    "key3",
    "value3"
  );
```

### キャッシュから取得（dictionary)

dictionaryのキー取得は、順不同なのが注意点です。

またこちらも同様にストレージから特定のキーを指定し、取得することも可能です。

```typescript
  @measureProcessingTime()
  async featchFromCache(cacheName: string, key: string) {
    if (!this.client) throw new Error("Client not initialized");

    const response = await this.client.dictionaryFetch(cacheName, key);
    if (response instanceof CacheDictionaryFetch.Hit) {
      console.log("featchFromCache: ", response.value());
    } else {
      console.log("featchFromCache: ", response.type);
    }
  }

  @measureProcessingTime()
  async getDirectoryItem(
    cacheName: string,
    directoryName: string,
    key: string
  ) {
    if (!this.client) throw new Error("Client not initialized");

    const response = await this.client.dictionaryGetField(
      cacheName,
      directoryName,
      key
    );
    if (response instanceof CacheDictionaryGetField.Hit) {
      console.log("getDirectoryItem: ", response.value());
    } else {
      console.log("getDirectoryItem: ", response.type);
    }
  }
```
```typescript
  // Key-Valueの順序は不同
  await cacheManager.featchFromCache(cacheName, "dic-key");
  // { key3: 'value3', key1: 'value1', key2: 'value2' }

  // valueのDirectoryの中のキーを指定して取得
  await cacheManager.getDirectoryItem(cacheName, "dic-key", "key2");
  // value2
```

## 最後に

初めて書きましたが、とっても直感的に書くことができ、簡単に実装できそうです！

ぜひ業務に取り入れいたいところです！