Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Effective AppSync ~ Serverless Framework を使用した ...
Search
geeawa
December 02, 2020
Programming
1.4k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Effective AppSync ~ Serverless Framework を使用した AppSync の実践的な開発方法とテスト戦略 ~ / Effective AppSync
geeawa
December 02, 2020
More Decks by geeawa
See All by geeawa
AI が開発・運用しやすいクラウド 〜サーバーレスの視点から考える設計原則〜 / serverless-design-principles-for-ai-agent-era
gawa
2
43
Amazon Bedrock AgentCore ワークショップ JAWS UG TOHOKU / amazon-bedrock-agentcore-workshop-jawsug-tohoku-2026
gawa
9
1.9k
運用エージェントは "作る" から "育てる" へ - 記憶と自己進化の3層設計パターン / self-evolving-agents-three-layer-agent-design
gawa
12
4.6k
ローカルで稼働するAI エージェントを超えて / beyond-local-ai-agents
gawa
3
450
AI は "道具" から "同僚" へ 自律型 AI エージェントの最前線と、AI 時代の人材の在り方 / Colleague in the AI Era - Autonomous AI Seminar 2026 at Niigata
gawa
0
420
Server Less Code More - コードを書かない時代に生きるサーバーレスデザイン / server-less-code-more
gawa
5
3.3k
[JAWS-UG TOHOKU] AI エージェント作って・使って・楽しんで ~Bedrock Engineer で AI エージェントを体験しよう~ / create, use and enjoy AI agents
gawa
6
860
AI Agent that supports ”YOU” / introducing-bedrock-engineer-en
gawa
2
1.2k
開発エージェントの作り手から見る開発エージェントの利用テクニック / ai-coding-dojyo-bedrock-engineer
gawa
3
920
Other Decks in Programming
See All in Programming
AI主導のプログラミング学習アプリを作ってみた
econanringo
0
180
Event is what happens
moro
2
770
設計次第でAIコードの読む量は減らせる / designing-for-code-reading
minodriven
35
17k
[Rails World 2026] Durable orchestration on Rails: from continuation to workflow
palkan
1
750
最近”作らせてる” ローカルAIアプリの話
akit37
0
470
コードレビューのボトルネックを"する側"と"される側"の両面から解消する
yub0n
2
1.6k
Simple Storage Service(S3) is not simple
iwatsukayura
0
290
Herb in Rails 8.2: Your ERB views, now HTML-aware @ Kaigi on Rails 2026, Tokyo, Japan
marcoroth
2
220
Railsバージョンを4倍にした話
optim
0
350
UPDATE をやめる — EF Core でマスタをバージョン管理する
panda728
PRO
0
1.2k
AIに “答え“を書かせるのを やめてみた
___akari11
0
120
JIS keyboard support in Omarchy
lef237
0
110
Featured
See All Featured
WENDY [Excerpt]
tessaabrams
14
40k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.2k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.9k
Designing for humans not robots
tammielis
254
26k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
18k
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
730
BBQ
matthewcrist
90
10k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
2
330
Lightning talk: Run Django tests with GitHub Actions
sabderemane
0
300
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
A Tale of Four Properties
chriscoyier
163
24k
Transcript
Effective AppSync ~ Serverless Framework を使⽤した AppSync の実践的な開発⽅法とテスト戦略 ~ 2020.12.02
Serverless MeetUp Japan Virtual #12
淡路⼤輔 / INTEC @gee0awa 好きな技術 Serverless / React Native /
Testing
AppSync の 始め⽅ AppSync と テスト What is GraphQL
AppSync の 始め⽅ AppSync と テスト What is GraphQL
What is GraphQL ? Facebookにより開発されたAPI ⽤のクエリ⾔語 Query / Mutation /
Subscription でデータ操作 Schema と呼ばれる型システム
REST vs GraphQL
API Document Mock Server Client SDK API Server の 型定義
OpenAPI による 恩恵 YAMLファイルにAPI定義を書き下すことで、各種ソースコードを⾃動⽣成
/users/:userId /users/:userId/friends friends users /posts Posts RESTful API の 課題
・1つの画⾯を表⽰するために幾つものAPIを実⾏しなければいけない ・バックエンドAPIによって、叩き⽅が異なる ・APIドキュメントに書いてあることと、実装がズレてしまう OpenAPI によって WebAPI は開発しやすくなってきたが・・・
user: { name: bob imageUrl: https://image.com/bob } posts: [ {
id: 1, imageUrl: https://image.com/1 title: xxxyyyzzz }, { id: 2, imageUrl: https://image.com/2 title: aabbcc }, … ] ブログサイト / ユーザプロフィール画⾯ ユーザの情報と投稿したブログの⼀覧を表⽰したい
/users/:userId user { name imageUrl age group } 1、ユーザを1件取得 /users/:userId/posts
items: [ { id title }, … ] 2、ユーザの記事を複数取得 /users/:userId/posts/:postId { id title imageUrl content } 3、記事の詳細をIDごとに取得 REST API Overfetching Underfetching and N+1 Problem 記事の数だけ何度もAPIを実⾏してしまう Client 不要なフィールドも取得してしまう RESTful API / Data Fetching
GraphQL API /graphql Client query { getUser(id: “1”) { name
imageUrl posts: { id title imageUrl } } } { name: “⽥中太郎” imageUrl: “http://xxx” posts: [ { id: “0001” title: “沖縄滞在記その1” imageUrl: “https://xxx" }, { id: “0002” title: “沖縄滞在記その2” imageUrl: “https://xxx" }, ] } GraphQL API / Data Fetching 必要なものだけを指定して取得できる Overfetching を解消 内部で Relation の解決を⾏う Underfetching や N+1 problem を解消
type Query { getUser(id: ID!): User } type User {
id: ID! name: String email: String imageUrl: String posts: [Post] } type Post { id: ID! title: String imageUrl: String content: String author: User } query { getUser(id: “1”) { id name posts: { id title imageUrl } } } { id: “1” name: “⽥中太郎” posts: [ { id: “0001” title: “沖縄滞在記その1” imageUrl: “https://xxx" }, { id: “0002” title: “沖縄滞在記その2” imageUrl: “https://xxx" }, ] } Schema Request Response SDL(Schema Definition Language) を使⽤して型を記述する 必要なフィールドを指定して リクエストする リクエストしたフィールドだけが 返却される Core Concepts of GraphQL
Core Concepts of GraphQL Query 取得 Mutation 変更 Subscription 購読
3種類 の データ操作 query { getUser(id: “1”) { name } } mutation { createUser(name: “John”, email: “
[email protected]
”) { name } } subscription { addedUser() { name } } mutation createUser { data: { name: “John”, email: “
[email protected]
" } } Mutation をトリガーにイベントベースでデータを購読する
Web API に 秩序 を 持たせる Query / Mutation /
Subscription でデータ操作 Schema と呼ばれる型システム フロントエンドの要件にあったデータを返却 Why GraphQL ?
GraphQL バックエンドを どのように組み⽴てるのか
GraphQL サーバ構築 の 苦労 VPC EC2 に GraphQL APIサーバ⼊れて「はい終わり」、ではない EC2
S VPC GraphQL サーバ構築 の 苦労 EC2 に GraphQL APIサーバ⼊れて「はい終わり」、ではない
Availability Zone Availability Zone Subscription するために 状態 を Redis に保持して Pub/Sub 冗⻑化、弾⼒性などを考慮して サーバにスケーラビリティを持たせる ELB ElasticCache for Redis CloudWatch Logs EC2のログを 出⼒する仕組みも必要 fluentd? awslogs? そもそもアプリケーション作るのも⾯倒 ・グローバルエラーハンドラー ・リゾルバーやルーティング処理 ・ビジネスロジック ・認証・認可の処理 ・etc・・・ EC2 EC2 GraphQL サーバは各種バックエンド データソースと接続する
AppSync AWS が提供するフルマネージド GraphQL サービス
AppSync を使⽤する 即座に⼿に⼊る GraphQL バックエンド CloudWatch Logs デフォルトでログを CloudWatchに出⼒ Cognito
Auth0 などの third party tool 認証・認可 各種サービスと統合 GraphQL エンドポイントを提供 内部のサーバは ⾃動的にスケールアウト GraphQL サーバは各種バックエンド データソースと接続する Lambda DynamoDB Elasticserach APIGateway Aurora Serverless
AppSync と テスト What is GraphQL AppSync の 始め⽅
AppSync を 構築する⽅法 AWS CLI マネジメントコンソール CloudFormation Amplify CLI Serverless
Framework 直感的なインターフェースで作成、簡単に検証できる 複数環境に全く同じ構成で構築しづらい。デリバリを⾃動化できない デリバリーを⾃動化できる 冪等性がないため、エラー時にリカバリが難しい 冪等性があり、エラー発⽣時に元に戻る スキーマ定義を S3 に配置しておく必要がある、YAML の量が多いなどの苦労 CloudFormation のコードを⾃動⽣成、抽象化されたコマンドで即座に構築可能 amplify コマンドが抽象化しすぎていて困るケースも・・・ 適度に抽象化しており、YAML の記述量は少なめ ローカルでテストできるツールセットが豊富 ⽤途に応じて選択する さくっと作るなら Amplify CLI、腰を据えてじっくり作るなら Serverless か CFn がオススメ 他にも AWS CDK や Pulumi, Terraform などもあります
AppSync を 構築する⽅法 AWS CLI マネジメントコンソール CloudFormation Amplify CLI Serverless
Framework 直感的なインターフェースで作成、簡単に検証できる 複数環境に全く同じ構成で構築しづらい。デリバリを⾃動化できない デリバリーを⾃動化できる 冪等性がないため、エラー時にリカバリが難しい 冪等性があり、エラー発⽣時に元に戻る スキーマ定義を S3 に配置しておく必要がある、YAML の量が多いなどの苦労 CloudFormation のコードを⾃動⽣成、抽象化されたコマンドで即座に構築可能 amplify コマンドが抽象化しすぎていて困るケースも・・・ 適度に抽象化しており、YAML の記述量は少なめ ローカルでテストできるツールセットが豊富 ⽤途に応じて選択する さくっと作るなら Amplify CLI、腰を据えてじっくり作るなら Serverless か CFn がオススメ 他にも AWS CDK や Pulumi, Terraform などもあります 今⽇はこれのお話しです
Serverless Framework for AppSync sid88in / serverless-appsync-plugin serverless-components / aws-app-sync
Serverless Framework の プラグイン ローカルに配置した schema.grapql や mapping-template を 参照して AppSync をデプロイする Serverless Components の AppSync コンポーネント 最⼩限のコード量で AppSync だけでなく、 カスタムエンドポイント付きで意味のある単位でリソース群をデプロイ https://github.com/sid88in/serverless-appsync-plugin https://github.com/serverless-components/aws-app-sync Serverless Framework と Serverless Components
Serverless Framework for AppSync Serverless Framework と Serverless Components sid88in
/ serverless-appsync-plugin serverless-components / aws-app-sync Serverless Framework の プラグイン ローカルに配置した schema.grapql や mapping-template を 参照して AppSync をデプロイする Serverless Components の AppSync コンポーネント 最⼩限のコード量で AppSync だけでなく、 カスタムエンドポイント付きで意味のある単位でリソース群をデプロイ https://github.com/sid88in/serverless-appsync-plugin https://github.com/serverless-components/aws-app-sync 今⽇はこれのお話しです
アプリ名 バージョン情報 タスク管理 テーブル サンプルアプリケーション タスク管理アプリケーションのバックエンドを AppSync で構築することを想定
アプリ名 バージョン情報 タスク管理 テーブル サンプルアプリケーション Schema, Resolver, DataSource の3つの要素 Query
{ getTask(id=“1”) { id name status } } ”data” { “getTask” { “id”: “1”, “name”: “掃除” “status”: “Done” } } AppSync Resolver (response) Resolver (request) Apache Velocity Template Language (VTL) で記述された マッピングテンプレートを使⽤して GraphQL リクエスト・レスポンスを データソースが理解できる形式に変換する レスポンスを変換、加⼯ リクエストを変換、加⼯ $util.toJson($ctx.result) Data Source { "version": "2018-05-29", "operation": "GetItem", "key": { "id": $util.dynamodb.toDynamoDBJson($ctx.args.id) } }
Serverless AppSync Plugin serverless.yaml (続き) functions: appInfo: handler: src/functions/handler.appInfo name:
appInfo resources: Resources: Table: Type: AWS::DynamoDB::Table Properties: TableName: task AttributeDefinitions: - AttributeName: id AttributeType: S - AttributeName: status AttributeType: S KeySchema: - AttributeName: id KeyType: HASH - AttributeName: status KeyType: RANGE ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 AppSyncDynamoDBServiceRole: Type: "AWS::IAM::Role" Properties: RoleName: appsync-dynamodb-role AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "appsync.amazonaws.com" Action: - "sts:AssumeRole" Policies: - PolicyName: "dynamo-policy" …ུ custom: appSync: name: taskboard_backend schema: schema.graphql dataSources: - type: AMAZON_DYNAMODB name: task description: λεΫཧςʔϒϧ config: tableName: { Ref: Table } serviceRoleArn: { Fn::GetAtt: [AppSyncDynamoDBServiceRole, Arn] } region: ap-northeast-1 - type: AWS_LAMBDA name: appInfo description: "Lambda DataSource for appInfo" config: functionName: appInfo iamRoleStatements: - Effect: "Allow" Action: - "lambda:invokeFunction" Resource: - “*" mappingTemplatesLocation: mapping-templates mappingTemplates: # ΞϓϦέʔγϣϯͷใΛऔಘ͢Δ - dataSource: appInfo type: Query field: appInfo request: Query.appInfo.request.vtl response: Query.appInfo.response.vtl # λεΫใΛ̍݅औಘ͢Δ - type: Query field: getTask request: “Query.getTask.request.vtl” response: “Query.getTask.response.vtl” plugin: - serverless-appsync-plugin serverless.yaml
Serverless AppSync Plugin serverless.yml (続き) functions: appInfo: handler: src/functions/handler.appInfo name:
appInfo resources: Resources: Table: Type: AWS::DynamoDB::Table Properties: TableName: task AttributeDefinitions: - AttributeName: id AttributeType: S - AttributeName: status AttributeType: S KeySchema: - AttributeName: id KeyType: HASH - AttributeName: status KeyType: RANGE ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 AppSyncDynamoDBServiceRole: Type: "AWS::IAM::Role" Properties: RoleName: appsync-dynamodb-role AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "appsync.amazonaws.com" Action: - "sts:AssumeRole" Policies: - PolicyName: "dynamo-policy" …ུ custom: appSync: name: taskboard_backend schema: schema.graphql dataSources: - type: AMAZON_DYNAMODB name: task description: λεΫཧςʔϒϧ config: tableName: { Ref: Table } serviceRoleArn: { Fn::GetAtt: [AppSyncDynamoDBServiceRole, Arn] } region: ap-northeast-1 - type: AWS_LAMBDA name: appInfo description: "Lambda DataSource for appInfo" config: functionName: appInfo iamRoleStatements: - Effect: "Allow" Action: - "lambda:invokeFunction" Resource: - “*" mappingTemplatesLocation: mapping-templates mappingTemplates: # ΞϓϦέʔγϣϯͷใΛऔಘ͢Δ - dataSource: appInfo type: Query field: appInfo request: Query.appInfo.request.vtl response: Query.appInfo.response.vtl # λεΫใΛ̍݅औಘ͢Δ - type: Query field: getTask request: “Query.getTask.request.vtl” response: “Query.getTask.response.vtl” plugin: - serverless-appsync-plugin serverless.yml 使⽤するプラグインを宣⾔
Serverless AppSync Plugin serverless.yml (続き) functions: appInfo: handler: src/functions/handler.appInfo name:
appInfo resources: Resources: Table: Type: AWS::DynamoDB::Table Properties: TableName: task AttributeDefinitions: - AttributeName: id AttributeType: S - AttributeName: status AttributeType: S KeySchema: - AttributeName: id KeyType: HASH - AttributeName: status KeyType: RANGE ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 AppSyncDynamoDBServiceRole: Type: "AWS::IAM::Role" Properties: RoleName: appsync-dynamodb-role AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "appsync.amazonaws.com" Action: - "sts:AssumeRole" Policies: - PolicyName: "dynamo-policy" …ུ custom: appSync: name: taskboard_backend schema: schema.graphql dataSources: - type: AMAZON_DYNAMODB name: task description: λεΫཧςʔϒϧ config: tableName: { Ref: Table } serviceRoleArn: { Fn::GetAtt: [AppSyncDynamoDBServiceRole, Arn] } region: ap-northeast-1 - type: AWS_LAMBDA name: appInfo description: "Lambda DataSource for appInfo" config: functionName: appInfo iamRoleStatements: - Effect: "Allow" Action: - "lambda:invokeFunction" Resource: - “*" mappingTemplatesLocation: mapping-templates mappingTemplates: # ΞϓϦέʔγϣϯͷใΛऔಘ͢Δ - dataSource: appInfo type: Query field: appInfo request: Query.appInfo.request.vtl response: Query.appInfo.response.vtl # λεΫใΛ̍݅औಘ͢Δ - type: Query field: getTask request: “Query.getTask.request.vtl” response: “Query.getTask.response.vtl” plugin: - serverless-appsync-plugin serverless.yml GraphQL のスキーマファイルを指定 データソースを指定 データソースとなるリソースも 同じテンプレートファイルで宣⾔して参照する
Serverless AppSync Plugin serverless.yml (続き) functions: appInfo: handler: src/functions/handler.appInfo name:
appInfo resources: Resources: Table: Type: AWS::DynamoDB::Table Properties: TableName: task AttributeDefinitions: - AttributeName: id AttributeType: S - AttributeName: status AttributeType: S KeySchema: - AttributeName: id KeyType: HASH - AttributeName: status KeyType: RANGE ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 AppSyncDynamoDBServiceRole: Type: "AWS::IAM::Role" Properties: RoleName: appsync-dynamodb-role AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "appsync.amazonaws.com" Action: - "sts:AssumeRole" Policies: - PolicyName: "dynamo-policy" …ུ custom: appSync: name: taskboard_backend schema: schema.graphql dataSources: - type: AMAZON_DYNAMODB name: task description: λεΫཧςʔϒϧ config: tableName: { Ref: Table } serviceRoleArn: { Fn::GetAtt: [AppSyncDynamoDBServiceRole, Arn] } region: ap-northeast-1 - type: AWS_LAMBDA name: appInfo description: "Lambda DataSource for appInfo" config: functionName: appInfo iamRoleStatements: - Effect: "Allow" Action: - "lambda:invokeFunction" Resource: - “*" mappingTemplatesLocation: mapping-templates mappingTemplates: # ΞϓϦέʔγϣϯͷใΛऔಘ͢Δ - dataSource: appInfo type: Query field: appInfo request: Query.appInfo.request.vtl response: Query.appInfo.response.vtl # λεΫใΛ̍݅औಘ͢Δ - type: Query field: getTask request: “Query.getTask.request.vtl” response: “Query.getTask.response.vtl” plugin: - serverless-appsync-plugin serverless.yml マッピングテンプレートを宣⾔する ローカルに配置した VTL ファイル と クエリ を紐づける
Serverless AppSync Plugin $ serverless deploy serverless.yml query.vtl functions/handler.js デプロイ
CloudFormation Stack S3 Bucket AppSync Lambda Function DynamoDB Table ローカルの設定ファイルを読み込み、AWS上に各種リソースを構築します
AppSync の 始め⽅ AppSync と テスト What is GraphQL
AppSync をどのようにテストするのか? (今⽇話したいこと)
テスト の フィードバックループ https://speakerdeck.com/twada/testable-lambda-working-effectively-with-legacy-lambda?slide=19 Testable Lambda / t-wada先⽣ の 公演
@AWS Summit Tokyo 2017
AWS Cloud localhost AWS をエミュレートする テストコードによる 継続的な検証 ローカル環境に AWS の
Fake Object を作る ローカル環境に AWS と同じ環境を再現することで “テスタビリティをこじあける”
AWS サービスの Fake といえば LocalStack: ローカルに http通信できる AWS のモック(Fake)となるエンドポイントを起動する が有名ですが、
AppSync は 有料です
$ amplify mock api コマンドでローカルに AppSync のシミュレータを起動 amplify-appsync-simulator
serverless-appsync-simulator amplify-appsync-simulator をラップし、 ServerlessFramework のプラグインとして提供している
serverless-appsync-simulator plugin: - serverless-dynamodb-local - serverless-appsync-simulator - serverless-offline - serverless-appsync-plugin
← Dynamodb リゾルバーを使う場合は必要 ← sls offline として起動する $ sls offline start … AppSync Simulator: AppSync endpoint: http://localhost:20002/graphql AppSync Simulator: GraphiQl: http://localhost:20002 … serverless.yaml terminal serverless offline プラグインとして起動する ← serverless-offline より前に書くことに注意
serverless-appsync-simulator http://localhost:20002 に起動した GrapiQL
テストを書く準備ができました ☕
アプリ名 バージョン情報 タスク管理 テーブル Integration Testing AppSync の リゾルバー を
Black Box 的にテストを記述する Query { getTask(id=“1”) { id name status } } ”data” { “getTask” { “id”: “1”, “name”: “掃除” “status”: “Done” } } GraphQLのリクエストに対するレスポンスが 期待した通りかどうかテストします。 INPUT OUTPUT AppSync Data Source Resolver (response) Resolver (request) レスポンスを変換、加⼯ リクエストを変換、加⼯ $util.toJson($ctx.result) { "version": "2018-05-29", "operation": "GetItem", "key": { "id": $util.dynamodb.toDynamoDBJson($ctx.args.id) } }
Integration Testing Jest (JavaScriptのテストライブラリ) によるコード例 import { GraphQLClient, gql }
from "graphql-request"; import { getTask } from "./query"; import { createTask, deleteTask } from "./mutation"; const client = new GraphQLClient("http://localhost:20002/graphql"); describe("Dynamodb resolver Integration Testing", () => { test("createTask / getTask by id / deleteTask", async () => { const request = { id: “123456789", name: "study serverless framework”, status: “NoStatus” }; // ̍݅λεΫσʔλΛ࡞ const created = await client.request(createTask, request); expect(created).toStrictEqual({ createTask: request }); // ࡞ͨ݁͠ՌΛऔಘ const got = await client.request(getTask, { id: request.id }); expect(got.getTask).toEqual(request); // ࡞ͨ͠λεΫσʔλΛআ const deleted = await client.request(deleteTask, { id: request.id }); expect(deleted).toStrictEqual({ deleteTask: request }); }); });
Integration Testing Jest (JavaScriptのテストライブラリ) によるコード例 import { GraphQLClient, gql }
from "graphql-request"; import { getTask } from "./query"; import { createTask, deleteTask } from "./mutation"; const client = new GraphQLClient("http://localhost:20002/graphql"); describe("Dynamodb resolver Integration Testing", () => { test("createTask / getTask by id / deleteTask", async () => { const request = { id: “123456789", name: "study serverless framework”, status: “NoStatus” }; // ̍݅λεΫσʔλΛ࡞ const created = await client.request(createTask, request); expect(created).toStrictEqual({ createTask: request }); // ࡞ͨ݁͠ՌΛऔಘ const got = await client.request(getTask, { id: request.id }); expect(got.getTask).toEqual(request); // ࡞ͨ͠λεΫσʔλΛআ const deleted = await client.request(deleteTask, { id: request.id }); expect(deleted).toStrictEqual({ deleteTask: request }); }); });
アプリ名 バージョン情報 タスク管理 テーブル Unit Testing ビジネスロジックが 凝集する リゾルバー をユニットテストする
Query { getTask(id=“1”) { id name status } } ”data” { “getTask” { “id”: “1”, “name”: “掃除” “status”: “Done” } } VTL で記述された mapping-template が リクエストに応じてどのような加⼯を⾏うかテストする OUTPUT INPUT AppSync Data Source Resolver (response) Resolver (request) レスポンスを変換、加⼯ リクエストを変換、加⼯ $util.toJson($ctx.result) { "version": "2018-05-29", "operation": "GetItem", "key": { "id": $util.dynamodb.toDynamoDBJson($ctx.args.id) } }
Unit Testing amplify-velocity-template (Velocity.js) を使⽤して VTL ファイル を Compile /
Parse するヘルパー関数を⽤意する import * as fs from "fs"; import * as path from "path"; import { Compile, parse } from "amplify-velocity-template"; import { map } from "amplify-appsync-simulator/lib/velocity/value-mapper/mapper"; import * as utils from "amplify-appsync-simulator/lib/velocity/util"; // VTLϑΝΠϧͰల։͞ΕΔ context Λ࡞͢Δ const createVtlContext = <T>(args: T) => { const util = utils.create([], new Date(Date.now()), Object()); const context = { args, arguments: args, }; return { util, utils: util, ctx: context, context, }; }; // ࢦఆύεͷϑΝΠϧΛࢀর͠ɺೖྗύϥϝʔλΛͱʹɺvtlϑΝΠϧʹΑΓϚοϐϯά͞ΕͨϦκϧόϦΫΤετJSONΛϩʔυ͢Δ const vtlLoader = (filePath: string, args: any) => { const vtlPath = path.resolve(__dirname, filePath); const vtl = parse(fs.readFileSync(vtlPath, { encoding: "utf8" })); const compiler = new Compile(vtl, { valueMapper: map, escape: false }); const context = createVtlContext(args); const result = JSON.parse(compiler.render(context)); return result; }; ← VTLファイルをロード・パース・コンパイル
Unit Testing GraphQL リクエスト を リゾルバー が変換した結果を検査する test("getTask.req.vtl", () =>
{ const args = { id: "000" }; const result = vtlLoader(“./getTask.request.vtl", args); expect(result).toStrictEqual({ version: "2018-05-29", operation: "GetItem", key: { id: { S: "000" } } }); }); { "version": "2018-05-29", "operation": "GetItem", "key": { "id": $util.dynamodb.toDynamoDBJson($ctx.args.id) } } getTask.request.vtl (VTLファイル) getTask.resolver.test.ts (テストコード) ← GraphQL のリクエストから Resolver に渡される引数の情報(INPUT) ← mapping-template によって⽣成された結果(OUTPUT) ← mapping-template の振る舞いを模したヘルパ関数
mutation.createTask.request.vtl (VTLファイル) Unit Testing 1件取得系の処理はシンプルだが、Mutation は if ⽂が増えて複雑になりがち ## [Start]
Prepare DynamoDB PutItem Request. ** $util.qr($context.args.input.put("createdAt", $util.defaultIfNull($ctx.args.input.createdAt, $util.time.nowISO8601()))) $util.qr($context.args.input.put("updatedAt", $util.defaultIfNull($ctx.args.input.updatedAt, $util.time.nowISO8601()))) $util.qr($context.args.input.put("__typename", "Task")) #set( $condition = { "expression": "attribute_not_exists(#id)", "expressionNames": { "#id": "id" } } ) #if( $context.args.condition ) #set( $condition.expressionValues = {} ) #set( $conditionFilterExpressions = $util.parseJson($util.transform.toDynamoDBConditionExpression($context.args.condition)) ) $util.qr($condition.put("expression", "($condition.expression) AND $conditionFilterExpressions.expression")) $util.qr($condition.expressionNames.putAll($conditionFilterExpressions.expressionNames)) $util.qr($condition.expressionValues.putAll($conditionFilterExpressions.expressionValues)) #end #if( $condition.expressionValues && $condition.expressionValues.size() == 0 ) #set( $condition = { "expression": $condition.expression, "expressionNames": $condition.expressionNames } ) #end { "version": "2017-02-28", "operation": "PutItem", "key": { "id": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.args.input.id, $util.autoId())), "status": $util.dynamodb.toDynamoDBJson($context.args.input.status) }, "attributeValues": $util.dynamodb.toMapValuesJson($context.args.input), "condition": $util.toJson($condition) } ## [End] Prepare DynamoDB PutItem Request. **
test("createTask.req.vtl / expect attributeValues: createdAt, updateAt etc...", () => {
const args = { input: { id: “001", name: “study", status: “InProgress" } }; const result = vtlLoader(“./Mutation.createTask.request.vtl”, args); expect(result).toEqual({ version: "2017-02-28", operation: "PutItem", key: { id: { S: "001" }, status: { S: "InProgress" }, }, attributeValues: { __typename: { S: "Task", }, createdAt: { S: expect.anything(), }, id: { S: "001", }, name: { S: "study", }, status: { S: "InProgress", }, updatedAt: { S: expect.anything(), }, }, condition: { expression: "attribute_not_exists(#id)", expressionNames: { "#id": "id", }, }, }); }); Unit Testing テストケースはシンプルに保つことができる ✨ ← INPUT ← OUTPUT
CircleCI でテストを実⾏する
CircleCI でテストを実⾏する serverless-appsync-simulator が起動するまで テストの実⾏ を待たせる必要があるので、start-server-and-test を使⽤する version: 2.1 orbs:
aws-cli: circleci/
[email protected]
serverless: circleci/
[email protected]
jobs: test: executor: serverless/default steps: - checkout - run: name: apt update command: sudo apt update - run: name: apt install java command: sudo apt install openjdk-8-jdk - run: name: install dependencies command: yarn install - run: name: setup for dynamodb local command: yarn sls:setup - run: name: unit test command: yarn ci workflows: version: 2 build_and_test: jobs: - test "scripts": { "sls:setup": "sls dynamodb install", "start": "sls offline start", "test": "jest", "start-server": "yarn start", "ci": "start-server-and-test start-server http://localhost:20002 test" } .circleci/config.yml package.json serverless-appsync-simulator を起動する jest によるテストの実⾏ serverless-appsync-simulator を停⽌する
まとめ AppSync の 始め⽅ AppSync と テスト What is GraphQL
・GraphQL は Facebookにより開発されたAPI ⽤のクエリ⾔語 ・Query / Mutation / Subscription でデータ操作 ・Schema と呼ばれる型システム ・Schema, Resolver, DataSource の3つで構成 ・Serverless Framework か Amplify CLI がオススメ ・ローカルで AppSync のシミュレータを起動 ・GraphQL リクエスト - レスポンスをテストする Integration Testing ・VTLファイルによるマッピングテンプレートの振る舞いをテストする Unit Testing
Appendix サンプルソースを serverless/examples として上げています。 以下コマンドで簡単に⼿元で動かすことができます。 $ serverless create \ ̶template-url
https://github.com/daisuke-awaji/serverless-appsync-offline-typescript-template \ ̶path app