AppSync で DynamoDB リゾルバーを使ってみる

May 07, 2023

AppSync とは

AppSync は AWS のマネージドな GraphQL サービスであり、GraphQL API を作成するのに必要なバックエンドのリソースを提供してくれます。AppSync を使うことでユーザーがやらなければいけないことは以下のような点に絞られます。

  • スキーマの定義
  • リゾルバーの作成
  • データソースの作成

なお、リゾルバーは Lambda 関数を用いてカスタマイズすることが可能ですが、その他にも DynamoDB 、RDS 、OpenSearch といった AWS サービスに直接接続することもできるため、データハンドルが容易になります。
今回は AppSync の DynamoDB リゾルバーを用いて、前回 作成した ToDo アプリ用の GraphQL API を作っていきたいと思います。

AppSync API の作成

まずは AppSync API を作成していきます。AppSync コンソールから API を作成 ボタンを押し、Design from scratch を選択します。API 名等必要な情報を入力し、作成します。

f:id:shiro_kochi:2018××××××××:plain:w100:left

スキーマの作成

API の作成が完了したら、次にスキーマを作成します。ToDo アプリで利用したものをそのままこちらでも使うので、以下のスキーマを利用します。

Schema
schema {
  query: Query
  mutation: Mutation
}

type Query {
  listTodos: [Todo!]!
}

type Mutation {
  createTodo(data: CreateTodoInput!): Todo!
  updateTodo(id: ID!, data: UpdateTodoInput!): Todo!
  deleteTodo(id: ID!): Todo!
}

input CreateTodoInput {
  title: String!
  description: String!
  createDate: String!
}

input UpdateTodoInput {
  title: String!
  description: String!
  createDate: String!
}

type Todo {
  id: ID!
  title: String!
  description: String!
  createDate: String!
}

AppSync コンソールから先に作成した API を選択し、左ナビゲーションペインより スキーマ を選択します。以下の通りスキーマを入力し、スキーマを保存 ボタンを押せば完了です。

f:id:shiro_kochi:2018××××××××:plain:w100:left

データソースの作成

次に DynamoDB テーブル用のデータソースを作成します。これにより AppSync サービスがどのテーブルを参照すれば良いか判断ができるようになります。
左ナビゲーションペインの Data sources を選択し、データソースを作成 をクリックします。必要な情報を入力し、作成 を押して完了です。

f:id:shiro_kochi:2018××××××××:plain:w100:left

ちなみに、新規でロールを作成すると以下のようなポリシーがアタッチされます。

IAMPolicy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:UpdateItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:region:account-id:table/your-table-name",
                "arn:aws:dynamodb:region:account-id:table/your-table-name/*"
            ]
        }
    ]
}

リゾルバーの作成

それではいよいよリゾルバーを作成していきます。リゾルバーの作成はスキーマ定義画面にあるフィールド横の アタッチ から行います。

f:id:shiro_kochi:2018××××××××:plain:w100:left

AppSync のリゾルバーはリクエスト用及びレスポンス用のマッピングテンプレートで構成されます。これらにより、AppSync が受信したリクエストをバックエンドのデータソースへの指示に変換する方法と、そのデータソースからのレスポンスを GraphQL レスポンスへ、つまりクライアントが受け取るレスポンスへ変換する方法が提供されます。
また、AppSync のリゾルバーにはユニットリゾルバーとパイプラインリゾルバーの 2 種類があります。ユニットリゾルバーは単一のリクエストマッピングテンプレートとレスポンスマッピングテンプレートのみで構成されます。一方で、パイプラインリゾルバーでは単一のリクエストに対していくつかの関数を実行し、最終的にレスポンスを返すというような構成が取れます。これにより、シンプルなマッピングのみでは解決できない複雑なロジックを含むリゾルバーを作成できます。なお、今回はユニットリゾルバーを使用します。

f:id:shiro_kochi:2018××××××××:plain:w100:left

createTodo

まずは createTodo 用のリゾルバーを作成します。アタッチ をクリックします。するとデフォルトでパイプラインリゾルバー作成画面が表示されるので、右上の アクション から ランタイムを更新 を選択し、リゾルバータイプを Unit Resolver (VTL only) へ変更します。
データソースを選択する画面になるので、先に作成したデータソースを選択し、リクエストマッピングテンプレート及びレスポンスマッピングテンプレートを以下のように入力します。

RequestMappingTemplate
{
    "version" : "2017-02-28",
    "operation" : "PutItem",
    "key" : {
        "id" : $util.dynamodb.toDynamoDBJson($util.autoId())
    },
    "attributeValues" : {
      "title" : $util.dynamodb.toDynamoDBJson($ctx.args.data.title),
      "description" : $util.dynamodb.toDynamoDBJson($ctx.args.data.description),
      "createDate" : $util.dynamodb.toDynamoDBJson($ctx.args.data.createDate)
    }
}

$util.dynamodb.toMapValuesJson($ctx.args.data)

ResponseMappingTemplate
$util.toJson($ctx.result)

f:id:shiro_kochi:2018××××××××:plain:w100:left

それでは動作確認をしてみましょう。左ナビゲーションペインから クエリ を選択し、以下のクエリを実行します。正しくレスポンスが得られれば成功です。

Query
mutation CreateTodo {
  createTodo(data: {createDate: "2023-05-06", description: "From AppSync", title: "From AppSync"}) {
    title
    id
    description
    createDate
  }
}

f:id:shiro_kochi:2018××××××××:plain:w100:left

DynamoDB テーブル側にもデータが追加されたことがわかります。

f:id:shiro_kochi:2018××××××××:plain:w100:left

listTodos

次に listTodos 用のリゾルバーです。先と同様にユニットリゾルバーを以下のリクエスト・レスポンスマッピングテンプレートで作成します。

RequestMappingTemplate
{
    "version" : "2017-02-28",
    "operation" : "Scan"
}
ResponseMappingTemplate
$utils.toJson($ctx.result.items)

それでは実際に実行してみましょう。

f:id:shiro_kochi:2018××××××××:plain:w100:left

ちゃんとリストも取得できてそうですね。

updateTodo

updateTodo も同様に行います。

RequestMappingTemplate
{
    "version" : "2017-02-28",
    "operation" : "UpdateItem",
    "key" : {
        "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
    },
    "update" : {
        "expression" : "SET title = :title, description = :description, createDate = :createDate",
        "expressionValues": {
            ":title" : $util.dynamodb.toDynamoDBJson($ctx.args.data.title),
            ":description" : $util.dynamodb.toDynamoDBJson($ctx.arguments.data.description),
            ":createDate" : $util.dynamodb.toDynamoDBJson($ctx.args.data.createDate)
        }
    }
}
ResponseMappingTemplate
$util.toJson($ctx.result)

実際に動くところも確認しましょう。

f:id:shiro_kochi:2018××××××××:plain:w100:left

deleteTodo

最後は deleteTodo です。

RequestMappingTemplate
{
    "version" : "2017-02-28",
    "operation" : "DeleteItem",
    "key" : {
        "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
    }
}
ResponseMappingTemplate
$util.toJson($ctx.result)

実際にクエリ実行画面から削除してみます。

f:id:shiro_kochi:2018××××××××:plain:w100:left

listTodos で実際に消されていることも確認できます。

f:id:shiro_kochi:2018××××××××:plain:w100:left

以上でリゾルバーの作成は完了です。

エンドポイントを利用してみる

最後に AppSync から払い出される API エンドポイントを利用して動作の確認をしてみます。

$ curl -g \
-X POST \
-H "Content-Type: application/json" \
-H "x-api-key: your-api-key" \
-d '{"query": "query {listTodos {id title description createDate}}"}' https://your-api-id.appsync-api.us-west-2.amazonaws.com/graphql
{"data":{"listTodos":[{"id":"8295a0c5-81a0-40e2-97d1-89bd66041bf3","title":"Ask Cathy for the case update","description":"Haven't received any update on my L1 extension case for a while. Check with Cathy for the update. ","createDate":"2023-05-06"},{"id":"0386490d-23a8-4fc3-9009-646e23a9c234","title":"Pick up rice \"Tsuyahime\"","description":"Ordered \"Tsuyahime\" from Great Rice. \nNeed to pick it up at 10:00 am on Sunday. \nPick up location is 1424 Howell St","createDate":"2023-05-06"}]}}

良さそうですね。実際のアプリケーションではこのエンドポイントを使用して接続すれば良さそうです。


 © 2023, Dealing with Ambiguity