🍣

Amplify GraphQL Transformer v2 @hasManyの実際と@indexを使ったカスタマイズ

2022/01/17に公開

はじめに

AWS Amplifyのバージョンが上がり、graphQLのスキーマからAppSyncのレゾルバや、フロントエンド用のAPI、GraphQLクエリを生成する機能のバージョンが上がりました。
GraphQL Transformer v2と呼ばれています(古いものはv1)
このあたりは、詳しい皆さんがまとめてくださっています。(いつもありがとうございます)
https://zenn.dev/jaga/articles/dc045c6918b11b
https://qiita.com/too/items/982dff46f9e70d5226d4

この記事ではタイトルの通り、新しく生まれた@hasManyディレクティブに関連して、手動でindexをはる方法の@indexディレクティブに触れながら

  • SortKeyを設定して時系列順にソートできるようにする方法
  • リレーションは使わずにクエリする方法

を記述します。

hasManyディレクティブの説明

上記リンク等ですでに理解している方は読み飛ばしていただいてOKです。
1対多のリレーションをはることができるディレクティブです。

simple schema
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の中身を見てみると・・・

Post Table Data
{
  "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がセットされています。
実際に下記のようなクエリを投げると、

getBlogクエリ
query MyQuery {
  getBlog(id: "772460da-6d7a-4167-ad48-4fbb99d5a9ee") {
    createdAt
    id
    name
    updatedAt
    posts {
      items {
        blogPostsId
        id
        title
        updatedAt
        createdAt
      }
    }
  }
}

下記のように値を引けています。

getBlogの返り値
{
  "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を改めてみてみると、

このようにソートキーが設定されていないことがわかります。
つまり、多数のデータを紐づけても時間でソートすることはできないとわかります。
実際に複数のデータを入れてデータを引いてみると、、、

getBlogクエリ
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
}

このように、

  1. PostテーブルにBlogと紐づけるためのIdを追加して、@indexディレクティブでGSIを明示、この時にsortKeyFieldsプロパティを設定してsortKeyを指定
  2. BlogテーブルのhasManyディレクティブにindexNameプロパティとfieldsプロパティを設定
    してあげます。

ちなみにblogPostsIdは元々のhasManyディレクティブで自動生成されたカラム名と同じにしました。
amplify pushしてやると、特にエラーは出ずにアップデートされます。

これで、先のクエリを再度試すと・・・

index設定後の返り値
{
  "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で引きたいケースもあるかと思います。
その場合は

graghql/schema.graphql
type Post @model
{
  id: ID! @primaryKey
  title: String!
  blogPostsId: ID @index(name: "byBlogPostsId", sortKeyFields: ["updatedAt"], queryField: "getPostsByBlogPostsId")
  createdAt: AWSDateTime
  updatedAt: AWSDateTime
}

このように@indexディレクティブにqueryFieldを追加すると、クエリができるようになります。
実際に

getPostsByBlogPostsIdクエリ
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が参考になりました。
一度見てみると新しい気付きがあるかもしれません。
https://docs.amplify.aws/cli/graphql/examples-and-solutions/

Discussion