Slide 1

Slide 1 text

AWS AppSyncを用いた GraphQL APIの開発について 川端 航平


Slide 2

Slide 2 text

2 川端 航平
 
 経歴
 2019~ ネットワークエンジニア 
 2022/10〜 SRE 
 
 
 「@nifty天気予報:フルリニューアル」にはSREとして参加 
 APIについては開発フローの提案、構築を担当 


Slide 3

Slide 3 text

!注意!
 
 GraphQLとAppSyncの利用は初であり、 
 ベストプラクティスではなく事例の共有となります 
 3

Slide 4

Slide 4 text

4 RDS
 AppSync 
 フロント 
 

 App
 Lambda 
 S3


Slide 5

Slide 5 text

このセッションの範囲 
 5 RDS
 AppSync 
 フロント 
 

 App
 S3
 Lambda 


Slide 6

Slide 6 text

6 GraphQLとは? AI GraphQLは効率的なAPIクエリ言語で、 以下が特徴です: 1. クライアント主導のデータ取得 2. 単一エンドポイントで複数リクエストを統合 3. 強力な型システムによるスキーマ定義 4. オーバーフェッチングの防止 5. APIの柔軟な機能追加が可能

Slide 7

Slide 7 text

7 クライアント API 必要なデータだけを要求するクエリを送る 必要なデータだけ返す /graphql

Slide 8

Slide 8 text

AppSyncを採用した理由 
 8 ● 運用工数を削減したい 
 ● コスト削減したい 
 ● 開発の不自由さはある程度許容できる 
 ウェザーニューズ様提供のデータに依存したAPI 
 → サービスリリース以降のAPIの機能増強は少ない 
 
 
 ↓
 マネージドでGraphQL APIを作れるAppSync 


Slide 9

Slide 9 text

9 AppSyncでのGraphQL API 
 開発体験は? 


Slide 10

Slide 10 text

GraphQL APIの初期開発フロー 
 10 フロント
 バックエンド 
 スキーマ作成 インフラ構築 スキーマから型生成 リゾルバ作成 クエリ作成 クエリから 型生成

Slide 11

Slide 11 text

インフラ構築 
 11 すべてTerraformで管理可能 
 ○ aws_appsync_graphql_api 
 ○ aws_appsync_datasource 
 ○ aws_appsync_domain_name 
 ○ aws_appsync_domain_name_api_association 
 ○ aws_appsync_api_cache 
 
 キャッシュも有効化するだけで使える 
 APIメトリクスも出してくれる 
 フルマネージド いいね

Slide 12

Slide 12 text

GraphQL APIの初期開発フロー 
 12 フロント
 バックエンド 
 スキーマ作成 インフラ構築 型生成 リゾルバ作成 クエリ作成 クエリから 型生成 スキーマから型生成

Slide 13

Slide 13 text

13 GrapQLスキーマとは? 
 ←Queryオブジェクト 
 ● earthquakeInfoByCodeはcodeを引数にEarthquakeInfoを返す 
 ←地震情報を表現するEarthquakeInfoオブジェクト 
 ● 例) epicenterはStringを返す 


Slide 14

Slide 14 text

AppSyncでは 
 14 RDSと接続して自動でスキーマ作成する機能がある 
 
 が。。。出来に難あり 
 
 ↓
 0から作成 


Slide 15

Slide 15 text

スキーマ作成 
 15 ● Devcontainerでスキーマを書くための環境を準備 
 ○ graphql-eslintでlint 
 ● ドキュメントを自動生成 
 ○ graphql-voyager 
 一般的なGraphQL プラクティスを使える

Slide 16

Slide 16 text

参考: 
 https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/scalars.html 
 AppSync特有なこと 
 16 AWS AppSync スカラー 
 
 例えば
 ● AWSDateTime 
 ○ ISO8601 日付および時刻文字列 "YYYY-MM-DDThh:mm:ss.sssZ" 
 ○ フォーマットに沿わない文字列をエラーにしてくれる 
 
 


Slide 17

Slide 17 text

参考: 
 https://github.com/aws/aws-appsync-community/issues/38 
 AppSync特有なこと 
 17 Description(BlockString)を使用できない 
 ● Terraform経由でスキーマ反映すると Descriptionだけ消えてくれる 
 ←Descriptionでエラー

Slide 18

Slide 18 text

GraphQL APIの初期開発フロー 
 18 フロント
 バックエンド 
 スキーマ作成 インフラ構築 型生成 リゾルバ作成 クエリ作成 クエリから 型生成 スキーマから型生成

Slide 19

Slide 19 text

AppSyncでは 
 19 npx @aws-amplify/cli codegen 
 で型生成できるらしい 
 が。。
 ● 事例が少ない 
 ● カスタマイズに難あり 
 
 ↓
 GraphQL Code Generatorを使用 
 ● TypeScriptの型生成で最も認知度の高い 
 
 
参考: https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/building-a-client-app.html 


Slide 20

Slide 20 text

型生成
 20 参考: 
 https://the-guild.dev/graphql/codegen/docs/guides/vanilla-typescript 
 const codegenConfigDefault: CodegenConfig = { generates: { './src/graphql/generated/': { preset: 'client', plugins: ['typescript-msw'], config: { scalars: { AWSDateTime: 'Date', enumsAsTypes: true, avoidOptionals: true, }, documentMode: 'string', }, }, }, hooks: { afterAllFileWrite: ['pnpm run format'], }, }; ● AWSDateTimeはDateとして型生成する設定 


Slide 21

Slide 21 text

AppSyncでも型生成に問題なし 
 21 const query = graphql(` query EarthquakeInfoDetail($code: Int!) { earthquakeInfoByCode(code: $code) { announcementDateTime occurrenceDateTime depth code epicenter imageName magnitude maxIntensityStr } `); export type EarthquakeInfoDetailQuery = { __typename?: 'Query'; earthquakeInfoByCode: { __typename?: 'EarthquakeInfo'; announcementDateTime: Date; occurrenceDateTime: Date; depth: string; code: number; epicenter: string; imageName: string; magnitude: number; maxIntensityStr: string; }; }; 自動生成 
 クエリ
 APIが返すデータの型 


Slide 22

Slide 22 text

GraphQL APIの初期開発フロー 
 22 フロント
 バックエンド 
 スキーマ作成 インフラ構築 型生成 リゾルバ作成 クエリ作成 クエリから 型生成 スキーマから型生成

Slide 23

Slide 23 text

リゾルバとは 
 23 GraphQLスキーマで定義されたフィールドのデータを取得 
 する方法を指定する関数 
 ● データ取得(生成) 
 ● スキーマ定義に合わせたデータの整形・マッピング 
 ↓
 今回の場合 
 ● RDSから データ取得 
 ● スキーマ定義に合わせたデータの整形・マッピング 


Slide 24

Slide 24 text

AppSyncのJavaScript RDSリゾルバ場合 
 24 ● データ取得→リクエストハンドラ 
 ○ SQLを返却する関数を書く 
 export const request = (ctx) => { const statement = sql ` SELECT id, epicenter, magnitude, max_intensity, max_intensity_str, depth FROM earthquake_info WHERE id = ${ctx.arguments.code}`; return createPgStatement(statement); }; ←SQL


Slide 25

Slide 25 text

AppSyncのJavaScript RDSリゾルバ場合 
 25 ● データの整形・マッピング→レスポンスハンドラ 
 ○ SQLの結果がctx.resultに入って来るので整形・マッピングする関数を書く 
 export const response = (ctx) => { const { error, result } = ctx; if (error !== null && error !== undefined) { util.error(error.message, error.type); } const resultObject = toJsonObject(result)[0]; return { code: resultObject[0].code, magnitude: resultObject[0].magnitude, epicenter: resultObject[0].epicenter, maxIntensity: resultObject[0].max_intensity, maxIntensityStr: resultObject[0].max_intensity_str, depth: resultObject[0].depth, }; }; ←マッピング 


Slide 26

Slide 26 text

26 AWSコンソール上でリゾルバが書けるが。。。 


Slide 27

Slide 27 text

リゾルバー作成フロー 
 1. Devcontainerで開発環境準備 
 ○ ESlintで plugin:@aws-appsync/recommended を使ってlint 
 2. TypeScriptでリゾルバーを書く 
 ○ 生成した型を使える 
 3. tscでトランスパイル 
 4. vitestでテスト 
 5. TerraformでJSを割り当て 
 27

Slide 28

Slide 28 text

テストについて 
 AWS SDKsに含まれるEvaluateCodeコマンドを使う 
 ● ctxとリゾルバーのJSを引数にAWS上でリゾルバのテスト実行をしてくれる 
 ● クエリ変数 
 ● レスポンスハンドラに渡される予定のSQL結果のモックデータ 
 ● など
 28 参考: 
 https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/test-resolvers.html 


Slide 29

Slide 29 text

テスト観点 
 リクエストハンドラ 
 ● SQL文を生成できているか 
 ● 引数に関するエラーハンドリング 
 
 レスポンスハンドラ 
 ● 出力結果が正しいこと 
 ● 異常値がDBから返されたときのエラーハンドリング 
 29 参考: 
 https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/test-resolvers.html 
 AppSyncでも リゾルバのテストは可能

Slide 30

Slide 30 text

AppSyncの辛いところ 
 30 ローカルでDB接続したAPIを立てて 
 リゾルバの動作確認ができない! 


Slide 31

Slide 31 text

今回のプロジェクトでは 
 31 リゾルバ検証用にAppSyncを立てた 
 ローカルでAWSリソースを動かせる LocalStackってのもあるらしい

Slide 32

Slide 32 text

32 RDS
 AppSync 
 フロント 
 

 App
 S3
 Lambda 
 リゾルバ検証用 
 AppSync 
 クエリ実行数の課金だから コスパGood

Slide 33

Slide 33 text

まとめ
 ● インフラ構築 
 ○ マネージドの恩恵を享受できる 
 ● スキーマ作成、型生成 
 ○ 一般的なGraphQLプラクティスを使える 
 ● リゾルバ作成 
 ○ AppSyncの制限が一番大きい 
 ○ 大規模、継続的な開発では限界が来るかも 


Slide 34

Slide 34 text

graphql-voyager 
 34 ● スキーマから依存関係グラフを生成できる 
 依存関係が分かって 便利

Slide 35

Slide 35 text

日付周りでの罠 
 35 1. ランタイムの仕様でJSのDateは使えない 
 2. RDSのTZに東京を設定しても、UTCで返される 
 →裏で使っているData APIの仕様 
 
 
 3. PostgreSQLのフォーマット( YYYY-MM-DD hh:mm:ss )
 をAWSDataTime( YYYY-MM-DDThh:mm:ss.sssZ )に変換が必要 
 select date as original_date, // 2024-09-20 05:14:00 to_char(date AT TIME ZONE 'Asia/Tokyo', 'YYYY-MM-DD"T"HH24:MI:SS"+09:00"') AS formatted_date // 2024-09-20T14:14:00+09:00 TZを指定した取得が必要 AWSDataTimeフォーマットに変換

Slide 36

Slide 36 text

リゾルバーの割当 
 36 locals { resolvers = { Query_allTyphoonInfo = { type = "Query" // 対象type field = "allTyphoonInfo" // 対象field caching_keys = [ // 対象キャッシュキー "$context.arguments", ] ttl = 60, // キャッシュ TTL resolver = "query/allTyphoonInfo.js" // JSパ ス }, Query_allArea = { type = "Query" field = "allArea" caching_keys = [ "$context.arguments", ] ttl = 3600, resolver = "query/allArea.js" data "local_file" "resolvers" { for_each = local.resolvers filename = "${path.module}/resolvers/${each.value["resolver"]}" } resource "aws_appsync_resolver" "this" { for_each = local.resolvers api_id = aws_appsync_graphql_api.this.id type = each.value["type"] field = each.value["field"] data_source = aws_appsync_datasource.weather_db.name caching_config { caching_keys = each.value["caching_keys"] ttl = each.value["ttl"] } code = data.local_file.resolvers[each.key].content runtime { name = "APPSYNC_JS" runtime_version = "1.0.0" } Local Valueにリゾルバ情報を記載して、for_eachでまわす