私の戦闘力は53万です

awsとgcpについて書きます

AWS Appsync(GraphQL)の概要をRDB慣れした人向けに説明してみます

AWS Appsync(GraphQL)のDynamoDBチュートリアルを触ってみました。
チュートリアル : DynamoDB リゾルバー - AWS AppSyncdocs.aws.amazon.com

私はインフラよりの仕事が中心なので
多くのエンジニアと同じくRDBを触ったことがあるのですが、
GraphQLには馴染みがありませんでした。
と言うかGraphQL自体を初めて触りました。

そこで、同じ立場の人もいると思い、
RDBの感覚を含めてGraphQLの理解を書いてみたいと思います。 間違ってたらコメント頂けるとありがたいです。

GraphQLは何が良いのか

graphql.org

REST形式のAPIでは、一般的なアプリケーションで下記のようなことがありました。
* 欲しいレスポンスに応じてエンドポイントを複数用意する
* エンドポイント毎にリクエスト方式や、レスポンス内容等を理解する必要がある
* レスポンス内の一部の情報しか必要なくても決められた情報が全部返ってくる

GraphQLではこれらの点が改善されて使いやすくなっています。
* エンドポイントは1つのみ
* 欲しい情報のみをリクエストし取得できる

この違いを知ったときに、これってRDBに感覚近いのではと思いました。

上記以外にもまだまだメリットや変化点はあると思いますが、
詳しくはGraphQLのドキュメントを参照です。

簡単な例

こんな風にクエリを書いて

{
  me {
    name
  }
}

こんなレスポンスをもらえると言う感じです。

{
  "me": {
    "name": "Luke Skywalker"
  }
}

各用語の整理

GraphQLのマネージドサービスであるAppsyncの概要を整理してみます。

f:id:remmemento:20200519094110p:plain
概念を整理

各用語の説明は下記です。

GraphQL スキーマ

GraphQLで受け入れ可能なスキーマを定義します。
RDBとかでいうところのテーブル定義に近いと思います。
SQLでも存在しないテーブルにSelectしようとしたり、
key項目なしでInsertしようとしたらエラーになるように、
GraphQLでの受入可能なクエリを定義します。

データソース

データソースは、GraphQL API で操作できる AWS アカウント内のリソースです。
AWS AppSync は、AWS Lambda、Amazon DynamoDB
リレーショナルデータベース (Amazon Aurora Serverless)、
Amazon Elasticsearch Service、HTTP エンドポイントを
データソースとしてサポートしています

RDBで例えて言うと、各テーブルやviewでしょうか。
何の情報を、どのオブジェクトに持たせるのかを整理します。

ゾルバー

GraphQL スキーマとデータソースの関連づけを定義します。
どのリクエストを、どのデータソースと紐付けるかを設定します。

ここはRDBでうまく例えられませんでした。
新しい考え方として認識頂いた方が早いかもしれません。

ゾルバは下記4つのコンポーネントで構成されます

  1. ゾルバーをアタッチする、GraphQL スキーマ内の場所
    (どのスキーマとアタッチするか)

  2. ゾルバーで使用するデータソース
    (どのデータソースとアタッチするか)

  3. リクエスマッピングテンプレート
    (リクエスト内容をどう変換しデータソースへのリクエストするか)

  4. レスポンスマッピングテンプレート
    (レスポンス内容をどう変換するか)

多分リゾルバーが1番イメージの沸きにくいところなので、実際の設定画面をみてみます。
f:id:remmemento:20200519094515p:plain
この画面左がスキーマです。右側にResolverとありますが、ここの「アタッチ」ボタンで
スキーマとデータの紐付けを設定します。
f:id:remmemento:20200519094528p:plain この画面でデータソースを選択します。
リクエスマッピングテンプレートで、リクエストデータとスキーマをどのように紐付けるかを定義します。
f:id:remmemento:20200519094703p:plain レスポンスマッピングテンプレートでレスポンスデータとスキーマをどのように紐付けるかを定義します。

リクエスト実行の流れ

まだ分かりにくいと思いますのでリクエストを実行してみます。
Appsyncの画面からリクエストを実行可能なので流してみます。
ここではMutation(書き込みとそれに続く取得。RDBのinsert+selectに近い)を試してみます
f:id:remmemento:20200519094939p:plain

リクエス

mutation addPost {
  addPost(
    id: 123
    author: "AUTHORNAME"
    title: "Our first post!"
    content: "This is our first post."
    url: "https://aws.amazon.com/appsync/"
  ) {
    id
    author
    title
    content
    url
    ups
    downs
    version
  }
}

クライアントが上記のaddPostと言うクエリを投げます。

スキーマ

type Mutation {
    addPost(
        id: ID!,
        author: String!,
        title: String!,
        content: String!,
        url: String!
    ): Post!
}


type Post {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
}


type Query {
    getPost(id: ID): Post
}


schema {
    query: Query
    mutation: Mutation
}

スキーマで、送られてきたMutationのaddPostが含まれているかを確認します。
上記では定義されているため、AppsyncはaddPostを受入可能です。

マッピングテンプレート

{
    "version" : "2017-02-28",
    "operation" : "PutItem",
    "key" : {
        "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
    },
    "attributeValues" : {
        "author" : $util.dynamodb.toDynamoDBJson($context.arguments.author),
        "title" : $util.dynamodb.toDynamoDBJson($context.arguments.title),
        "content" : $util.dynamodb.toDynamoDBJson($context.arguments.content),
        "url" : $util.dynamodb.toDynamoDBJson($context.arguments.url),
        "ups" : { "N" : 1 },
        "downs" : { "N" : 0 },
        "version" : { "N" : 1 }
    }
}

まずは、Mutationを受け取り、リクエスト内容を読み替えます。

$util.dynamodb.toDynamoDBJson:
DynamoDB用のJSONエンコードしてくれる便利な書き方

$context.arguments:
リクエスト内容を参照する、RDBで言うプレースホルダ的な書き方

{
    "version" : "2017-02-28",
    "operation" : "PutItem",
    "key" : {
        "id" : { "S" : "123" }
    },
    "attributeValues" : {
        "author": { "S" : "AUTHORNAME" },
        "title": { "S" : "Our first post!" },
        "content": { "S" : "This is our first post." },
        "url": { "S" : "https://aws.amazon.com/appsync/" },
        "ups" : { "N" : 1 },
        "downs" : { "N" : 0 },
        "version" : { "N" : 1 }
    }
}

そして、Appsyncがリクエスマッピングテンプレートの定義に従い
GraphQLの形として下記に変換させます
上記では、operationがPutItemになっていますので
下記の内容がDynamoへのPutItemとして実行されます。

{
    "id" : "123",
    "author": "AUTHORNAME",
    "title": "Our first post!",
    "content": "This is our first post.",
    "url": "https://aws.amazon.com/appsync/",
    "ups" : 1,
    "downs" : 0,
    "version" : 1
}

そしてレスポンスマッピングテンプレートに
定義された項目がレスポンスとして返します。
下記は、そのまま返すの意味です。

$utils.toJson($context.result)

上記Mutationの流れにより、
DynamoへのPutItemが実行され、またその結果を得ることができます。
リクエスト側は、欲しい情報のみを記載するのと、 エンドポイントが複数にバラけないのが フロントエンド側の実装としては嬉しいですね。

チュートリアルは、上記以外ににも、データをDeleteするパターンとかがあり 非常に分かりやすく纏まっていますので、ぜひ気になる方は試してみてください。