AppSyncで複数のDynamoDBテーブルから情報を取得する

# 目的
AppSyncで複数のDynamoDBのテーブルから情報をつなぎ合わせて取得したい。

~~そもそもDynamoDBの設計としてどうなんだという話はあるかもですが~~
インデックスの関係もあり二つのテーブルに分ける必要がありそうだなという背景でした。
その際にテーブル間のjoinして結果をAppSyncのGraphQL queryから参照したいということを実現します。

```graphql
type Parent {
	id: String!
	name: String!
	children: [Child]!
}

type Child {
	id: String!
	name: String!
	parent: Parent!
}
```

# 実現したいこと
### リクエスト

```graphql
query listParents {
  listParents {
    items {
      id
      name
      children {
        items {
          id
          name
        }
      }
    }
  }
}
```

### レスポンス

```json
{
  "data": {
    "listParents": {
      "items": [
        {
          "id": "2",
          "name": "parent2"
        },
        {
          "id": "1",
          "name": "parent1"
        }
      ]
    }
  }
}
```

# 事前準備
## DynamoDBの準備
とりあえずさっくりDynamoDBテーブルを作る(セカンダリインデックス以降はデフォルト値です)
![parent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/6a1aea3b-2f19-d815-4064-07d3b15eb745.png)
![スクリーンショット 2021-03-06 19.20.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/0891c6ae-5bdc-4040-412c-8600edb46b72.png)

初期データとして雑にデータを投入
![スクリーンショット 2021-03-06 19.21.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/7b15b635-6557-d9b7-e376-c8bdeec50acb.png)
![スクリーンショット 2021-03-06 19.21.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/b088b420-ec58-93cb-5e75-d52510611bf2.png)

## AppSyncでAPIを作成する
DynamoDB テーブルをインポートするを選択
![スクリーンショット 2021-03-06 19.22.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/88498db4-2de9-6db5-c03f-6efaa4e92aba.png)

今回作成したテーブルを指定
![スクリーンショット 2021-03-06 19.22.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/bdbc2068-3260-5c95-ad42-8965d7eae7f0.png)

必要に応じてフィールドをたせます(今回は必須ステータスだけつけました)
![スクリーンショット 2021-03-06 19.22.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/08012689-aa7b-fa40-eae6-050a91cc0638.png)
![スクリーンショット 2021-03-06 19.22.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/281a06d5-6ddb-f0f0-04bd-abd9fe69cac9.png)

そんなこんなでぽちぽちするとあっという間にParentに対するGraphQL一式作成される！(すごすぎて引いた)
![スクリーンショット 2021-03-06 19.23.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/10d2d248-e88e-e2ca-040f-7cd58dd8b460.png)

ちょっと横道)
クエリ画面でlistParentsを実行すると確かに登録している情報が取得できた！

![スクリーンショット 2021-03-06 19.24.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/6ef2e996-0f6c-12ac-1eef-0a96e3df4b71.png)


### データソースの追加
今回は二つのテーブルを利用するのでデータソース画面からデータソースの追加を行います
![スクリーンショット 2021-03-07 16.30.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/15ca774f-59c4-c90b-a3af-41846a136b10.png)


データソースが追加されました
![スクリーンショット 2021-03-06 19.26.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/20345122-453e-da87-e597-ab114b0f89bc.png)

# スキーマ整理
今回はわかりやすくするためにgetParentを用意し、配下のchildrenは条件をつけて検索をできるようにしました。
Child -> parentの情報は不要ですが紐付けイメージのために用意しています

```graphql
type Child {
	id: String!
	name: String!
	parent: Parent!
}

type ChildConnection {
	items: [Child]!
	nextToken: String
}

type Parent {
	id: String!
	name: String!
	children(filter: TableChildFilterInput, first: Int, after: String): ChildConnection!
}

type Query {
	getParent(id: String!): Parent
}

input TableChildFilterInput {
	id: TableStringFilterInput
	name: TableStringFilterInput
}

input TableStringFilterInput {
	ne: String
	eq: String
	le: String
	lt: String
	ge: String
	gt: String
	contains: String
	notContains: String
	between: [String]
	beginsWith: String
}
```

![スクリーンショット 2021-03-07 15.40.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/40d5eb6a-d674-045a-36b2-3270661763f0.png)

# リゾルバーの用意
## Child#parentのリゾルバーを作成
schema画面の右側でChildのparentに対してリゾルバーを追加していきます
![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/4afdd8bf-4f0c-d0bc-e6b8-17198155d09b.png)

今回はParentを参照するのでデータソースでparentを指定
![スクリーンショット 2021-03-07 15.49.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/2ef8abc7-bd86-e54c-12de-5fd9556f814a.png)

![スクリーンショット 2021-03-07 15.49.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/adb923c7-5942-d73f-3f4c-17b5ec557df7.png)

`リクエストマッピングテンプレートを設定します。` にこちらを指定

```json
{
    "version": "2017-02-28",
    "operation": "GetItem",
    "key": {
        "id": $util.dynamodb.toDynamoDBJson($ctx.source.parent_id),
    }
}
```

`$ctx.source` は呼び元の `child` を指します。`child#parent_id` を `parent#id` のキーに条件指定して `GetItem` を実行します。

リゾルバーを保存することによってschemaにリゾルバーがアタッチされているのが確認できます。
![スクリーンショット 2021-03-07 15.53.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/1b45e1f1-a81d-e611-9e4e-8268b56c9d01.png)

## Parent#childrenのリゾルバーを作成
今度はデータソースにchildを指定します
![スクリーンショット 2021-03-07 15.56.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/ca0a3d6f-d38a-8703-65eb-5b9382ad4ca1.png)

右上にいくつかのテンプレートが用意されているので今回は `Simple query with pagenation` を選択
![スクリーンショット 2021-03-07 16.03.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/f73ab2de-6dad-30a7-1a75-2658ea920020.png)

中身を修正します

```json
{
    "version" : "2017-02-28",
    "operation" : "Query",
    "index" : "parent_id-index",
    "query" : {
        ## Provide a query expression. **
        "expression": "#parent_id = :parent_id",
        "expressionNames": {
          "#parent_id": "parent_id",
        },
        "expressionValues" : {
            ":parent_id" : {
                "S" : "${ctx.source.id}"
            }
        }
    },
    ## Add 'limit' and 'nextToken' arguments to this field in your schema to implement pagination. **
    "limit": $util.defaultIfNull(${ctx.args.limit}, 20),
    "nextToken": $util.toJson($util.defaultIfNullOrBlank($ctx.args.nextToken, null))
}
```

![スクリーンショット 2021-03-07 16.24.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280595/dbef31e2-d715-ffab-d039-c956f9b06e9a.png)

# 結論
複数のDynamoDBをまたいだリソースアクセスも割と簡単に行うことができました
リゾルバーを使うことによってかなり柔軟なGraphQLを実現できるのではという所管を得られました
とはいえまだまだ触り出したばかりで細かい部分で冗長な点がありましたらご教授いただけると幸いです :bow:

# 参考
https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/configuring-resolvers.html