Closed11

AWS CloudFormationでAWS AppSync上にGraphQL APIを構築したい(HTTPデータソース)

qmotasqmotas

AWS AppSync JavaScript ResolverでHTTPデータソース(REST API)からデータを収集すると同じ仕様のAPIをCloudFormationで作りたい

参考資料

備考

  • CloudFormationは全く触ったことない
    • AppSyncのサンプルAPIがCloudFormationでシャッと作れるのが体験よかったので触っておきたい
  • GraphQLのスキーマをテンプレートファイルに埋め込むのではなく外部ファイルに出せるとうれしい
qmotasqmotas

やってみて

  • CloudFormationそのものの取っつきにくさがある
    • テンプレートを書くときのGetting Started的な情報が意外と出てこない
    • デザイナーのUXがよくない
      • 結局リファレンスを見ながらプロパティを手書きする必要があり、何のためにGUIになっているのかがわからない
    • テンプレートをS3に置いて~みたいな記事を複数見たけど結局ファイルをアップロードするのが一番ストレートでよかった
  • AppSyncの設定自体はいったんコンソールで作ったものを転記していくだけだったので特に問題なかった
  • スキーマやリゾルバの実装をテンプレート内に書く必要があるので、IaCとしては微妙さを感じる
    • Gitでバージョン管理してCI/CDしたい
    • JSとGraphQLのコードは外に出してそれぞれlinterを通したいけどそれができない
    • S3に置けば外に出せるけど微妙
      • GitHubからS3にデプロイして~というのは可能だと思うけど...
      • テンプレートも一緒にS3にデプロイするようにしてGitHub -> S3 -> CloudFormationとすればよい?
qmotasqmotas

スキーマを外部ファイルで管理したい

外部ファイルはAWS::Include transformで埋め込むことができるけどS3に置いておく必要がある
テンプレートファイルをGitHubで管理したいのでこれだと微妙

qmotasqmotas

テンプレート

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  GraphQLApi:
    Type: AWS::AppSync::GraphQLApi
    Properties:
      AuthenticationType: API_KEY
      Name: AppSync Playground(cfn)

  ApiKey:
    Type: AWS::AppSync::ApiKey
    Properties:
      ApiId:
        Fn::GetAtt: [GraphQLApi, ApiId]
      Expires: 1677510000 # 2023年2月28日 火曜日 00:00:00 GMT+09:00

  DataSource1:
    Type: AWS::AppSync::DataSource
    Properties:
      ApiId:
        Fn::GetAtt: [GraphQLApi, ApiId]
      Name: catstronauts_rest_api
      Type: HTTP
      HttpConfig:
        Endpoint: https://odyssey-lift-off-rest-api.herokuapp.com

  FunctionConfiguration1:
    Type: AWS::AppSync::FunctionConfiguration
    Properties:
      ApiId:
        Fn::GetAtt: [GraphQLApi, ApiId]
      DataSourceName:
        Fn::GetAtt: [DataSource1, Name]
      FunctionVersion: '2023-02-14'
      Name: getTracksForHome
      Runtime:
        Name: APPSYNC_JS
        RuntimeVersion: 1.0.0
      Code: |
        import { util } from '@aws-appsync/utils';

        export function request(ctx) {
          return {
            method: 'GET',
            params: {
              headers: { 'Content-Type': 'application/json' },
            },
            resourcePath: '/tracks',
          };
        }

        export function response(ctx) {
          const { error, result } = ctx;
          if (error) {
            ctx.stash.errors = ctx.stash.errors ?? [];
            ctx.stash.errors.push(ctx.error);
            return util.appendError(error.message, error.type, result);
          }

          if (result.statusCode === 200) {
            return JSON.parse(result.body);
          } else {
            return util.appendError(result.body, result.statusCode);
          }
        }

  FunctionConfiguration2:
    Type: AWS::AppSync::FunctionConfiguration
    Properties:
      ApiId:
        Fn::GetAtt: [GraphQLApi, ApiId]
      DataSourceName:
        Fn::GetAtt: [DataSource1, Name]
      FunctionVersion: '2023-02-14'
      Name: getAuthor
      Runtime:
        Name: APPSYNC_JS
        RuntimeVersion: 1.0.0
      Code: |
        import { util } from '@aws-appsync/utils';

        export function request(ctx) {
          return {
            method: 'GET',
            params: {
              headers: { 'Content-Type': 'application/json' },
            },
            resourcePath: `/author/${ctx.source.authorId}`,
          };
        }

        export function response(ctx) {
          const { error, result } = ctx;
          if (error) {
            ctx.stash.errors = ctx.stash.errors ?? [];
            ctx.stash.errors.push(ctx.error);
            return util.appendError(error.message, error.type, result);
          }

          if (result.statusCode === 200) {
            return JSON.parse(result.body);
          } else {
            return util.appendError(result.body, result.statusCode);
          }
        }

  GraphQLSchema:
    Type: AWS::AppSync::GraphQLSchema
    Properties:
      ApiId:
        Fn::GetAtt: [GraphQLApi, ApiId]
      Definition: |
        type Author {
          id: ID!
          name: String!
          photo: String
        }

        type Query {
          tracksForHome: [Track!]!
        }

        type Track {
          id: ID!
          title: String!
          author: Author!
          thumbnail: String
          length: Int
          modulesCount: Int
        }

  Resolver1:
    Type: AWS::AppSync::Resolver
    Properties:
      ApiId:
        Fn::GetAtt: [GraphQLApi, ApiId]
      TypeName: 'Query'
      FieldName: 'tracksForHome'
      Kind: PIPELINE
      Runtime:
        Name: APPSYNC_JS
        RuntimeVersion: 1.0.0
      Code: |
        import {util} from '@aws-appsync/utils';

        export function request(ctx) {
            return {};
        }

        export function response(ctx) {
            return ctx.prev.result;
        }
      PipelineConfig:
        Functions: [Fn::GetAtt: [FunctionConfiguration1, FunctionId]]

  Resolver2:
    Type: AWS::AppSync::Resolver
    Properties:
      ApiId:
        Fn::GetAtt: [GraphQLApi, ApiId]
      TypeName: 'Track'
      FieldName: 'author'
      Kind: PIPELINE
      Runtime:
        Name: APPSYNC_JS
        RuntimeVersion: 1.0.0
      Code: |
        import {util} from '@aws-appsync/utils';

        export function request(ctx) {
            return {};
        }

        export function response(ctx) {
            return ctx.prev.result;
        }
      PipelineConfig:
        Functions: [Fn::GetAtt: [FunctionConfiguration2, FunctionId]]
qmotasqmotas

CloudFormationコンソールからスタックの作成 > 新しいリソースを使用(標準)で作成した
スタック名のみ編集してそれ以外はデフォルトのまま

qmotasqmotas
  • 2回テンプレートのエラーでスタックを作り直した
      1. ApiKeyExpiresを参考にした記事の値のまま設定してしまった(エポック秒なのでちゃんと未来の日付で設定しないとダメ)
      1. リゾルバのRuntimeAppSyncRuntimeオブジェクトで設定しないといけないのにリファレンスを斜め読みして"APPSYNC_JS"のみ文字列で指定していた
  • 作成に失敗したスタックはスタックの更新が行えないので、スタックを削除 -> 再度作成する必要があった
  • エラーはコンソールのイベントタグに出るメッセージを見てリファレンスを確認したら特に困ることなく解消できた
qmotasqmotas

雑感

  • 作りたい構成に近いサンプルがあれば編集してサッと作れるのはよい
  • リゾルバのJSコードをYAMLに書くのが微妙
    • AppSyncのコンソールで編集してリソースのインポートで更新するのがよさそう?未検証
    • S3に置けば別管理できるけど、Git管理できないのでやる意味がない
  • 特に認証とか考えなければAppSyncのリソースだけ定義すればよいのでシンプル
このスクラップは2023/02/14にクローズされました