AWS Amplify を自分のために理解しなおす

# はじめに

AWS Amplify を使えば爆速でアプリが作れる！ といううたい文句は聞くが、いくつか記事を読んでみても Amplify とはどういうものかが正直ピンとこない。 そのため、ちゃんと Amplify を使ってみて、自分なりに理解することとした。 この記事は、その整理のために記載したものである。

Amplify では Mobile Application なども作れるが、今回実際に試してみた対象は「Web Application」であり、利用している各種ツールのバージョンは以下の通り。 将来的には改善される可能性も十分にあるが、記事執筆 (2021年2月10日現在) の話である。

```bash
$ npm -v
7.5.2
$ yarn -v
1.22.10
$ amplify version
4.41.2
```

# TL; DR

- AWS Amplify とは、数多くあるAWSサービスのインテグレーション（統合）サービスと考えるのが自分としては最もしっくりくる結論
  - 個別の要素を扱ったり、サービスを連携させようとすると、それらの知識に加え、IAMの知識が必要になる
  - 一方で、Web画面や Console を使ってこれら個々のサービスを隠蔽し、あたかも１つの「アプリケーションのサービス」を主眼とし、そのための部品として抽象化された AWSの各サービスを利用することができるようにしたものが AWS Amplify である、という認識
- Github などと連携した CI/CD 機能を備えており、これを簡単な設定で使える
- ただし amplify cli から使う場合と、amplify console (CI/CD) で使う場合でホスティングの機能が異なるため、Hosting (CloudFront周り) で細かい設定をしたい人には不向き
  - 普通に JavaScript アプリを作る分にはそのあたりは気にならないかもしれないが、個人的にはものすごく使いづらい仕様だった

# Amplify を採用するメリット

## AWS サービスの詳細を知ることなく、サービスを扱い、連携できる。

例えば、Rest API を作成しようとする場合、AWS のサービスを直に使おうとすると「API Gateway」「AWS Lambda」「(これらを動かす) AWS IAM」についてをある程度知った上でAWS上の設定を行い、これらをデプロイしなくてはいけない。 しかし、Amplify を利用することで、これらの細かいサービスを知ることなく「Rest APIを作成」「(Lambdaにデプロイされる)ソースコードを書く」「amplifyによって push (デプロイ)」する、という手順さえ知っていれば　Rest API を作成することができる。
ただ、Rest API だけにターゲットを絞れば、 別のフレームワークである Serverless Framework や Chalice などでも対応できる。 しかし、Amplify が強いのはそれ以外のリソースと連携でき、なおかつ、それらを同じグループの中で管理することができる点である。

具体的に対応可能なサービスとしては以下のものがある。 なお、全部に AWS IAM が、また、構築のデフォルト設定では CloudFormation が絡んでくるが、これは省略する。

| Amplify Resource Name | できること | 具体的な AWS Service | 備考(所感) |
| --------------------- | --------- | ------------------- | --------------- |
| API (GraphQL)         | GraphQL のエンドポイントを作って、CRUD操作を可能にする | AWS AppSync, DynamoDB |  |
| API (REST)         | HTTPS でアクセス可能なエンドポイントを作って、Lambda関数の実行を可能にする | API Gateway, AWS Lambda | GraphQLを使うことが推奨されている感じはする |
| Auth         | ユーザー認証を実施する | Cognito |  |
| Hosting         | 外部サーバーや S3 + CloudFront へのデプロイを行う | S3, CloudFront |  |
| Storage         | アプリケーション用の外部ストレージを生成して利用できる | S3 (+Cognitoによる認証が強制), DynamoDB | S3の場合、 Auth によってフロントエンドから直接Storageの管理を行えるような機能を提供する |
| Analytics   | ウェブサイトの分析 | Amazon Pinpoint |  |
| Notifications   | モバイルへの通知を実施する機能 | Amazon Pinpoint | Analytics のサブ機能なので、利用する場合は Analytics と同時利用になる |
| Interactions   | テキストや音声を分析して、botなどを作成する機能を提供 | Amazon Lex |  |
| Predications   | AWSのAI系サービスを利用した予測・分析サービス | Amazon Rekognition (画像処理), Amazon Transcribe (音声->テキスト変換), Amazon Polly (テキスト->音声変換), Amazon Comprehend (自然言語解析), Amazon Translate (翻訳) など | [参考URL](https://feature-ai-service.dma9ecr5ksxts.amplifyapp.com/amplify/predictions/) |
| XR | 3D、VRやARなどのアプリケーションレンダリングの変換機能を提供 | Amazon Sumerian | |

簡単に調べてみた結果、これらのうち、特に新しい機能 (PinpointやAI系各種) は Amplify との連携を前提に置かれているようで、使うときにはAmplifyによるアプリケーションを構築する方が楽そうには見える。 
ただ一方、これまでに培った細かいサービスの知識を使おうとすると、Amplify による設定が優先されてしまうため、それらをうまく使おうとして調べると時間がかかるという問題はあった。 このあたりの調査はデメリットに記載。

結論としては、つべこべ言わずに、このフレームワークに則った使い方をしろ！ ということだとは思うが、S3 Bucket を Web Hosting として開けたくなかったり、CloudFront で署名付きCookieによる閲覧制限を付けたいがためにマルチオリジンのアプリケーションを作ろうとしたりと、既存の知識をベースに色々とカスタマイズしたい点が出てきてしまう。
ただ、これは私が「すでにその仕様を知っていて、そのためにAWS上でどのようにすればよいのかを過去の経験に基づいて判断しているため」に考えてしまうことであって、こういった知識・経験がない場合は何も考えずにこれを使えるので、サービスの詳細を知らずに「アプリを作る」ということにフォーカスできるというのは強みではあると思う。

## CI/CD がサービスに含まれている

実際に本番リリースする場合、CI/CDは重要かつ構築がやや面倒くさいところもある。 しかし、amplify で作成したアプリケーションは Github や Code Commit などと連携して、ブランチの更新に対して CI/CD を走らせるような仕組みが自動的についてくる。
実際に使ってみたところ、以下の通り個人的な不満点はあった。

- Amplify の Web Console から「保存とデプロイ」を実施しようとすると、エラーメッセージも何も出ずに先に進まなくなった
  - 原因は S3AndCloudFront 種別の Hosting を追加していたこと。 これを追加していると CI/CD が通らない
  - エラーが出ないので、原因がなかなかつかめず調査に非常に時間がかかった
  - 元のサービスから Hosting を削除して再度実施すれば可能だった
- Amplify で実際に利用されるS3バケットやCloudFrontはこちらで見るサービスの一覧に表示されない
  - 言い換えると、この方法で実際に利用している S3 や CloudFront の各挙動を編集することができない
- 複数環境が設定できるにも関わらず Hosting 周りの挙動が１つしか定義できない
  - 例えば dev のホスティング周りを更新しようとすると、都度デプロイが必要になる (Hosting 設定は Amplify 側にあって `amplify status` で表示しても Hosting が表示されない。 そのため、Hosting 周りの更新は `amplify push` が通らない)
- S3AndCloudFront を hosting で利用している場合、CI/CD を利用できないのでパス毎に振り分けを行うような処理ができな
- ビルドに時間がかかる
  - デフォルトでも大体5分ぐらい
  - API で Python を使おうとすると、[ビルドの都度環境構築をする](https://qiita.com/Y_uuu/items/8867bfa3a13ea3406d89) ので余計に時間がかかる (事前にImageを作ってしまえば短縮はできるかもしれないが…)

これらの不満はあるものの、簡単な設定だけで本番にデプロイができるのは非常にメリットがあるとは感じる。 というか、正直、この機能を使うか使わないかで Amplify を採用するか否かが分かれる、ぐらいの印象である。

# Amplify を採用するデメリット

## Amplifyが対応できていないことをしようとすると、非常に手間がかかる

手間、というよりは「最終的にはこうしたいのだが、それを Amplify で実現する方法がわからない」と言ったほうが良いかもしれない。 これは、私を含めて、そもそも Amplify を使わずに AWS の各サービスを開発していた人間に当てはまると考えている。 発展途上のサービスとは言え、元々のサービスを使っていた人間からするとこれは Amplify を採用しない理由になりえる。
例えば、Amplify 以外でアプリケーションを使っているときに自分が使っている各サービスの機能で、Amplify CLI で素直に再現できなかったものには以下のようなものがあった。

### A: DynamoDB Table (Storage)

コマンド `amplify add storage` で DynamoDB リソースを構築する場合に Local Secondary Index (LSI) が指定できない。

- GraphQL の場合は方法がある ([Github](https://github.com/aws-amplify/amplify-cli/issues/1725), [Qiita](https://qiita.com/too/items/cb1dfb4f44536a3e9855))
  - が、後から変更する場合に色々と大変そう...
- 他にも、TTLカラムが指定できない、作成するテーブルの Billing Mode が指定できない (プロビジョニング R:5 / W:5で初期化される)、使わない場合でも DynamoDB Stream が有効、などがある
  - TTL は Table に一時データを格納する場合に削除を自動化してくれるありがたい機能なので、間違えて削除することが致命的なテーブル以外では基本 ON としておきたい
  - 少なくとも dev モードや、本番のワークロードがわからない場合、デフォルトで `PAY_PER_REQUEST` にしておきたいと考えているのだが、なぜ初期値が Provisionning の R:W = 5:5 なのだろうか
  - 単に生で DynamoDB のストレージを使わず、GraphQL で使うことが推奨、ということなのかもしれないが

### B: CloudFront + S3 (Hosting)

CloudFront の[署名付きCookieによる、特定パス以下のアクセスをCookieによって制御する](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-cookies.html)機能を使いたい。

- これは例えば、SPAの会員制サイトで `/private/` 以下はログインしていないと表示できない、といった機能を作るときに大いに役に立つ。 というのも、クライアントサイドの判断だと、ページを開いてからログイン状況を判断して表示を拒否するので、一瞬ページが見えてしまう。 しかし、この方法を使えば、アクセスの前段階で防げる
  - この場合、同じ Origin に対して、CloudFront で `/private/*` と `* (Default)` で異なる Behavior を設定する必要がある
- しかし [記事執筆時点では実装されていない](https://github.com/aws-amplify/amplify-js/issues/5896)
- どちらかというと Congito の Authentication で機能を制限する、という方向なのだろうか…？


### 問題A, Bの解消

これらは Amplify CLI では解決できなかった。 しかし、Amplify CLI によって管理されるコンテンツは CloudFormation でデプロイされるので、**コマンドによって CloudFormation 用のファイルを作って管理しているだけ** とみなすこともできる。

そこで、実際に調べてみると、DynamoDBの場合は `amplify/backend/storage/[リソース名]` 以下に DynamoDB 定義の CloudFormation ファイルが生成されている。 S3AndCloudFront の場合も `amplify/backend/hosting/S3AndCloudFront` 以下に CloudFormation ファイルが生成されている。

これらのファイルを CLI からでなく手作業で編集することで上記の問題には対処は可能。 CLIベースではなく、手作業でファイルを直接編集した場合でも、CLI側は「更新あり」とみなしてくれるため、編集後に CLI によるデプロイフローに乗せられる。 **ただし、S3AndCloudFront リソースの場合、先述した通り Console による CI/CD フローには載せられないため注意** 。

ただ、Amplify の出力する CloudFormation ファイルは JSON であり、これを手で編集するのがつらいので、YAML で出力してくれないかと試してみた。 結果、`template.json` を消去して `template.yaml` を配置してみたが、こうすると `amplify push` は通るが `amplify publish` が通らないという結果に。

Github を見る限り、[一部対応した動きをしていることは確認できたが、公式には現時点では未対応の模様](https://github.com/aws-amplify/amplify-cli/issues/1904)。 そこで、この Issue 内にもある通り、[cfn-flip](https://github.com/awslabs/aws-cfn-template-flip) を使って YAML version を作って編集 -> JSON に再コンバートしてこれを使う、のような方法を採ったほうがよさそうである。

#### 限定的別解: Import してくる (非推奨?)

すでに AWS 上にリソースがある場合、`amplify import storage` などでインポートしてくることが可能。 この場合、CloudFormation ファイルはローカルに作られず、ただ Import された、という情報だけを持つ。

ただし、この方法は現時点では推奨できない。 というのも、`env` で複数の環境を持っている場合でも、同一のリソースを参照してしまう挙動を見せるため。　DynamoDB のテーブルを Import することはできるが、開発・本番の両方で同じリソースを参照するのであれば、正直使いどころが…。

現時点では、あまりリソースの import は利用しないほうがよさそうに見える。 env を一つしか使わないならともかく、１つしか使わないということは Amplify の大きいメリットを１つ投げ捨ててるようなものに見えるので…


### C. ローカルでの稼働 (mock) に関する機能優先順位が低いため、複数人開発の難易度が高く見える

個人的に、開発するときは AWS にデプロイせずともローカル環境下で動作確認できる環境を作りたいと考えている。 しかし、現時点の amplify ではこれが限定的にしかできない。 具体的には、

- `amplify mock` の機能を使って、GraphQL の場合はローカルにモックエンドポイントを作成できる
- しかし、REST API を実装した場合は `amplify mock` による[モックエンドポイントの構築が未だ提供されていない](https://github.com/aws-amplify/amplify-cli/issues/3399)
  - 一応、`amplify mock function <function-name>` で関数そのもののローカル「実行」の検証は可能

という部分である。 ちなみに、Chalice であれば `chalice local` でローカル用のエンドポイントは構築できる。

ただ、[mock の記事を目に通す限り](https://dev.classmethod.jp/articles/aws-amplify-local-mock-feat/)、Cognite のトークンは本番からとってくる必要がある、など、完全にローカルで完結しているわけではなさそう…

複数人開発の場合ももちろん想定されているとは書かれている。
この場合、環境を作って、`env pull` で同期する、という風に書かれていたが、これを実施するということは「全開発者で同一の環境を共有する」ということに読める。 すると、環境が共有されているので以下のような問題があると考えている。

- デプロイが遅い
  - 単体テストなどの話でも出てくるが、１回の試行の時間が長くなり、開発ペースが保てない。 REST API を書き換える都度、AWSへのデプロイを行うとなると、CloudFormation のデプロイには数十秒はかかるので、簡単な変更をすぐに確認するというサイクルが乱れる
- DBの値を書き換えた場合などの問題が別の開発者にも影響を与える
  - 例えばバグによるデータの消去によって別の開発者の開発計画が狂ってしまう、ということが考えられる
- 開発環境でのコンフリクトが発生しうる
  - Console による CI/CD が有効な場合、ブランチ事に個別の環境を作る設定にできるので、個別に開発者がブランチを切れば個別の環境を作って検証できそう
- 開発に参加するために AWS IAM User (Programmic Access) の生成が必要になる
  - local にエンドポイントを立てて機能検証するだけであれば、本質的にはIAMは不要のはずだが、今の仕組みだと開発者すべてに IAM を生成して開発環境に設置する必要がある

# まとめ

AWS Amplify が推奨する作り方に乗っかるのであれば、デメリットは 

- REST API を使おうとしたときの `mock` の対応具合が悪い
- AWSの権限管理周り (いろんなリソースを使うので、Administratorアクセスを要求してる説明ページが多い) 

ぐらいかと思う。 前者は、推奨と思われる GraphQL による API を採択することで解消可能ではある。 

個人的には機能を クライアント・サーバーに分離して、権限のある部分は API 側の Lambda に投げて作りたい、とは考えている。 
しかし、もしかすると Client サイドでロジックを書いてしまい、データストアとしてだけバックエンドを使う、というような作り方を Amplify が推奨していて、そうすると REST API は使わなくてもよいのかもしれない。

その一方で Amplify がサポートしないことをしようとしたり、調べようとすると苦労する。 フレームワークあるあるではあるが、Amplify も同様の事象が発生する。

また、これは個人的な問題だが「Console CI/CDを使おうとすると、CloudFront系の挙動を操作できない」というのは非常に痛い。 メリットにも書いたが、この機能がやはり一番のメリットだと思えるので、これが使えないのは Amplify を採用する理由が１つ減った感じではある。

以上を踏まえて、この記事のまとめとして、Amplify を使うか否かの判断は **既存の AWS の構築・デプロイ周りの知識は一度捨ててから、Amplify のやり方に則った形でアプリケーションをデプロイすることに同意できる** か否かを基準とするとわかりやすい、と個人的には感じた。

重ねて書くが、ここに記載されているものは「記事執筆時点」の話であり、将来的には対応される可能性もあるため、将来に渡ってこれらがデメリットである、とは言っていないことは留意のこと。 また、調査不足によって、本来は解決できる問題を見落としている可能性もあるので、その場合は教えていただけるとありがたい。

# 補足 (どこにも関係ないもの)

検証中に起きた問題として、S3AndCloudFront の Hosting でデプロイしたにも関わらず、CloudFront の URL にアクセスすると、なぜか Web Hosting している S3 に飛ばされる問題があった。

これは Bucket を ap-northeast-1 に作成したため、DNS浸透が間に合わず、Origin からのアクセスに 307 を返していたため。 解決のためにはS3バケットのFQDNにリージョン情報を入れたオリジンを利用することですぐに解決できる他、24時間程度時間を置くことでも解決できる。 より細かい問題は以下のページが詳しい。

https://dev.classmethod.jp/articles/s3-cloudfront-redirect/