概要
- Serverless 界隈でよく使われる GraphQL を触ってみた
- Backend を用意するのが面倒なので AWS AppSync を採用
- 簡単に構築できるように Terraform で書いた
サンプルコード
以下リポジトリを clone するとすぐに terraform で環境構築できます。
https://github.com/tsubasaogawa/terraform-appsync-graphql-test
Terraform v0.12.24 で動作確認
おさらい
GraphQL
- API によく使われる言語
- REST API と比べ
- リクエストの回数を減らしやすい (一度に複数の機能を呼び出せる)
- レスポンスされる項目を自由に指定できる
- スキーマ第一主義
- フロントエンドとバックエンドの認識齟齬を防ぐ
AppSync
- GraphQL の API サーバーをマネージドで提供してくれるサービス
- REST における API Gateway の GraphQL 版のようなもの
- データソース (API のデータ読み取り先) として AWS の各サービス (Dynamo/Elasticsearch/Lambda etc.) や HTTP が選択できる
- ノンプログラミングで DB からデータを読み書きする API が作れる
作るもの
- AppSync (GraphQL API)
- Query (SQL の SELECT に相当)
- user: ユーザー情報を DB から取得
- Mutation (SQL の UPDATE/INSERT/DELETE に相当)
- createUser: ユーザー情報を DB に書き込み
- Query (SQL の SELECT に相当)
- DynamoDB
- user の保存先
コード抜粋
ここでは AppSync の tf ファイルを掲載。variables は別ファイルで定義されている。
必要となる resource は以下の 4 つ。
- aws_appsync_graphql_api
- aws_appsync_api_key
- aws_appsync_datasource
- aws_appsync_resolver
# schema は別ファイルで定義しておく
data "local_file" "graphql_schema" {
filename = "modules/appsync/resources/schema.graphql"
}
resource "aws_appsync_graphql_api" "this" {
name = var.name
authentication_type = "API_KEY"
schema = data.local_file.graphql_schema.content
}
resource "aws_appsync_api_key" "this" {
api_id = aws_appsync_graphql_api.this.id
}
resource "aws_appsync_datasource" "this" {
api_id = aws_appsync_graphql_api.this.id
name = var.datasource_name[
# IAM Role も別ファイルで定義
service_role_arn = aws_iam_role.this.arn
type = "AMAZON_DYNAMODB"
dynamodb_config {
table_name = var.dynamo_table_name
}
}
# field の数だけ resolver を定義できるように map 型の resolvers 変数を使う
# https://github.com/tsubasaogawa/terraform-appsync-graphql-test/blob/c112fbd649ef80aeae2648a1fab21ff0bf64af6d/locals.tf#L13-L24
data "local_file" "resolver_requests" {
for_each = var.resolvers
filename = "modules/appsync/resources/resolver_templates/${each.key}/request.template"
}
data "local_file" "resolver_responses" {
for_each = var.resolvers
filename = "modules/appsync/resources/resolver_templates/${each.key}/response.template"
}
resource "aws_appsync_resolver" "this" {
for_each = var.resolvers
api_id = aws_appsync_graphql_api.this.id
field = lookup(each.value, "field")
type = lookup(each.value, "type")
data_source = lookup(each.value, "data_source")
request_template = lookup(data.local_file.resolver_requests, each.key).content
response_template = lookup(data.local_file.resolver_responses, each.key).content
}
実行
環境構築
# Setup
cd terraform-appsync-graphql-test
terraform init
terraform apply
成功すると AppSync のマネジメントコンソールで API が作成されていることが確認できる。
API テスト
下準備
ここでは作成された GraphQL API を curl で叩いてみる。
curl するために必要な API KEY とホスト名を terraform の output から取得する。
# Set environment variables from tfstate
API_KEY=$(cat terraform.tfstate | jq -r '.outputs.appsync_api_key.value')
HOST=$(cat terraform.tfstate | jq -r '.outputs.appsync_api_uris.value.GRAPHQL' | grep -oP '[^/]+\.amazonaws.com')
createUser でユーザーを作成
curl \
-H 'Content-Type: application/json' \
-H "x-api-key: $API_KEY" \
-H "Host: $HOST" \
-X POST -d '
{
"query": "mutation { createUser(name: \"yoshida\") { id name } }"
}' \
https://$HOST/graphql
{"data":{"createUser":{"id":"2e591d0b-c4cd-41bd-b66a-ad637e506f5f","name":"yoshida"}}}
yoshida
というユーザーが 2e591...
という ID で作成されたことがわかる。
user でユーザー情報を取得
先程作成した yoshida さんの情報を DB から取得する。ここでは name 属性のみを取得する。
curl \
-H 'Content-Type: application/json' \
-H "x-api-key: $API_KEY" \
-H "Host: $HOST" \
-X POST -d '
{
"query": "query { user(id: \"2e591d0b-c4cd-41bd-b66a-ad637e506f5f\") { name } }"
}' \
https://$HOST/graphql
{"data":{"user":{"name":"yoshida"}}}
必要な項目を API の利用者側が選択できるため、データのやり取りが軽量で済む。
備考/所感
AppSync の Resolver
AWS AppSync 特有の機能が Resolver であり、GraphQL の Field (本頁における user
/createUser
) のロジックを表現する。Request と Response を別々の mapping template ファイルとして定義する。
例えば user
の Resolver は以下のように定義されている。
- Request: DynamoDB に GetItem を発行する。
- Response: 得られた結果を JSON で返す。
Mapping template のリファレンスは以下にまとめられている。
https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/resolver-mapping-template-reference.html
学習コスト
上記の通り Resolver の考え方が若干複雑なのと、GraphQL の Schema における記法がこれまた独特であることから、REST API と比べると若干の取っ付きにくさ・学習コストの高さを感じる。ちょっとした API であれば素直に API Gateway を使うのが手っ取り早い。
※ なお、API Gateway であっても Service Proxy を用いることで Lambda レスで DynamoDB などの AWS リソースを扱うことができる。
https://dev.classmethod.jp/articles/api-gateway-aws-service-proxy-dynamodb/
一方、比較的大きい (育ちそうな) API であれば GraphQL のメリットをしっかり享受できるため、利用を検討してもいいかもしれない。