Amplify GraphQL Transformer v2 @hasManyの実際と@indexを使ったカスタマイズ
はじめに
AWS Amplifyのバージョンが上がり、graphQLのスキーマからAppSyncのレゾルバや、フロントエンド用のAPI、GraphQLクエリを生成する機能のバージョンが上がりました。
GraphQL Transformer v2と呼ばれています(古いものはv1)
このあたりは、詳しい皆さんがまとめてくださっています。(いつもありがとうございます)
この記事ではタイトルの通り、新しく生まれた@hasManyディレクティブに関連して、手動でindexをはる方法の@indexディレクティブに触れながら
- SortKeyを設定して時系列順にソートできるようにする方法
- リレーションは使わずにクエリする方法
を記述します。
hasManyディレクティブの説明
上記リンク等ですでに理解している方は読み飛ばしていただいてOKです。
1対多のリレーションをはることができるディレクティブです。
type Blog @model
{
id: ID!
name: String!
posts: [Post] @hasMany
}
type Post @model
{
id: ID!
title: String!
}
今回のAmplifyの目玉機能はこのシンプルなhasManyディレクティブだけで、Blogからpostsを引くためのIdをPostテーブルに作成し、GSI(グローバルセカンダリインデックス)を作ってくれるという部分でした。このあたりはDynamoDBの設定と、AppSyncのレゾルバを自動で作ってくれていると理解すればよいと思います。
実際にcreatePostクエリでデータを入れてあげて、AWSのマネージドコンソールからDynamoDBの中身を見てみると・・・
{
"id": {
"S": "1b23932c-661d-4895-8f2e-df995765153c"
},
"__typename": {
"S": "Post"
},
"updatedAt": {
"S": "2022-01-14T10:10:38.390Z"
},
"createdAt": {
"S": "2022-01-14T10:10:38.390Z"
},
"blogPostsId": {
"S": "772460da-6d7a-4167-ad48-4fbb99d5a9ee"
},
"title": {
"S": "testBlog-testPost1"
}
}
このようにblogPostsIdというカラムが追加されていて(プロパティと言うべきなのか・・・?)対応するBlogレコードのIdがセットされています。
実際に下記のようなクエリを投げると、
query MyQuery {
getBlog(id: "772460da-6d7a-4167-ad48-4fbb99d5a9ee") {
createdAt
id
name
updatedAt
posts {
items {
blogPostsId
id
title
updatedAt
createdAt
}
}
}
}
下記のように値を引けています。
{
"data": {
"getBlog": {
"createdAt": "2022-01-14T09:26:46.428Z",
"id": "772460da-6d7a-4167-ad48-4fbb99d5a9ee",
"name": "testBlog",
"updatedAt": "2022-01-14T09:26:46.428Z",
"posts": {
"items": [
{
"blogPostsId": "772460da-6d7a-4167-ad48-4fbb99d5a9ee",
"id": "1b23932c-661d-4895-8f2e-df995765153c",
"title": "testBlog-testPost1",
"updatedAt": "2022-01-14T10:10:38.390Z",
"createdAt": "2022-01-14T10:10:38.390Z"
}
]
}
}
}
}
時系列でソートしたPostをとれるようにしたい
さて、ここからが本題ですが、上記のhasManyディレクティブで出来たGSIを改めてみてみると、
このようにソートキーが設定されていないことがわかります。
つまり、多数のデータを紐づけても時間でソートすることはできないとわかります。
実際に複数のデータを入れてデータを引いてみると、、、
query MyQuery {
getBlog(id: "772460da-6d7a-4167-ad48-4fbb99d5a9ee") {
posts(sortDirection: ASC) {
items {
id
title
createdAt
updatedAt
}
}
}
}
{
"data": {
"getBlog": {
"posts": {
"items": [
{
"id": "1b23932c-661d-4895-8f2e-df995765153c",
"title": "testBlog-testPost1",
"createdAt": "2022-01-14T10:10:38.390Z",
"updatedAt": "2022-01-14T10:10:38.390Z"
},
{
"id": "3b190941-2ec4-49d5-914a-312b7a5903d2",
"title": "BBB",
"createdAt": "2022-01-17T03:36:23.794Z",
"updatedAt": "2022-01-17T03:36:23.794Z"
},
{
"id": "84b42f46-1c80-4959-a789-94404b784d93",
"title": "testpost2",
"createdAt": "2022-01-15T08:30:08.024Z",
"updatedAt": "2022-01-15T08:30:08.024Z"
},
{
"id": "1ed5557b-e7bd-4b79-9eee-c302fa89e12e",
"title": "testpost3",
"createdAt": "2022-01-15T08:30:14.395Z",
"updatedAt": "2022-01-15T08:30:14.395Z"
},
{
"id": "f57dc0f7-b30b-4fdf-9d69-fdbc74aa89a1",
"title": "testpost4",
"createdAt": "2022-01-15T08:30:20.025Z",
"updatedAt": "2022-01-15T08:30:20.025Z"
},
{
"id": "09f5456b-1133-4a1c-b83d-e2a8e15352d8",
"title": "AAA",
"createdAt": "2022-01-17T03:36:11.559Z",
"updatedAt": "2022-01-17T03:36:11.559Z"
},
{
"id": "282e4347-cba2-4d57-9429-9ef8a0c78348",
"title": "CCC",
"createdAt": "2022-01-17T03:35:39.582Z",
"updatedAt": "2022-01-17T03:35:39.582Z"
}
]
}
}
}
}
このように時系列順には並びません。
ではソートできるようにするにはどうしたらよいかというと、
type Blog @model
{
id: ID!
name: String!
posts: [Post] @hasMany (indexName: "byBlogPostsId", fields:["id"])
user: User @belongsTo
}
type Post @model
{
id: ID! @primaryKey
title: String!
+ blogPostsId: ID @index(name: "byBlogPostsId", sortKeyFields: ["updatedAt"])
createdAt: AWSDateTime
updatedAt: AWSDateTime
}
このように、
- PostテーブルにBlogと紐づけるためのIdを追加して、@indexディレクティブでGSIを明示、この時にsortKeyFieldsプロパティを設定してsortKeyを指定
- BlogテーブルのhasManyディレクティブにindexNameプロパティとfieldsプロパティを設定
してあげます。
ちなみにblogPostsIdは元々のhasManyディレクティブで自動生成されたカラム名と同じにしました。
amplify push
してやると、特にエラーは出ずにアップデートされます。
これで、先のクエリを再度試すと・・・
{
"data": {
"getBlog": {
"posts": {
"items": [
{
"id": "1b23932c-661d-4895-8f2e-df995765153c",
"title": "testBlog-testPost1",
"createdAt": "2022-01-14T10:10:38.390Z",
"updatedAt": "2022-01-14T10:10:38.390Z"
},
{
"id": "84b42f46-1c80-4959-a789-94404b784d93",
"title": "testpost2",
"createdAt": "2022-01-15T08:30:08.024Z",
"updatedAt": "2022-01-15T08:30:08.024Z"
},
{
"id": "1ed5557b-e7bd-4b79-9eee-c302fa89e12e",
"title": "testpost3",
"createdAt": "2022-01-15T08:30:14.395Z",
"updatedAt": "2022-01-15T08:30:14.395Z"
},
{
"id": "f57dc0f7-b30b-4fdf-9d69-fdbc74aa89a1",
"title": "testpost4",
"createdAt": "2022-01-15T08:30:20.025Z",
"updatedAt": "2022-01-15T08:30:20.025Z"
},
{
"id": "282e4347-cba2-4d57-9429-9ef8a0c78348",
"title": "CCC",
"createdAt": "2022-01-17T03:35:39.582Z",
"updatedAt": "2022-01-17T03:35:39.582Z"
},
{
"id": "09f5456b-1133-4a1c-b83d-e2a8e15352d8",
"title": "AAA",
"createdAt": "2022-01-17T03:36:11.559Z",
"updatedAt": "2022-01-17T03:36:11.559Z"
},
{
"id": "3b190941-2ec4-49d5-914a-312b7a5903d2",
"title": "BBB",
"createdAt": "2022-01-17T03:36:23.794Z",
"updatedAt": "2022-01-17T03:36:23.794Z"
}
]
}
}
}
}
無事updatedAtでソートされました!
実際DynamoDBのGSIは下記のように新しいindexのモノに変わっています。
直接PostをBlogIdで引きたい
Blogテーブルの値は引かず、PostのデータだけBlogIdで引きたいケースもあるかと思います。
その場合は
type Post @model
{
id: ID! @primaryKey
title: String!
blogPostsId: ID @index(name: "byBlogPostsId", sortKeyFields: ["updatedAt"], queryField: "getPostsByBlogPostsId")
createdAt: AWSDateTime
updatedAt: AWSDateTime
}
このように@indexディレクティブにqueryFieldを追加すると、クエリができるようになります。
実際に
query MyQuery {
getPostsByBlogPostsId(blogPostsId: "772460da-6d7a-4167-ad48-4fbb99d5a9ee") {
items {
id
title
}
}
}
{
"data": {
"getPostsByBlogPostsId": {
"items": [
{
"id": "1b23932c-661d-4895-8f2e-df995765153c",
"title": "testBlog-testPost1",
"blogPostsId": "772460da-6d7a-4167-ad48-4fbb99d5a9ee"
},
{
"id": "84b42f46-1c80-4959-a789-94404b784d93",
"title": "testpost2",
"blogPostsId": "772460da-6d7a-4167-ad48-4fbb99d5a9ee"
},
{
"id": "1ed5557b-e7bd-4b79-9eee-c302fa89e12e",
"title": "testpost3",
"blogPostsId": "772460da-6d7a-4167-ad48-4fbb99d5a9ee"
},
{
"id": "f57dc0f7-b30b-4fdf-9d69-fdbc74aa89a1",
"title": "testpost4",
"blogPostsId": "772460da-6d7a-4167-ad48-4fbb99d5a9ee"
},
{
"id": "282e4347-cba2-4d57-9429-9ef8a0c78348",
"title": "CCC",
"blogPostsId": "772460da-6d7a-4167-ad48-4fbb99d5a9ee"
},
{
"id": "09f5456b-1133-4a1c-b83d-e2a8e15352d8",
"title": "AAA",
"blogPostsId": "772460da-6d7a-4167-ad48-4fbb99d5a9ee"
},
{
"id": "3b190941-2ec4-49d5-914a-312b7a5903d2",
"title": "BBB",
"blogPostsId": "772460da-6d7a-4167-ad48-4fbb99d5a9ee"
}
]
}
}
}
と、値を引くことができました。
おわりに
新しいschemaの書き方と実際の挙動を確認したので、記事にしてみました。
記事には書いていませんが、DynamoDBの値が消えかねない変化についてはちゃんとamplify push
時に警告が出るようになっており、悲しみを生まない形でよいなーと思っています。
また、今回のように自動で作ったテーブル構造から追記することで機能をアップデートできるのもよいなと思います。
各リレーションを作るディレクティブの実際については、公式ドキュメントのExamplesが参考になりました。
一度見てみると新しい気付きがあるかもしれません。
Discussion