24
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AWS Amplify フレームワークの使い方Part18〜GraphQL Transform v2 @hasOne/@hasMany/@belongsTo/@manyToMany (旧@ connection)編〜

Posted at

はじめに

非常に複雑だった@connectionがv2になってシンプルになって帰ってきました。
今回の変更で、よりAmplifyを始めやすくなったのではないでしょうか。

環境

@aws-amplify/cli 7.6.3

定義

今回、公式ドキュメントに定義としては記載がなかったので、ドキュメントを見て分かる範囲で記載しています。

directive @hasOne(fields: [String!])
directive @hasMany(indexName: String, fields: [String!])
directive @belongsTo(fields: [String!])
directive @manyToMany(relationName: String)

fields

fieldsを指定することで、指定したキーを使って紐付先のテーブルから情報を取得することができます。

indexName

@indexで設定したセカンダリインデックスを使って、データを取得することができます。indexNameがない場合は、紐付先のプライマリーキーでのデータ取得になります。。

relationName

manyToManyで自動生成されるテーブル名を指定します。

スキーマへの記載方法

以下のようにまとめていきます。

  • 1対1
  • 1対多
  • 多対多

1対1

fields指定なし / 双方向データ取得なし

graghql/schema.graphql
type Project @model {
  id: ID!
  name: String
  team: Team @hasOne
  # projectTeamId: ID! が自動生成
}

type Team @model {
  id: ID!
  name: String!
}
graghql/mutations.js
mutation CreateProject {
  createProject(input: {projectTeamId: "team-id", name: "Some Name"}) {
    id
    projectTeamId
    name
    team {
      name
      id
    }
  }
}

紐付け元に、${紐付元のテーブル名}${紐付先のテーブル名}Idが自動生成されます。
今回は、projectTeamIdという変数が自動生成され、create時にprojectTeamIdを保存することで、紐付けが完了します。

fields指定あり / 双方向データ取得なし

projectTeamIdの変数名を自分で指定したい場合は、以下のように記載します。
個人的には、fields指定ありの方が一手間はかかりますが、スキーマファイル内で明示的に記載できるので、後々わかりやすい気がしています。

graghql/schema.graphql
type Project @model {
  id: ID!
  teamId: ID!
  name: String
  team: Team @hasOne(fields: ["teamId"])
}

type Team @model {
  id: ID!
  name: String!
}
graghql/mutations.js
mutation CreateProject {
  createProject(input: { name: "New Project", teamId: "a-team-id"}) {
    id
    teamId
    name
    team {
      id
      name
    }
  }
}

fields指定なし / 双方向データ取得あり

fields指定なしで双方向からデータの取得がしたい場合は、以下のように記載します。

graghql/schema.graphql
type Project @model {
  id: ID!
  name: String
  team: Team @hasOne
  # projectTeamId : ID! が@hasOneによって自動生成
}

type Team @model {
  id: ID!
  name: String!
  project: Project @belongsTo
  # teamProjectId : ID! が@belongsToによって自動生成
}
graghql/mutations.js
mutation CreateProject {
  createProject(input: { name: "New Project", projectTeamId: "a-team-id"}) {
    id
    projectTeamId
    name
    team {
      id
      teamProjectId
      name
      project {
        id
        projectTeamId
        name
      }
    }
  }
}

fields指定あり / 双方向データ取得あり

fields指定なしで双方向からデータの取得がしたい場合は、以下のように記載します。

graghql/schema.graphql
type Project @model {
  id: ID!
  teamId: ID!
  name: String
  team: Team @hasOne(fields: ["teamId"])
}

type Team @model {
  id: ID!
  projectId: ID!
  name: String!
  project: Project @belongsTo(fields: ["projectId"])
}
graghql/mutations.js
mutation CreateProject {
  createProject(input: { name: "New Project", teamId: "a-team-id"}) {
    id
    teamId
    name
    team {
      id
      projectId
      name
      project {
        id
        teamId
        name
      }
    }
  }
}

1対多

fields指定なし / 双方向データ取得なし

graghql/schema.graphql
type Post @model {
  id: ID!
  title: String!
  comments: [Comment] @hasMany
}

type Comment @model {
  id: ID!
  content: String!
  # postCommentsId: ID! が自動生成
}
graghql/mutations.js
mutation CreatePost {
  createPost(input: {title: "Hello World!!"}) {
    id
    title
    comments {
      items {
        id
        postCommentsId
        content
      }
    }
  }
}

hasManyの紐付け先テーブル内に、${紐付け元のテーブル名}${@hasManyをつけた項目名}Idが自動生成されます。

上記の例では、commentを新規作成する場合は、必ずinputにpostCommentsIdを保存する必要があります。

fields指定あり / 双方向データ取得なし

紐付け用のIDを自動生成したくないときは、hasOneと同様にfiledを指定+indexNameを指定、@indexを利用することで変更できます。

graghql/schema.graphql
type Post @model {
  id: ID!
  title: String!
  comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"])
}

type Comment @model {
  id: ID!
  postId: ID! @index(name: "byPost", sortKeyFields: ["content"])
  content: String!
}
graghql/mutations.js
mutation CreatePost {
  createPost(input: {title: "Hello world!"}) {
    id
    title
    comments {
      items {
        postId
        content
        id
      }
    }
  }
}

fields指定なし / 双方向データ取得あり

これまでの流れだと、hasManyとbelongsToによって、postCommentsIdcommentPostIdの2つの変数が作成されそうですが、commentPostIdだけ生成されます。

graghql/schema.graphql
type Post @model {
  id: ID!
  title: String!
  comments: [Comment] @hasMany
}

type Comment @model {
  id: ID!
  content: String!
  post: Post @belongsTo
  # postCommentsId: ID がhasManyによって自動生成
}
graghql/mutations.js
mutation CreatePost {
  createPost(input: {title: "Hello World!!"}) {
    id
    title
    comments {
      items {
        id
        commentPostId
        content
        post {
          id
          title
        }
      }
    }
  }
}

fields指定あり / 双方向データ取得あり

こちらもお好みで、変数を指定したい場合は、fieldsを指定します。

graghql/schema.graphql
type Post @model {
  id: ID!
  title: String!
  comments: [Comment] @hasMany(indexName: "byPost", fields: ["id"])
}

type Comment @model {
  id: ID!
  postId: ID! @index(name: "byPost")
  content: String!
  post: Post @belongsTo(fields: ["postId"])
}
graghql/mutations.js
mutation CreatePost {
  createPost(input: {title: "Hello World!!"}) {
    id
    title
    comments {
      items {
        id
        postId
        content
        post {
          id
          title
        }
      }
    }
  }
}

多対多

@manyToManyを利用した場合

中間テーブルの作成を勝手にやってくれます。

graghql/schema.graphql
type Post @model {
  id: ID!
  title: String!
  content: String
  tags: [Tag] @manyToMany(relationName: "PostTags")
}

type Tag @model {
  id: ID!
  label: String!
  posts: [Post] @manyToMany(relationName: "PostTags")
}
# 実質的に以下をスキーマに記載した時と同じ動作になります。
# type PostTags @model {
#   id: ID!
#   postID: ID!
#   tagID: ID!
#   post: Post @belongsTo(fields: ["postID"])
#   tag: Tag @belongsTo(fields: ["tagID"])
# }
graghql/mutations.js
mutation CreatePost {
  createPost(input: {title: "Hello World!!"}) {
    id
    title
    content
    tags {
      items {
        id
        postID
        tagID
        tag {
          id
          label
          posts {
            items {
              post {
                id
                title
                content
              }
            }
          }
        }
        post {
          id
          title
          content
        }
      }
    }
  }
}

@manyToManyを利用しない場合/双方データ取得あり

現状、manyToManyで生成される中間テーブルに独自の項目を追加したり、アクセス権限を縛ったりはできません。(厳密にはやり方わからない。)
なので、とりあえずmanyToManyを手動でスキーマに書くと以下のようになります。

graghql/schema.graphql
type Post @model {
  id: ID!
  title: String!
  content: String
  tags: [PostTags] @hasMany(indexName: "byPost", fields: ["id"])
}

type Tag @model {
  id: ID!
  label: String!
  posts: [PostTags] @hasMany(indexName: "byTag", fields: ["id"])
}

type PostTags @model {
  id: ID!
  postId: ID! @index(name: "byPost")
  tagId: ID!  @index(name: "byTag")
  post: Post @belongsTo(fields: ["postId"])
  tag: Tag @belongsTo(fields: ["tagId"])
}
graghql/mutations.js
mutation CreatePost {
  createPost(input: {title: "Hello World!!"}) {
    id
    title
    content
    tags {
      items {
        id
        postId
        tagId
        tag {
          id
          label
          posts {
            items {
              post {
                id
                title
                content
              }
            }
          }
        }
        post {
          id
          title
          content
        }
      }
    }
  }
}

@manyToManyを利用しない場合/双方データ取得なし

地味に多対多で双方向データ取得不要なケースもあるので、それも記載しておきます。

graghql/schema.graphql
# 例:Postからのみ取得できればOKパターン
type Post @model {
  id: ID!
  title: String!
  content: String
  tags: [PostTags] @hasMany(indexName: "byPost", fields: ["id"])
}

type Tag @model {
  id: ID!
  label: String!
}

type PostTags @model {
  id: ID!
  postId: ID! @index(name: "byPost")
  tagId: ID!
  tag: Tag @hasOne(fields: ["tagId"])
}
graghql/mutations.js
mutation CreatePost {
  createPost(input: {title: "Hello World!!"}) {
    id
    title
    content
    tags {
      items {
        id
        postId
        tagId
        tag {
          id
          label
        }
      }
    }
  }
}

注意点/メモ

fieldsに指定できるのはstringのみ?

未検証ですが、@connectionの時は、fieldsに指定できるのはstringのみでしたので、おそらく今回もその可能性が高いと思っています。

その場合は、自分でカスタムリゾルバーを作成しましょう。

IdとID問題

v7.6.3では、自動生成されるidは、hasOne/hasMany/belongToはxxxId、manyToManyで生成されるテーブルのみxxxIDで生成されます。なぜこうなっているのかは、わかりませんが、注意が必要です。

indexName

未検証なので定義のところには記載しませんでしたが、hasOneやbelongToでも多分indexNameでindex指定できると思われます。また検証したら更新します。

おわりに

その昔issueで議論されていた内容が、気づいたら新機能として実装されていました。
これもAmplifyの開発は活発である証拠とかと思いますので、引き続き、今後のAmplifyの進化に期待です!

参考

24
13
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?