見出し画像

今から始めるServerless Frameworkでサーバーレスデビュー【マッピングテンプレート番外編#5】

番外編としてマッピングテンプレート編になります。
マッピングテンプレートはServerless Frameworkとは直接的な関係はありませんが、AppSyncに関連するものです。
マッピングテンプレートはVTL(Velocity Template Language)と呼ばれる言語で記述されます。

このデータソースには様々なものが存在します。
例えば、DynamoDBやLambda、Elasticsearchなどがあります。

AppSyncは各データソースとリクエストとレスポンスを介して処理を行うために必要不可欠な存在です。
GraphQLを使用してデータベースへの書き込みや削除などのCRUD操作を行う際に、「特定の条件下では特定の方法でデータを保存する」といった動作を設定するためにマッピングテンプレートが使用されます。
また、「データが書き込まれた日時を自動的にデータベースに保存する」といった処理も行うことができます。
今回はGraphQLの動作を確認しながら、Schema.graphqlと組み合わせてマッピングテンプレートを作成していきます。

// Schema.graphql

# ------------------------------
# Model
# ------------------------------
type Post @model {
	postId: ID!
	content: String!
	userId: ID!
}

type User @model {
	userId: ID!
	username: String!
	email: String!
}

# ------------------------------
# Schema
# ------------------------------
type Mutation {
	createPost(input: CreatePostInput!): Post
	deletePost(input: DeletePostInput!, expectedVersion: Int): Post
	updatePost(input: UpdatePostInput!): Post
}

type Query {
	getUser(userId: ID!): User
	getPost(postId: ID!): Post
	listPost: [Post!]!
}

schema {
	query: Query
	mutation: Mutation
}

# ------------------------------
# Input
# ------------------------------
input CreatePostInput {
	postId: ID
	content: String!
	userId: ID!
	createdAt: AWSDateTime
	updatedAt: AWSDateTime
}

input DeletePostInput {
	postId: ID!
}

input UpdatePostInput {
	postId: ID!
	content: String!
	userId: ID!
}

Schemaの詳細な書き方については割愛しますが、ここでは投稿(Post)のモデルを持った簡単なクエリとミューテーションを用意しました。
このSchemaに基づいて、以下のVTL(Velocity Template Language)ファイルを用意します。

├── mapping-templates
│   ├── Mutation.createPost.request.vtl
│   ├── Mutation.createPost.response.vtl
│   ├── Mutation.deletePost.request.vtl
│   ├── Mutation.deletePost.response.vtl
│   ├── Mutation.updatePost.request.vtl
│   ├── Mutation.updatePost.response.vtl
│   ├── Query.getPost.request.vtl
│   ├── Query.getPost.response.vtl
│   ├── Query.getUser.request.vtl
│   ├── Query.getUser.response.vtl
│   ├── Query.listPost.request.vtl
│   └── Query.listPost.response.vtl

マッピングテンプレートには必ずリクエストとレスポンスが存在するため、それぞれのファイルをセットで用意します。

1.Query.getUser

// Query.getUser.request.vtl
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
  "userId": $util.dynamodb.toDynamoDBJson($ctx.args.userId)
}
}
// Query.getUser.response.vtl
$util.toJson($context.result)

2.Query.getPost

userIdを渡すことで、該当するユーザーデータを返すVTLを作成します。

// Query.getPost.request.vtl
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
  "postId": $util.dynamodb.toDynamoDBJson($ctx.args.postId)
}
}

// Query.getPost.response.vtl
$util.toJson($context.result)

3.Query.listPost

全てのユーザーを取得し、それぞれのユーザーの情報を返すVTLを作成します。

// Query.listPost.request.vtl
{
"version": "2017-02-28",
"operation": "Scan"
}
// Query.listPost.response.vtl
$util.toJson($context.result.items)

4.Mutation.createPost

userIdとその他のキーを持つinputDataを受け取り、createdAtやupdatedAtなどの日時を自動的にDBのフィールドに保存するVTLを作成します。

// Mutation.createPost.request.vtl
{
"version": "2017-02-28",
"operation": "PutItem",
"key": {
  "postId": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.args.input.postId, $util.autoId())),
  "userId": $util.dynamodb.toDynamoDBJson($ctx.args.input.userId),
  "createdAt": $util.dynamodb.toDynamoDBJson($util.time.nowISO8601()),
  "updatedAt": $util.dynamodb.toDynamoDBJson($util.time.nowISO8601()),
},
"attributeValues": $util.dynamodb.toMapValuesJson($ctx.args.input),
"condition": {
  "expression": "attribute_not_exists(#postId)",
  "expressionNames": {
    "#postId": "postId"
  }
}
}


// Mutation.createPost.response.vtl
$util.toJson($ctx.result)

5.Mutation.deletePost

特定のuserIdを受け取り、該当するデータを削除するためのVTLを作成します。

// Mutation.deleteUser.request.vtl
{
"version": "2017-02-28",
"operation": "DeleteItem",
"key": {
  "postId": $util.dynamodb.toDynamoDBJson($ctx.args.input.postId)
}
}

// Mutation.deletePost.response.vtl
$util.toJson($ctx.result)

6.Mutation.updatePost

特定のuserIdを受け取り、nameやemailなどのフィールドのデータを更新するためのVTLを作成します。

// Mutation.updateUser.request.vtl
{
"version": "2017-02-28",
"operation": "PutItem",
"key": {
  "postId": $util.dynamodb.toDynamoDBJson($$ctx.args.input.postId)
},
"attributeValues": {
  "content": $util.dynamodb.toDynamoDBJson($$ctx.args.input.content),
  "userId": $util.dynamodb.toDynamoDBJson($$ctx.args.input.userId),
  "updatedAt": $util.dynamodb.toDynamoDBJson($util.time.nowISO8601())
}
}

// Mutation.updatePost.response.vtl
$util.toJson($ctx.result)

このVTLをServerlessFrameworkの環境構築時にAppSyncに反映させるために、serverless.ymlに記述していきます。

custom:
appSync:
  name: ${self:service}-${self:provider.stage}
  authenticationType: AMAZON_COGNITO_USER_POOLS
  userPoolConfig:
    awsRegion: ap-northeast-1
    defaultAction: ALLOW
    region: ap-northeast-1
    userPoolId:
      Ref: UserPool
  mappingTemplatesLocation: mapping-templates
  mappingTemplates:
    # ------------------------------------------
    # Queries
    # ------------------------------------------
    -
      type: Query
      field: getPost
      dataSource: Post
    -
      type: Query
      field: listPost
      dataSource: Post
    -
      type: Query
      field: getUser
      dataSource: User
    -
      type: Query
      field: listPost
      dataSource: Post

    # ------------------------------------------
    # Mutations
    # ------------------------------------------
    -
      type: Mutation
      field: createPost
      dataSource: Post
    -
      type: Mutation
      field: updatePost
      dataSource: Post
    -
      type: Mutation
      field: deletePost
      dataSource: Post
  schema: schema.graphql

このように、typeやfield、dataSourceを指定することでAppSyncのリゾルバーとしてVTLを反映することができます。
ただし、VTLは慣れない言語であるため、学習コストがかかる場合もあります。
最近ではDirect Lambda Resolversがリリースされ、VTLを書かずに済む場面も増えていますが、まだまだVTLを使用する必要がある場面も多いです。
そのため、VTLの学習は重要かもしれませんが、正直なところ書くのが億劫な気持ちもあります。

● 今から始めるServerless Frameworkでサーバーレスデビュー【バックエンド編#1】
https://note.com/ryoppei/n/n9163712b68ad

今から始めるServerless Frameworkでサーバーレスデビュー【フロントエンド編#2】
https://note.com/ryoppei/n/n0858cfca7784

今から始めるServerless Frameworkでサーバーレスデビュー【プラグイン番外編#3】
https://note.com/ryoppei/n/ne110ac6a440f

今から始めるServerless Frameworkでサーバーレスデビュー【ファンクション番外編#4】
https://note.com/ryoppei/n/n71809e2520e0

今から始めるServerless Frameworkでサーバーレスデビュー【マッピングテンプレート番外編#5】
https://note.com/ryoppei/n/n20b4afd705e5

この記事が気に入ったらサポートをしてみませんか?