TwilioのRuntime Clientを使ってSyncをデータベースのように扱おう

:::note alert
2023年5月1日を持ちまして、株式会社KDDIウェブコミュニケーションズのTwilioリセール事業が終了したため、本記事に記載されている内容は正確ではないことを予めご了承ください。
:::

# はじめに

Twilioには、Functionsというサーバーレスアーキテクチャがあります。
https://jp.twilio.com/functions
これにより、ユーザ側にWebサーバーを立てなくても、大体のことはできるようになりました。
ただ、Twilioにはデータベースがないので、ちょっとした情報を保存して、別のFunctionsから参照したりすることが難しいです。
Assetsというストレージサービスもあり、テキストファイルなどをFunctionsから読み込むことはできますが、こちらも更新ができません。

そこで、データベースの代わりにTwilio Syncを使うという手があります。
https://jp.twilio.com/sync

Twilio Syncは元々、複数のデバイス間で情報を同期させるためのサービスで、たとえばコールセンターで現在誰が待機中かを知るような、いわゆるリアルタイム通信をするような時に利用されます。
このSyncでは、あるデバイスから受け取った情報を、一旦Twilio上に保存してから配信するようになっています。これをうまく利用することで、データベース的に利用してしまおうというのが、本記事の趣旨となります。

## Runtime Client

Runtime Clientは、Functionsから利用できる特別なオブジェクトです。
ドキュメントは以下にございます。
https://jp.twilio.com/docs/api/runtime/runtime-client

たとえば、Runtime Clientを使って、デフォルトのSyncサービス（アカウントごとに１つだけ、自動的に生成されています）を読み出すのは、以下のようなコードになります。
なお、実行するためには、FunctionsのConfigureページにある「Enable ACCOUNT_SID and AUTH_TOKEN」のチェックボックスを入れてください。

```lang:functions
exports.handler = function(context, event, callback) {
  
  Runtime.getSync()
    .fetch()
    .then(defaultSyncService => {
        console.log('Sync Service SID: ' + defaultSyncService.sid)
        callback(null, defaultSyncService.sid)
    })
    .catch(error => {
        console.log(error)
        callback(error)
    });
};
```

## Documentsオブジェクト

- 単一のJSON形式オブジェクト
- 1データのサイズは最大16KB
- 単純な値のPUB/SUBに向いている
- 履歴を取るような使い方には向いていない
- JavaScript SDKを使うと、Documentオブジェクトの生成、更新、購読、削除が可能
- サーバーサイドのREST API経由でも管理可能
- Documentsオブジェクトの利用例
 - コールセンターでオペレータの稼働確認などに利用

デフォルトサービス内にDocumentsオブジェクトを作成するには、以下のコードが参考になるでしょう。
このコードでは、すでに同じ名前のDocumentsオブジェクトがあればそれを更新し、ない場合は新規で作成するようになっています。

```lang:functions
exports.handler = function(context, event, callback) {
    let sync = Runtime.getSync();
    let payload = {
        name: 'test'
    };

    sync.documents("testDoc")
        .update({
            data: payload
        }).then(response => {
            console.log(response)
            callback(null, 'OK')
        }).catch(error => {
            sync.documents.create({
                uniqueName: "testDoc",
                data: payload
            }).then(response => {
                console.log(response)
                callback(null, 'OK')
            }).catch(error => {
                console.log(error)
                callback(error)
            })
        })
};
```

作成したDocumentsオブジェクトを読み込むには以下のコードを参考にしてください。

```lang:functions
exports.handler = function(context, event, callback) {
    Runtime.getSync().documents('testDoc')
        .fetch()
        .then(response => {
            console.log(response);
            callback(null, response.data)
    }).catch(error => {
        console.log(error)
        callback(error)
    });
};
```

## Listsオブジェクト

- 格納順が保証されたJSONリスト
- 時系列で確認したい履歴を取るような使い方に向いている
- JavaScript SDKを使うと、Listオブジェクトの生成、更新、購読、削除が可能
- サーバーサイドのREST API経由でも管理可能
- Listsオブジェクトの利用例
 - ログの保存などに利用

デフォルトサービス内のListsオブジェクトにデータを追加していくには、以下のコードが参考になるでしょう。
このコードでは、すでに同じ名前のListsオブジェクトがあればそれにデータを追加し、ない場合は新規で作成するようになっています。

```lang:functions
exports.handler = function(context, event, callback) {
  let sync = Runtime.getSync();
  let payload = {
    id: 'ABC',
    number: 123,
  };

  sync.lists.create({ // リストオブジェクトを生成
    uniqueName: 'list_of_data',
  })
  .then(response => { // 作成完了
    console.log(`Sync list created.`);
  })
  .catch(error => { // すでに存在
    console.log(`Sync list already exists.`);
  })
  .then(() => {  // アイテムの書き込み
    return sync.lists('list_of_data').syncListItems.create({
      data: payload
    });
  }).then(response => {  // 書き込み成功
    console.log(response)
    callback(null)
  }).catch(error => {  // 書き込み失敗
    console.log(error)
    callback(error)
  });
};
```

また、Listsオブジェクトの内容を表示させるには、以下のコードが参考になるでしょう。
eachループは非同期で動作するので、callbackの記述場所が難しいのですが、doneパラメータを活用することで解決できます。

```lang:functions
exports.handler = function(context, event, callback) {
  let sync = Runtime.getSync();
  sync.lists('list_of_data').syncListItems.each({
    done: function() {
      callback(null, 'Done.');
    },
  }, item => {
    console.log(item.data);
  });
};
```

## Mapオブジェクト

- 格納順が保証されないKey/Value形式のJSONデータ
- Keyで検索して、内容を記録するような使い方に向いている
- JavaScript SDKを使うと、Listオブジェクトの生成、更新、購読、削除が可能
- サーバーサイドのREST API経由でも管理可能
- Mapsオブジェクトの利用例
 - オペレータの内線番号やスキルなどの登録に利用

デフォルトサービス内のMapsオブジェクトにデータを追加、もしくは更新していくには、以下のコードが参考になるでしょう。
このコードでは、すでに同じ名前のMapsアイテムがあればそれにデータを更新し、ない場合は新規で追加するようになっています。

```lang:functions
exports.handler = function(context, event, callback) {
    let sync = Runtime.getSync();
    let key = 1;
    let payload = {
        'name': 'Yoshio Hayashi',
        'gender': 'male',
        'age': 40
    };
    
    sync.maps.create({
        uniqueName: "users"
    })
    .then(response => {
        console.log(`Maps created.`);
    })
    .catch(error => {
        console.log(`Maps exist.`);
    })
    .then(() => {
        return sync.maps('users').syncMapItems(key).update({
            data: payload
        });    
    })
    .then(response => {
        console.log(response);
        callback(null, 'updated.');
    })
    .catch(error => {
        console.log(`Item not exist.`);
    })
    .then(() => {
        return sync.maps('users').syncMapItems.create({
           key: key,
           data: payload
        });
    })
    .then(response => {
        console.log(response);
        callback(null, 'added.');
    })
    .catch(error => {
        console.log(error);
        callback(error);
    });
};
```

また、特定のMapアイテムの内容を表示させるには、以下のコードが参考になるでしょう。

```lang:functions
exports.handler = function(context, event, callback) {
    let sync = Runtime.getSync();
    let key = 1;

    sync.maps('users').syncMapItems(key).fetch()
    .then(sync_map_item => {
        console.log(sync_map_item);
        callback(null, 'found.');
    })
    .catch(error => {
        console.log(error);
        callback(null, 'Not found.');
    });
};
```

ちなみに、Twilio Syncに保存されているデータは、明示的に削除しない限り消えません（TTLパラメータを指定している場合を除く）。
気になる料金は[こちら](https://jp.twilio.com/sync/pricing)です。

ちょっとしたデータの保存や、サンプルプログラムの作成などでデータベースを外部に構築するまでもないようなケースではきっと役に立つはずです。