Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AWS AppSyncを用いた GraphQL APIの開発について - NIFTY Tech...

AWS AppSyncを用いた GraphQL APIの開発について - NIFTY Tech Talk #22

イベント
Next.js/Go/GraphQLで生まれ変わった@nifty天気予報 開発のウラ側
https://nifty.connpass.com/event/328943/

登壇者
ニフティ株式会社
川端 航平

ニフティ株式会社

November 13, 2024
Tweet

Video


Resources

Next.js/Go/GraphQLで生まれ変わった@nifty天気予報 開発のウラ側 - connpass

https://nifty.connpass.com/event/328943/

More Decks by ニフティ株式会社

Other Decks in Programming

Transcript

  1. 2 川端 航平
 
 経歴
 2019~ ネットワークエンジニア 
 2022/10〜 SRE

    
 
 
 「@nifty天気予報:フルリニューアル」にはSREとして参加 
 APIについては開発フローの提案、構築を担当 

  2. 6 GraphQLとは? AI GraphQLは効率的なAPIクエリ言語で、 以下が特徴です: 1. クライアント主導のデータ取得 2. 単一エンドポイントで複数リクエストを統合 3.

    強力な型システムによるスキーマ定義 4. オーバーフェッチングの防止 5. APIの柔軟な機能追加が可能
  3. AppSyncを採用した理由 
 8 • 運用工数を削減したい 
 • コスト削減したい 
 •

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

  4. インフラ構築 
 11 すべてTerraformで管理可能 
 ◦ aws_appsync_graphql_api 
 ◦ aws_appsync_datasource

    
 ◦ aws_appsync_domain_name 
 ◦ aws_appsync_domain_name_api_association 
 ◦ aws_appsync_api_cache 
 
 キャッシュも有効化するだけで使える 
 APIメトリクスも出してくれる 
 フルマネージド いいね
  5. GraphQL APIの初期開発フロー 
 12 フロント
 バックエンド 
 スキーマ作成 インフラ構築 型生成

    リゾルバ作成 クエリ作成 クエリから 型生成 スキーマから型生成
  6. スキーマ作成 
 15 • Devcontainerでスキーマを書くための環境を準備 
 ◦ graphql-eslintでlint 
 •

    ドキュメントを自動生成 
 ◦ graphql-voyager 
 一般的なGraphQL プラクティスを使える
  7. 参考: 
 https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/scalars.html 
 AppSync特有なこと 
 16 AWS AppSync スカラー

    
 
 例えば
 • AWSDateTime 
 ◦ ISO8601 日付および時刻文字列 "YYYY-MM-DDThh:mm:ss.sssZ" 
 ◦ フォーマットに沿わない文字列をエラーにしてくれる 
 
 

  8. 参考: 
 https://github.com/aws/aws-appsync-community/issues/38 
 AppSync特有なこと 
 17 Description(BlockString)を使用できない 
 •

    Terraform経由でスキーマ反映すると Descriptionだけ消えてくれる 
 ←Descriptionでエラー
  9. GraphQL APIの初期開発フロー 
 18 フロント
 バックエンド 
 スキーマ作成 インフラ構築 型生成

    リゾルバ作成 クエリ作成 クエリから 型生成 スキーマから型生成
  10. 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 

  11. 型生成
 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として型生成する設定 

  12. 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が返すデータの型 

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

    リゾルバ作成 クエリ作成 クエリから 型生成 スキーマから型生成
  14. リゾルバとは 
 23 GraphQLスキーマで定義されたフィールドのデータを取得 
 する方法を指定する関数 
 • データ取得(生成) 


    • スキーマ定義に合わせたデータの整形・マッピング 
 ↓
 今回の場合 
 • RDSから データ取得 
 • スキーマ定義に合わせたデータの整形・マッピング 

  15. 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

  16. 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, }; }; ←マッピング 

  17. リゾルバー作成フロー 
 1. Devcontainerで開発環境準備 
 ◦ ESlintで plugin:@aws-appsync/recommended を使ってlint 


    2. TypeScriptでリゾルバーを書く 
 ◦ 生成した型を使える 
 3. tscでトランスパイル 
 4. vitestでテスト 
 5. TerraformでJSを割り当て 
 27
  18. テストについて 
 AWS SDKsに含まれるEvaluateCodeコマンドを使う 
 • ctxとリゾルバーのJSを引数にAWS上でリゾルバのテスト実行をしてくれる 
 • クエリ変数

    
 • レスポンスハンドラに渡される予定のSQL結果のモックデータ 
 • など
 28 参考: 
 https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/test-resolvers.html 

  19. テスト観点 
 リクエストハンドラ 
 • SQL文を生成できているか 
 • 引数に関するエラーハンドリング 


    
 レスポンスハンドラ 
 • 出力結果が正しいこと 
 • 異常値がDBから返されたときのエラーハンドリング 
 29 参考: 
 https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/test-resolvers.html 
 AppSyncでも リゾルバのテストは可能
  20. 32 RDS
 AppSync 
 フロント 
 

 App
 S3
 Lambda

    
 リゾルバ検証用 
 AppSync 
 クエリ実行数の課金だから コスパGood
  21. まとめ
 • インフラ構築 
 ◦ マネージドの恩恵を享受できる 
 • スキーマ作成、型生成 


    ◦ 一般的なGraphQLプラクティスを使える 
 • リゾルバ作成 
 ◦ AppSyncの制限が一番大きい 
 ◦ 大規模、継続的な開発では限界が来るかも 

  22. 日付周りでの罠 
 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フォーマットに変換
  23. リゾルバーの割当 
 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でまわす