サーバーレスを利用した従量課金で動くシステム開発

（注意）投稿者があとで読み直したところ、記事の内容がふんわりし過ぎていると感じたので、後日書き直します。一時的に非公開にしたかったのですが、できなさそうなので注意書きで。。。

# はじめに
AWSにおいて、下記のサービスを組み合わせてシステムを開発すると従量課金で動くシステムが作成できます。理論上は。

- AWS Lambda (リクエスト回数および実行時間に応じた課金)
- Amazon API Gateway (APIコール回数および転送データ量に応じた課金)
- Amazon DynamoDB (書き込み読み込みリクエスト回数および利用ストレージに応じた課金)

しかし、オープンソースでないということもあるのか、DynamoDBを利用したシステム例をあまり見かけません。そこで、上記3サービスを利用してブログシステム（CMSっぽいなにか）を構築してみたので、感想をまとめます。

# ソースコード

https://github.com/tabikurage/slsblog

いわゆるフロントエンドエンジニアと呼ばれるようなことをやってなかったので、HTML書いたりCSS書いたりするのが大変でした......
まだまだ直す部分はありますが、とりあえず全体の思考だけでも先にQiitaにまとめます。

# 構成
adminpage（管理用ページの構築）、webpage（閲覧用ページの構築）、database（データ格納先の構築）の3つのフォルダが存在し、管理用ページと閲覧用ページは[Serverless Framework][4]を利用して構築、データ格納先はCloudFormationを利用して構築しています。

管理用ページと閲覧用ページを分けているのは、別ドメインにした方が若干セキュリティが上がるのではないかと考えたことと、サイト単位でbasic認証かけた方が楽ではないかと思ったからです。

データ格納先を分けているのは、開発の都合上スタックごとまるっと削除したいとき、データが消えるのが嫌だったからです。

## データ格納先の構築
データの格納先は、DynamoDBとS3を併用しています。CloudFormationのテンプレートを作成し、DynamoDBとS3バケットをまとめて構築しています。

DynamoDBのインデックスは、すべてGSIで貼り付けました。LSIだとDB構築後にインデックスを変更できない、BillingModeをPAY_PER_REQUEST(オンデマンドキャパシティー)にしてあるので各GSIごとにキャパシティプランニングをする必要がない、というのが理由です。
また、のちに機能を追加した際に、不要なインデックスが出てきた場合でも、GSIであればデータベースを削除せずにインデックスの削除ができるのも強みです。

S3は画像の保存用として利用しています。静的webサイトホスティングを利用し、API GatewayからHTTP_PROXYで読み込ませるようにします。

```yml:cloudformation.yml(一部抜粋)
  SLSBlogDB:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub ${ResourceName}-DB
      BillingMode: PAY_PER_REQUEST
       ...(省略)... 
      GlobalSecondaryIndexes:
        - IndexName: DateIndex
          KeySchema:
            - AttributeName: type
              KeyType: HASH
            - AttributeName: date
              KeyType: RANGE
          Projection:
            ProjectionType: ALL
        - IndexName: updateIndex
          KeySchema:
            - AttributeName: type
              KeyType: HASH
            - AttributeName: updateDate
              KeyType: RANGE
          Projection:
            ProjectionType: ALL
        - IndexName: createIndex
          KeySchema:
            - AttributeName: type
              KeyType: HASH
            - AttributeName: createDate
              KeyType: RANGE
          Projection:
            ProjectionType: ALL
```

## 閲覧用ページの構築

閲覧用ページ（ユーザーがWebサイトを訪問した際に表示されるページ）は`@vendia/serverless-express`を利用し、Expressで記述したものをlambdaに変換して表示させています。
以下のサイトが参考になりました。
https://zenn.dev/yuta_saito/articles/8b543a1957c375593ee5

閲覧用ページでこだわった点は以下の点です。

- Dynamo DB上にマークダウンで保存してある情報を変換して表示させる

### Dynamo DB上にマークダウンで保存してある情報を変換して表示

これは、変換するだけであれば`marked`を使用するだけです。ただ、見た目を調整するのがつらかったです。（CSSの知識がないだけ）

## 管理用ページの構築

開発中は、Cloud9のエディタでExpressをローカルで動かしながら開発し、デプロイ時に`@vendia/serverless-express`のパッケージを利用してLambdaで利用できる形式に変換しました。
以下のサイトが参考になりました。

https://zenn.dev/yuta_saito/articles/8b543a1957c375593ee5

実は、`@vendia/serverless-express`のパッケージを見つける前に、自力でいろいろなサイトを探しながらコードを書いたりしていました。例えば、BASIC認証をかけようとする際、最初は以下のサイトを参考にしていました。

https://dev.classmethod.jp/articles/apigateway-lambda-basicauth/

しかし、Expressを利用すると、以下のコードを書いて、app.jsから呼び出してあげるだけです。[(参考サイト)][3]

```javascript:auth.js
const auth = require('basic-auth');

module.exports = function (request, response, next) {
  const admins = {
    [process.env.BASIC_AUTH_USERNAME]: { password: process.env.BASIC_AUTH_PASSWORD },
  };
  const user = auth(request);
  
  if (!user || !admins[user.name] || admins[user.name].password !== user.pass) {
    response.set('WWW-Authenticate', 'Basic realm="example"');
    return response.status(401).send();
  }
  return next();
};
```

もちろん、basic-authというパッケージがいろいろやってくれているので、これだけ短いコードで済むのですが、サーバレスでも既存の資産を利用できるというメリットが大きいと感じました。
あと、Lambda側に機能を寄せられるので、APIGatewayではHTTP APIを利用して料金を節約できます。

# サンプルサイト
[閲覧用ページ][1]

https://cgrp0pn7tc.execute-api.ap-northeast-1.amazonaws.com

[投稿用ページ][2]（ユーザー名：username パスワード：password）

https://x48jgvi2p0.execute-api.ap-northeast-1.amazonaws.com

投稿用ページは荒らされないように、データベースへの書き込み権限を剥奪しています。
一定期間経ったら消す予定です。

# おわりに
同じような構成のシステムを作成しようと考えている方の参考になりましたら幸いです。

[1]:https://cgrp0pn7tc.execute-api.ap-northeast-1.amazonaws.com
[2]:https://x48jgvi2p0.execute-api.ap-northeast-1.amazonaws.com
[3]:https://qiita.com/dnrsm/items/aa92acd4e648b33599c7
[4]:https://www.serverless.com/