Node.jsのSession StoreとしてIBM Cloudantを使ってみる

# はじめに
Webアプリケーションではsessionを用いることが多いと思いますが、前回の記事で触れたように`express-session`のデフォルトの実装を用いる場合はメモリにsessionの情報を保持するため、アプリケーションの再起動等で情報は失われてしまいますし、アプリケーションのインスタンスが複数ある場合などに問題が起きるため、プロダクションでは外部のデータストアを用いることになると思います。この用途にはRedisが使われることが多いようですが、IBM Cloudでは(2022年1月現在)RedisにLiteプランがないため、最小構成でもそこそこのコストがかかることになり、実験には向かないようです。そこで今回はIBM Cloudantをsessionのストアとして用いることを検討してみます。

# IBM Cloudant

[IBM Cloudant](https://cloud.ibm.com/catalog/services/cloudant)はJSON document databaseです。IBM Cloudのコンソールから検索するなどしてトップページを開き、Liteプランのインスタンスを1つプロビジョンします。

![スクリーンショット 2022-01-04 23.47.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632713/9ad16049-c48b-77a2-307d-13278e15cf1e.png)

プロビジョンが完了したら管理画面に入れるようになります。サービス資格情報から接続用の資格情報を作成しておきます。

![スクリーンショット 2022-01-04 23.54.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632713/4c48a1b5-8ac3-c5c6-ac93-44d6832f1958.png)

Session情報を保存するデータベースとして`sessions`を作成しておきます。管理画面の`Launch Dashboard`をクリックしてCloudantの管理画面を開き、`Create Database`のボタンからデータベースを作成できます。

![スクリーンショット 2022-01-05 0.04.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632713/e48d60cb-9b7a-9830-98ad-3b5f5bc9e267.png)

# Node.jsでの構成

まずは `connect-cloudant-store`をインストールします。

```shell
$ npm install connect-cloudant-store --save
```

[前回](https://qiita.com/fterui/items/4e0103f1aeeca3c71e7a)のApp IDのcredentialsと同様、Cloudantのcredentialsもローカルでは`local.json`から、クラウド環境では環境変数から指定するように構成します。まず`local.json`に`cloudant`というキーで先ほどの資格情報をペーストしておきます。クラウド環境用に`custom-environment-variables.json`へも追加しておきます。

```json:config/custom-environment-variables.json
{
    "appid": {
        "__name": "APPID_CREDENTIALS",
        "__format": "json"
    },
    "cloudant": {
        "__name": "CLOUDANT_CREDENTIALS",
        "__format": "json"
    },
    "url": "APPLICATION_URL"
}
```
早速設定した資格情報を使ってsession storeを構成してみます。`app.js`を以下のように変更します。

```javascript:app.js
const CloudantStore = require('connect-cloudant-store')(session);
const Cloudant = require('@cloudant/cloudant');
const cloudant = Cloudant({
  url: config.get('cloudant.url'),
  plugins: {
    iamauth: {
      iamApiKey: config.get('cloudant.apikey')
    }
  }
});
const store = new CloudantStore({client: cloudant});
app.use(session({
  secret: '123456',
  resave: true,
  saveUninitialized: true,
  store
}));
```
`CloudantStore`のパラメータではIAMベースの認証を行うオプションがないので、先に`Cloudant`クライアントを作成してそれを渡すようにしました。次にsessionをちゃんと保持できているかの確認を容易に行うため、APIにカウンターをつけてみます。


```javascript:routes/users.js
router.get('/user', function(req, res, next) {
  const count = (req.session.count || 0)+1;
  req.session.count = count;
  res.json({
    login: !!req.user,
    given_name: req.user?.given_name,
    count
  });
});
```
そのカウンターをUIで表示する部分も追加しておきます。

```javascript:client/src/App.js
        <p>
          Hello {user.given_name || "Unknown User"}!
          Count: {user.count}
        </p>
```

これでUIをリロードすると、以下のように`Count: 1`が表示され、リロードするたびに増えていくのがわかると思います。
![スクリーンショット 2022-01-05 0.37.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632713/34555d86-6c3b-e464-e68d-5ad4533ac8d9.png)

Cloudantの管理画面から中身を見るとちゃんと`count: 1`が保持されているのがわかります。
![スクリーンショット 2022-01-05 0.31.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632713/7aa23374-53d4-cd9f-f6df-e949caa35952.png)
また、カウントを増やした状態で一度アプリケーションを再起動してもカウントが維持されていることがわかります。

ここまでで基本的な動作は確認できたので、sessionのTTLを60秒にセットしてexpireの挙動を確認します。

```javascript:app.js
const store = new CloudantStore({
  client: cloudant,
  ttl: 60
});
```
これでアプリケーションを再起動すると、1分以内にリロードしているうちはログイン状態も保持されカウントもアップしていきますが、1分以上経ってからリロードするとログイン状態も解除され、カウントも1に戻ることがわかります。

# IBM Cloud Code Engineでの動作確認

Cloudantをsession storeに使ったアプリケーションがCode Engine上で正しく動くかどうかを確認します。`ttl`を3600(1時間)にセットしてコードをGitHubにpushしておきます。

まずCloudantのcredentialsを前回のApp IDのcredentialsと同様、プロジェクトの「シークレット及びconfigmap」からシークレットとして保存しておきます。
![スクリーンショット 2022-01-05 9.21.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632713/44386910-6d74-35ea-a9cd-10f4f8a80628.png)
次にpushしたコードから新しいイメージをビルドしておく必要があるので、Code Engineのコンソールからプロジェクトを選択し、「イメージ・ビルド」から該当するビルドを選択し、「ビルドの実行依頼」をクリックしてしばらく待つと新しいイメージが作成されます。最後にアプリケーションの構成から「編集して新規リビジョンを作成」をクリックし、新しいイメージを使ったリビジョンを作成します。この時「環境変数」タブからCloudantのcredentialsを環境変数に取り込むように設定します。
![スクリーンショット 2022-01-05 9.24.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632713/0c75d1dd-7e73-a43e-a2e2-18ad564ce3f9.png)
リビジョンを作成してしばらく待つとデプロイが完了し、アプリケーションが使えるようになります。トップページにアクセスしてカウントが表示されていること、またログインした状態にしてしばらく放置します。数分経つとCode Engineのインスタンスは自動的に停止されます。
![スクリーンショット 2022-01-05 9.17.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632713/958baf29-99bd-8909-3ace-39c5de1dd2c1.png)
停止されたことが確認できたら、再度アプリケーションをリロードしてみます。この時はインスタンスが停止されているのでロードにしばらく時間がかかりますが、アプリケーションが起動してトップページが表示されると、ログイン状態やカウントが維持されているのがわかります。

# 注意点

IBM CloudantのLiteプランは、1秒当たりの読み書きの回数に制限があるので、プロダクションでの利用を検討する場合は、Standardプランや他のストアの利用を検討した方が良いです。

また、`@cloudant/cloudant`モジュールはdeprecatedなので`connect-cloudant-store`自体をプロダクションで使い続けるのはあまり得策ではないかもしれません。現状は後継である`clouding-node-sdk`を使ったsession storeの実装はないようなので、もしプロダクションで使う場合は自分で実装するなどの代替手段を取る必要がありそうです。

# まとめ

IBM Cloudantをsession storeとして使えることが確認できました。Liteプランなので一部制限はありますが、開発段階では問題なく利用できると思います。