GraphQL API開発スピードを爆上げするAWS Amplify Mockingことはじめ

# 概要

[AWS Amplify Advent Calendar 2019](https://qiita.com/advent-calendar/2019/aws-amplify)、19日目はAWS Amplifyの新機能MockingでGraphQL API開発スピードを爆上げする方法について書いていきます！

## Amplify Mockingとは
2019.08.07に発表されたAmplify CLIの新(というにはいささか時間が経ってしまった)機能です。`$ amplify push`はAWS CloudFormationのスタックの参照と変更を行うため、それなりに時間がかかってしまいます。`$ amplify mock`コマンドを使用すると、`$ amplify push`でクラウドリソースに変更反映する前に、変更後の動作確認をローカル環境で行うことが可能です。

現在Mockingが提供されているCategoryは、以下の3つです。

- `api`(GraphQL): AWS AppSync + Amazon DynamoDB
- `storage`(Content): S3
- `function`: AWS Lambda

`storage`と`function`のMockingについては参考資料の共有に留め、本記事ではAPI(GraphQL)のMockingについて詳しく見ていきます。

## Amplify Mock Commands

```console:console
$ amplify mock
$ amplify mock api
$ amplify mock storage
$ amplify mock function <functionname>
```

`amplify mock`に続いてMockしたいCategoryを続けることでMockすることができます。`function`は一つのアプリケーションに複数の`function`を作成することができるため、Mockしたい`function`を明示的に渡します。Categoryを指定せず`amplify mock`を実行した場合は、`api`と`storage`のMockが始まります。


## 想定読者
`$ amplify add api`してAWS AppSyncいじってるけど、毎回`amplify push`の時間待つのだるいなぁ、とお考えの悩めるあなたに捧げます。途中Reactが出てきますが、一切触らないのでReactの知識は必要ありません。

## 動作確認環境
- react 16.12.0
- @aws-amplify/cli 4.5.0

## 参考資料
いくつか参考にした資料を載せておきます。Mockingを試すだけであれば、一番下の公式ドキュメントが早いです。

- [Mocking and Testing Serverless APIs with AWS Amplify - AWS Online Tech Talks - YouTube] (https://www.youtube.com/watch?v=OxrHplxZ8BA)
- [New – Local Mocking and Testing with the Amplify CLI | AWS News Blog](https://aws.amazon.com/jp/blogs/aws/new-local-mocking-and-testing-with-the-amplify-cli/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+AmazonWebServicesBlog+%28Amazon+Web+Services+Blog%29)
- [Developing and testing GraphQL APIs, Storage and Functions with Amplify Framework Local Mocking features | AWS Mobile Blog](https://aws.amazon.com/jp/blogs/mobile/amplify-framework-local-mocking/)
- https://aws-amplify.github.io/docs/cli-toolchain/quickstart?sdk=js#mocking-and-testing
- https://aws-amplify.github.io/docs/cli-toolchain/usage?sdk=js#mocking-and-testing



# 検証用アプリケーションの準備
## React App
本手順ではReactで進めますが、一切コードをいじらないので他のフレームワーク(Vue, Angular,Ionic)でアプリを作っていただいても大丈夫です。

```console:console
$ npx create-react-app amplify-react
$ cd amplify-react
$ npm start
```

ブラウザが立ち上がり、おなじみのスタートページが現れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532688/c26dd242-046a-818d-8ff1-59de8b19fd65.png)


## Amplifyの初期設定
`amplify init`でAmplifyの初期設定を行います。
`amplify configure`は一度実行している想定です。(`amplify configure`がまだの方は[コチラ](https://aws-amplify.github.io/docs/cli-toolchain/quickstart))
${profile name}にはご自身が使用されているAmplify用の[AWS Profile](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-profiles.html)名を入力してください。

```console:console
$ amplify init

? Enter a name for the project amplify-react
? Enter a name for the environment mocktest
? Choose your default editor: Vim (via Terminal, Mac OS only)
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using react
? Source Directory Path:  src
? Distribution Directory Path: build
? Build Command:  npm run-script build
? Start Command: npm run-script start
Using default provider  awscloudformation

For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html

? Do you want to use an AWS profile? Yes
? Please choose the profile you want to use ${profile name}
```
## GraphQL API作成
`$ amplify add api`でMockingの検証に使用するBackendを設定していきましょう。
ポイントとしては一度API KEYでセットアップした上で、追加でAmazon Cognito User PoolとAWS IAMを利用した認証を追加しています。これは後ほど`@auth`を利用した際のMockingを行うためです。画像を参考に設定してみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532688/c0769594-a3bf-2f9f-39e7-039bcd48df48.png)

準備完了です！Mockingを実際に使ってみましょう。

# API Mocking
## API Mockingを試してみよう！
先ほど`add api`したAppSyncの`schema.graphql`を編集しましょう。

```null:amplify/backend/api/amplifymocking/schema.graphql
type Todo 
@model 
@key(fields: ["id"])
@auth(rules: [
  {allow: owner, provider: userPools, operations: [create, read, update, delete]},
  {allow: private, provider: userPools, operations: [read]},
  {allow: public, provider: apiKey,operations: [read]},
])
{
  id: ID!
  name: String!
  owner: String!
  description: String
  updatedAt: AWSDatetime
  createdAt: AWSDatetime
}
```
`@auth`についての解説は拙著[Amplify CLI GraphQL TransformとディレクティブでAppSync+DynamoDBをいじってみよう！(@model @auth, @key) - Qiita](https://qiita.com/nagym/items/e5b9f43b11ef2309c546)をご覧ください。上記の`schema.graphql`における`@auth`は、以下の認可を実現します。

- Cognito User Poolで認証したユーザーに次のアクションを許可
    - Todoのcreate
    - 自身が作成したアイテムのread, update, delete
    - 他の人が作成したアイテムのread
- AppSyncのAPI_KEYで認証したユーザーにはreadのみを許可

ではこの設定をクラウドのリソースに反映する`$ amplify push`...の前に！`$ amplify mock api`してみましょう。

```console:console
$ amplify mock api
Failed to start API Mock endpoint Error: Type "AWSDatetime" not found in document.
```

`AWSDatetime`型なんて見つからないよ、というエラーが表示されました。[AppSyncのスカラー型](https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/scalars.html)によれば、正しくは`AWSDateTime`です。うっかり間違えてしまいました。`schema.graphql`を編集しましょう。

```null:amplify/backend/api/amplifymocking/schema.graphql
type Todo 
@model 
@key(fields: ["id"])
@auth(rules: [
  {allow: owner, provider: userPools, operations: [create, read, update, delete]},
  {allow: private, provider: userPools, operations: [read]},
  {allow: public, provider: iam, operations: [read]},
  {allow: public, provider: apiKey,operations: [read]},
])
{
  id: ID!
  name: String!
  owner: String!
  description: String
  updatedAt: AWSDateTime
  createdAt: AWSDateTime
}
```

`$ amplify mock api`を実行したままであれば、変更を検知して再読み込みしてくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532688/4b42e2b6-812e-8a92-232c-a74a0795ca8e.png)

今回はコンパイルが通りました！こうした細かな間違いを`$ amplify push`をせずに試せるのは嬉しいですね。
フロントエンドからGraphQLでクエリするためのコードを自動生成するため、いくつか聞かれます。全てデフォルトの選択肢で大丈夫です。

本来であればMock用のAPIエンドポイントが表示されるのですが、Error後に上記の設定を行うとAPIエンドポイントが表示されないようです。一度`Ctrl+C`で実行を止め、再度`$ amplify mock api`しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532688/f25dc5cd-e28c-bcf5-17ab-2ce6b6b9c1c8.png)

`http://192.168.1.6:20002`でGraphQLのMockサーバーが立ち上がりました！
`192.168.1.6`の部分は検証環境のネットワークでご自身のPC(またはサーバーなど)に割り当てられたローカルIPアドレスになります。

## API Mockingで作成されるもの
API Mockingが動かせたところで、`$ amplify mock api`した時に何が作られているのか確認して、全体感を抑えておきましょう。
[New – Local Mocking and Testing with the Amplify CLI | AWS News Blog](https://aws.amazon.com/jp/blogs/aws/new-local-mocking-and-testing-with-the-amplify-cli/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+AmazonWebServicesBlog+%28Amazon+Web+Services+Blog%29)によれば、`$ amplify mock api`によって以下のリソースが作成されます。

>
- the GraphQL transformations required by your API
- DynamoDB Local to manage your API data locally
- the Amplify GraphQL Explorer, based on the open source OneGraph graphiql-explorer plugin

- GraphQL transformatinos
    - `schema.graphql`からGraphQLで使用されるより詳細なschema、resolver、およびフロントエンドで使用するコードを生成
- [DynamoDB Local](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DynamoDBLocal.html)
    - SQLiteのDBをDynamoDB風のインタフェースでラップしたもので、Amplify GraphQL ExplorerのDBとして使用
    - SQLiteなのでSQLiteが読めるプラグインをいれて自由に閲覧・編集可能
- Amplify GraphQL Explorer
    - OSSの[OneGraph graphiql-explorer plugin](https://github.com/OneGraph/graphiql-explorer)をベースに開発された[GraphiQL](https://github.com/graphql/graphiql)(GraphQL IDEとも)プラグイン
    - HTTPによるクエリを受け付けるGraphQLエンドポイントと、クエリを手軽に試せるGUIを提供

紹介されていませんが、地味に便利なのが`aws-exports`をMockingの間だけlocalのMock API Endpointを指すように変更してくれる機能です。これについても後ほど確認します。

## Amplify GraphQL Explorer
前項で立ち上がったエンドポイントにアクセスしてみましょう。
`http://192.168.1.6:20002`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532688/e8cc133e-b988-35a0-ced5-48e1ef71fb8a.png)

Amplify GraphQL Explorerが立ち上がっています。いくつかアイテムを作成しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532688/ea3341e1-1c0d-9d50-e21e-3829d09db8e8.png)

1. + ADD NEW MUTATIONをクリック
2. createTodoをクリック
3. 紫文字のinputフィールドのうち、必須である`id, name, owner`を適当に埋めましょう(必須フィールドには`id*`のように`*`がついています)
4. 返り値として欲しいフィールドにチェックを入れます（今回は全て）

このように、GraphiQLのサポートのもと、非常に簡単に`mutation`を書くことができました。それでは実際に上部の▶ をクリックして実行してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532688/8a457775-ea37-1bfe-32e6-5cc15d719940.png)

右側に実行結果が表示されます。エラーがでました。メッセージを読むと`createTodo`にアクセスする権限がありませんと表示されます。
`schema.graphql`で書いた内容を思い出してみましょう。

>
- Cognito User Poolで認証したユーザーに次のアクションを許可
    - Todoのcreate
    - 自身が作成したアイテムのread, update, delete
    - 他の人が作成したアイテムのread
- IAMで認証したユーザーと、AppSyncのAPI_KEYで認証したユーザーにはreadのみを許可

右上を見ると、`Use: API Key`と表示されており、Amplify GraphQL Explorerがクエリを発行する際、認証情報としてAPI_KEYを使っていることがわかります。API_KEYで認証したユーザーには`read`権限しか与えていないため弾かれたのですね。Amplify GraphQL Explorerがちゃんと`@auth`を再現して認可をしていることがわかります。

では、`create`権限をもったCognito User Poolで認証したユーザーに切り替えてみましょう。
`Use: API Key`をクリックし、`Use: User Pool`に切り替えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532688/98f23852-8690-40cf-6b1f-0c26132dbc60.png)

続いて隣の`Update Auth`をクリックし、Auth Optionsを開きましょう。
UsernameとEmailに適当な値を入力し、`Generate Token`をクリックします。
UsernameはMutationのinputの`owner`と一致する必要があることに注意してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532688/7dd31422-2c2a-cfd6-e264-1682ec0abca2.png)

この状態で再度Mutationを実行してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532688/0ff95d13-5043-dee3-d1fe-30c5dc3cf7b2.png)

無事Mutationすることができました！

続けて別のユーザーに切り替えてみます。再度`Update Auth`をクリックしてユーザーの情報を書き換えましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532688/6278fcaf-4cc1-59c8-cbab-51f56acafe3e.png)
Generate Token後、先ほど他のユーザー作ったTodoにMutation(update)をしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532688/8c63c223-5ea8-e001-d3c8-d0895557926b.png)

失敗しますね、設定通りです。
最後にAPI_KEY認証に変え、`listTodo`してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532688/9f84f5b4-5d94-ec25-5499-3d8d71a956ed.png)

API_KEY認証で`read`できることが確認できました。

## ローカルのReactアプリからMockされたEndpointを叩く
Amplify CLIでは、 `$ amplify add {category}`して`$ amplify push`すると、Amplify Framework(SDK)から作成したリソースを叩くために必要な情報を`aws-exports.js`に吐き出してくれます。`aws-exports.js`の中身を使ってAmplifyの初期設定を行うとリソース変更や追加をしてもコンフィグを書き換える必要がなくなり、非常に楽です。
例えばJavaScriptでは、次のようにAmplifyの設定を行います。

```src/App.js
import awsmobile from './aws-exports';
import Amplify from 'aws-amplify';

Amplify.configure(awsmobile)
```

`aws-exports.js`の中身をみてみましょう。

```src/aws-exports.js
// WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.

const awsmobile = {
    "aws_project_region": "us-west-2"
};

export default awsmobile;
```

今回は一度も`$ amplify push`していないため、リソースに関する記述が一切ありません。この状態で`$ amplify mock api`するとどうなるのでしょうか。

```console:console
$ amplify mock api
```

でAPI Mockingしたまま、`aws-exports.js`を確認してみましょう。

```src/aws-exports.js
// WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.

const awsmobile = {
    "aws_project_region": "us-west-2",
    "aws_appsync_graphqlEndpoint": "http://10.221.93.66:20002/graphql",
    "aws_appsync_region": "us-west-2",
    "aws_appsync_authenticationType": "API_KEY",
    "aws_appsync_apiKey": "da2-fakeApiId123456",
    "aws_appsync_dangerously_connect_to_http_endpoint_for_testing": true
};

export default awsmobile;
```

__API Mockingをしている間だけ__、ローカルのGraphQL Endpointを指すように`aws-exports.js`を書き換えてくれるのです！便利！
ちなみにここではAPI_KEY認証を使っていますが、前述のAmplify GraphQL Explorerの`Update Auth`を行って`Use: User Pool`にしても`aws-exports.js`には反映されないようです。。。updateに期待しましょう。

ではAPI Mockingを`Ctrl+C`で抜けて`aws-exports.js`を確認してみます。

```src/aws-exports.js
// WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.

const awsmobile = {
    "aws_project_region": "us-west-2"
};


export default awsmobile;
```

空っぽの状態に戻っていますね。


# API Mocking Tips
API Mockingのちょっとした豆知識を紹介します。

## API Mockingに対応しているディレクティブ
https://aws-amplify.github.io/docs/cli-toolchain/usage?sdk=js#api-mocking-setup によれば、`$ amplify mock api`に対応しているディレクティブは執筆時点で次の5つです。`@searchable`と`@prediction`だけ対応していません。

```
- @auth
- @key
- @connection
- @versioned
- @function
```

## DynamoDB Localに破壊的な変更を加える

注)以下で紹介するテクニックは裏技的なもので、今後アップデートにより必要なくなったり、そもそも別の方法があったりするかもしれないものです。（他に方法があればぜひ教えてください！）最新情報はドキュメントやGitHubの確認をお願いします。

開発していると、DynamoDBにPK,SKの変更を伴う破壊的な変更をしたくなる場合があります。本番環境であれば[GSI(グローバルセカンダリインデックス)](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/GSI.html)の追加や、メンテナンスウィンドウを設けてのTable移行になるかと思います。重ねての紹介で恐縮ですが、AmplifyでGSIを使ってみようという内容も[前回のAmplify Advent Calendar記事](https://qiita.com/nagym/items/e5b9f43b11ef2309c546)で書いたのでよければご覧ください。

突如`id`でなく`name`フィールドでクエリしたい衝動に駆られました。`schema.graphql`を次のように変更しましょう。

```null:amplify/backend/api/amplifymocking/schema.graphql
type Todo 
@model 
@key(fields: ["name"])
@auth(rules: [
  {allow: owner, provider: userPools, operations: [create, read, update, delete]},
  {allow: private, provider: userPools, operations: [read]},
  {allow: public, provider: iam, operations: [read]},
  {allow: public, provider: apiKey,operations: [read]},
])
{
  id: ID!
  name: String!
  owner: String!
  description: String
  updatedAt: AWSDateTime
  createdAt: AWSDateTime
}
```

`$ amplify mock api`のログを見ると、普通に変更できていそうです。

```console:console
GraphQL schema compiled successfully.

...（中略）...

Creating table DataStore locally
Creating table TodoTable locally
Running GraphQL codegen
✔ Generated GraphQL operations successfully and saved at src/graphql
```

Amplify GraphQL Exploererはホットリロードされないので、一度リロードしてからMutationを実行してみましょう。
`Use User Pool`になっていることを確認してください。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532688/5de15381-d697-9b3c-ffa8-1443c5805244.png)

`DynamoDB:ValidationException`が発生しました。作成時のPK, SKのままのDynamoDB LocalにPK変換後のMutationを投げてしまったためです。（ちなみに`$ amplify push`の場合、DynamoDBのPK,SKの更新を伴う変更は、CloudFormation実行時に弾かれます。）
こうした場合、クラウドリソースであれば一度変更したいtypeをまるっとコメントアウトして`$ amplify push`してDynamoDB Tableを削除し、その後コメントアウトを解除して`$ amplify push`することで再度作成するというテクニックがあります。API Mockingの場合にはSQLiteのファイルを削除するとよさそうです。

```console:console
$ rm amplify/mock-data/dynamodb/fake_us-fake-1.db
$ amplify mock api
GraphQL schema compiled successfully.

Creating table DataStore locally
Creating table TodoTable locally
Running GraphQL codegen
✔ Generated GraphQL operations successfully and saved at src/graphql
AppSync Mock endpoint is running at http://10.221.93.17:20002
```

ログから、TodoTableが作成されたことがわかります。Amplify GraphQL Explorerを更新して、再度Mutationを実行してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532688/8e2ceac6-e999-1589-513c-65203e864548.png)

続いてQueryを実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532688/86ba2fba-7821-1f61-0886-9113f5af5913.png)

無事、新しいPKである`name`フィールドを使って`getTodo`できました。

# まとめ
- Mockingにより、__クラウドリソースに変更反映する前に__、変更後の動作確認をローカル環境で行うことが可能になります
- Mockingが提供されているCategoryはAPI(GraphQL)、Storage、Functionの三つ
- API Mockingが提供するのは次の四つです
    - GraphQL transformations
    - DynamoDB Local
    - Amplify GraphQL Explorer
    - aws-exports.jsの自動編集

Amplify Mockingによって開発スピードが大幅に向上した実感があります。細かい変更確認のために`$ amplify push`していたあの頃には戻れそうにありません。
ぜひAPI Mockingだけでなく、Storage、FunctionのMockingについても触ってみてください！

AWS Amplify Advent Calendar、明日は @rtaguchi さんです！