AWS Amplify フレームワークの使い方Part8〜GraphQL Transform @connection編〜

# 2022/1/4更新
GraphQL Transform V2から、`@connection`が`@hasOne/@hasMany/@belongsTo/@manyToMany`
に進化して使いやすくなっていますので、新しくプロジェクトを始める方は、以下の記事をご参考ください。

https://qiita.com/too/items/982dff46f9e70d5226d4

# はじめに
今回は、Amplifyで一対多や多対多を実現するのに必須である`@connection`について解説していきます。
2019年11月頃？に定義が変わって相当わかりやすくなりました。基本的には新定義を理解するだけで問題有りませんが、参考としてこの記事には旧定義も記載しています。

# 定義
まずは、定義から見ていきましょう。`@connection`について初めての方は、この定義解説は飛ばしてあとから読むことをおすすめします。


## 新定義
```
directive @connection(keyName: String, fields: [String!]) on FIELD_DEFINITION
```
### keyName
`@key`で設定したセカンダリインデックスを接続先に指定することができます。keyNameを指定しない場合は、接続するテーブルのプライマリーインデックスが指定されます。

### fields
fieldsに引数を指定することで、接続先のテーブルから情報を取得することができます。

## 旧定義
```
directive @connection(name: String, keyField: String, sortField: String, limit: Int) on FIELD_DEFINITION
```
### name
nameは宣言しなくても自動生成してくれますが、わかりにくい形になることも多いので、命名したほうがベターです。

### keyField
keyFieldを指定しない場合は、自動的にそのテーブルのプライマリインデックスが指定されます。keyFieldにの名前を指定することで、セカンダリインデックスを利用したテーブル間の紐付けが可能になります。

### sortField
sortFieldを設定することで、レコードのソート方法を設定できます。こちらもDynamoDB上では、ソートキーに設定されることで、ソート検索ができるようになります。createdAtでソートを掛けたい場合は必須です。

### limit
1回のget(list)で取得するデータの個数。設定しない場合は自動で10になります。

# 何ができるのか？
そもそも`@connection`を使って何ができるのか？っていう話を簡潔に。新定義のみ解説していきます。

## 1対1
`getUser `で所属するTeam情報も同時に取得できます。

```:schema.graphql
// 実際にDBに書き込まれるのは、id/teamId/nameの3つteamは取得用。

type User @model {
  id: ID!
  teamId: ID!
  name: String
  team: Team @connection(fields: ["teamId"])
}

type Team @model {
  id: ID!
  name: String!
}
```
```:graghql/queries.js
export const getUser = `query GetUser ($id: ID!) {
  getUser (id: $id) {
    id
    teamId
    name
    team {
      id
      name
    }
  }
}
```
以下の記載でも同様のことができます。
その場合、User情報をcreate時に、`userTeamId`という項目を作り、そこに所属するチームのIDを与えてあげる必要があります。

```:schema.graphql
type User @model {
  id: ID!
  name: String
  team: Team @connection
}

type Team @model {
  id: ID!
  name: String!
}
```
```:graghql/queries.js
export const getUser = `query GetUser ($id: ID!) {
  getUser (id: $id) {
    id
    name
    team {
      id
      name
    }
  }
}
```


## 1対多
`getTeam `でチームに所属するUserの情報一覧が取得できます。

```:schema.graphql
type User
  @model
  @key(name: "byTeam", fields: ["teamId"]) {
  id: ID!
  teamId: ID!
  name: String
  team: Team @connection(fields: ["teamId"])
}

type Team @model {
  id: ID!
  name: String!
  users: [User] @connection(keyName: "byTeam", fields: ["id"])
}

```
```:graghql/queries.js
export const getTeam = `query GetTeam($id: ID!) {
  getTeam (id: $id) {
    id
    name
    users {
      items {
        id
        name
      }
    }
  }
}
```
```:graghql/queries.js
export const getUser = `query GetUser ($id: ID!) {
  getUser (id: $id) {
    id
    name
    team {
      id
      name
    }
  }
}
```

## 多対多

ユーザーが複数のチームに所属する場合は、以下のようになります。

```:schema.graphql
type User @model {
  id: ID!
  name: String
  teams: [TeamGroup] @connection(keyName: "byUser", fields: ["id"])
}

type TeamGroup
  @model
  @key(name: "byTeam", fields: ["teamId"])
  @key(name: "byUser", fields: ["userId"])
{
  id: ID!
  userId: ID!
  teamId: ID!
  user: User @connection(fields: ["userId"])
  team: Team @connection(fields: ["teamId"])
}

type Team @model {
  id: ID!
  name: String!
  users: [TeamGroup] @connection(keyName: "byTeam", fields: ["id"])
}

```
```:graghql/queries.js
export const getUser = `query GetUser($id: ID!) {
  getUser (id: $id) {
    id
    name
    teams {
      items {
        id
        userId
        teamId
        team {
          id
          name
        }
      }
    }
  }
}
```
```:graghql/queries.js
export const getTeam = `query GetTeam($id: ID!) {
  getTeam (id: $id) {
    id
    name
    users {
      items {
        id
        userId
        teamId
        user {
          id
          name
        }
      }
    }
  }
}
```
# はまりポイント解説
理解するのに時間がかかったけれど大事なところをピックアップ。

## `@connetion`をつけたモデルの削除
`@connetion`をつけたモデルの削除をする場合は注意が必要です。削除はできますが、戻す時に下記エラーが出ることがあります。

```
REATE_FAILED      AnswerquestionResolver
AWS::AppSync::Resolver Fri Dec 13 2019 23:00:09 GMT 0900 (GMT 09:00)
Only one resolver is allowed per field. (Service: AWSAppSync; Status Code: 400;
Error Code: BadRequestException; Request ID: eefe6b89-2664-46a2-bf5a-435567035e15)
```
原因としては、削除したモデルに`@connection`で繋がっていたモデルが存在していた場合、その繋がりの記録がどこかしらに残っており、上書きできないので、エラーです、みたいな内容です。
こうなってしまうと、その両方のモデルを一度削除した上で、もう一度新しくpushし直す必要があり、`@connection`がいろんなモデルにクロスしているともはや再生不能に陥るので、モデルの削除には気をつけましょう。
以下でissueで話し合われています。

https://github.com/aws-amplify/amplify-cli/issues/682

## keyNameのfieldsはnon-null
keyNameのfieldsはnon-nullなので、「！」のつけ忘れに注意が必要です。
`@connection`関係なく、`@key`でGSIを設定する場合は、fieldsに指定するカラムがnullで問題ありません。

## fieldsに指定できるのはstringのみ
`@connection`の仕様上、fieldsに指定できるのはstringのみため、プライマリーキーがIntで宣言されているテーブルは取得することができないため、注意が必要です。

その場合は、以下の記事を参考に`@connection`ではなく、フィールドに対してカスタムリゾルバーを作成することで取得することができる、とAWSサポートから回答をもらいましたが、まだ手を付けてはおりません。

https://docs.amplify.aws/cli/graphql-transformer/resolvers/

# おわりに
新定義になり、とにかくわかりやすくなりました。初めはわかりづらいかもしれませんが、実践すればすぐになれると思います。
Amplifyは絶賛開発中に現在進行形でどんどん便利になっていっています。公式ドキュメントの定期なチェックは必須です。

# 参考
[公式ドキュメント](https://aws-amplify.github.io/docs/cli-toolchain/graphql?sdk=js)
[データアクセスパターン(公式)](https://aws-amplify.github.io/docs/cli-toolchain/graphql#data-access-patterns)


# 関連記事
[AWS amplify フレームワークの使い方Part1〜Auth設定編〜](https://qiita.com/too/items/fc961283dcbef3aafdeb)
[AWS Amplify フレームワークの使い方Part2〜Auth実践編〜](https://qiita.com/too/items/54992bb871fc1a2ab101)
[AWS Amplify フレームワークの使い方Part3〜API設定編〜](https://qiita.com/too/items/c0ce14d16772b9f71223)
[AWS Amplify フレームワークの使い方Part4〜API実践編〜](https://qiita.com/too/items/f9078803ea394c35df7d)
[AWS Amplify フレームワークの使い方Part5〜GraphQL Transform @model編〜](https://qiita.com/too/items/ab6b313da9f9e34be4a7)
[AWS Amplify フレームワークの使い方Part6〜GraphQL Transform @auth編〜]
(https://qiita.com/too/items/fae2879ea36f00c3ae10)
[AWS Amplify フレームワークの使い方Part7〜GraphQL Transform @key編〜](https://qiita.com/too/items/cb1dfb4f44536a3e9855)
[AWS Amplify フレームワークの使い方Part9〜Function 基礎編〜](https://qiita.com/too/items/d2732b3ec625011f4567)
[AWS Amplify フレームワークの使い方Part10〜Storage編〜](https://qiita.com/too/items/361bc10b6d27f133e9f8)
[AWS Amplify フレームワークの使い方Part11〜Function 権限管理編〜](https://qiita.com/too/items/b34cbfd7e96a01e33f23)
[AWS Amplify フレームワークの使い方Part12〜ENV編〜](https://qiita.com/too/items/5c1e28cf95fc6229547e)
[AWS Amplify フレームワークの使い方Part13〜Auth 設定更新編〜]
(https://qiita.com/too/items/52f35860bcb5bdf5e667)
[AWS Amplify フレームワークの使い方Part14〜Lambda レイヤー編〜]
(https://qiita.com/too/items/54de781085bd9a3a66d0)