AWS CDKでAppSyncを構築してみた

この記事は[弥生 Advent Calendar 2022](https://qiita.com/advent-calendar/2022/yayoi)の18日目の記事です。

こんちには、2022年7月に入社したバックエンドエンジニアの永野です。

弥生にはエンジニアリンググループという取り組みがあり、
そこで検証したAWS CDKでAppSyncを構築する方法について、ご紹介しようと思います。

なお、今回はAWS CDKとAppSync、またGraphQLについての説明は割愛させていただきます。
すでに詳しい記事が世に沢山あることと、そもそも私自身が実は上記に触るのが初めてで、知るより慣れろということで、手を動かすことをメインに取り組みを進めてきたためです。

# エンジニアリンググループとは
技術情報やノウハウの蓄積／共有を行い、エンジニアを育成するためのバーチャルグループです。
毎週水曜日に2～3時間ほど活動を行っています。

活動内容はグループごとに決めています。私が参加しているグループでは、直近はAWSのスキルアップを目的に、各自がハンズオンの実施やサービスの調査・検証などを行い、その結果の共有や意見交換を行っております。

ちなみに、この活動のために限らずですが、弥生では申請すればAWSの勉強用環境を利用することができるので、AWS利用コストを気にせず自己学習を進めることができます。

# 今回実装するもの
AppSyncで下記2つのコマンドを持つGraphQL APIを実装します。

#### 1. addBookFromIsbnミューテーション  
リクエストで送ったISBNコードでGoogleBooksAPIから書籍情報を取得して、それをDynamoDBに登録する操作を行うコマンドです。
データソースとしてLambdaを使用して、LambdaHandlerの中で必要な処理を行います
#### 2. allBooksクエリ
DynamoDBに登録されている全書籍データを取得するコマンドです。
データソースとしてDynamoDBを使用して、AppSyncから直接データを取得して呼び出し元に返します。

![AppSyncDemo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679719/48a5c5b7-e704-7bb4-48f7-1bd69a34420f.png)



# 事前準備
[AWS CDK の使用を開始する 入門ガイド](https://aws.amazon.com/jp/getting-started/guides/setup-cdk/module-one/)に記載のとおり、CDKを使用するためにAWS CLIとNode.JSのインストールが事前に必要です。
今回はWindowsマシーンで開発を行うので、Windowsで使用できるパッケージマネージャのWingetでインストールしていきます。

## Node.JSのインストール
```powershell
winget install -e --id OpenJS.NodeJS.LTS
```
## AWS CLIのインストール
```powershell
winget install -e --id Amazon.AWSCLI
```

認証情報は、[名前付きプロファイル](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-profiles.html)を利用して保存しておきます。
また、今回はAWS SSOで認証するので、`aws sso configure`コマンドを実行し、プロンプトに対話的に表示される設問に対して入力していきます。([参考](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-sso.html#sso-configure-profile-auto))
```powershell
aws configure sso --profile enggrp 
```
設定後はログインしている状態となりますが、時間が経ってから利用する場合は、
`aws sso login`コマンドでログインをしておいてください。
```powershell
aws sso login --profile enggrp
```


# CDKプロジェクトの作成
さて、事前準備が終わったところで、さっそくCDKを利用するためのプロジェクトを作成していきます。

まずは、今回プロジェクトを作成するフォルダの中で、`cdk init`コマンドを使用してプロジェクトを作成します。
```powershell
npx cdk@latest init app --language typescript
```

実行するとフォルダの中にCDKのプロジェクトが作成されます。
自動作成されたフォルダとファイルに、いくつか手動で追加して今回使用する全てのファイルとなります。
（手動追加と記載しているものは、後述の中で作成していくものです）

```bash
📦eng-grp-cdk-appsync
 ┣ 📂bin
 ┃ ┗ 📜eng-grp-cdk-appsync.ts
 ┣ 📂graphql                          # 手動追加
 ┃ ┗ 📜schema.graphql                 # 手動追加
 ┣ 📂lambda                           # 手動追加
 ┃ ┣ 📜appsync-resolver.ts            # 手動追加
 ┃ ┗ 📜graphql.ts                     # 手動追加(コードジェネレータで生成する)
 ┣ 📂lib
 ┃ ┗ 📜eng-grp-cdk-appsync-stack.ts
 ┣ 📂node_modules
 ┣ 📂test
 ┃ ┗ 📜eng-grp-cdk-appsync.test.ts
 ┣ 📜.gitignore
 ┣ 📜.npmignore
 ┣ 📜cdk.json
 ┣ 📜codegen.ts                       # 手動追加
 ┣ 📜jest.config.js
 ┣ 📜package-lock.json
 ┣ 📜package.json
 ┣ 📜README.md
 ┗ 📜tsconfig.json
```


プロジェクトの作成が終わったら、最後に[AWSアカウントのブートストラップ](https://aws.amazon.com/jp/getting-started/guides/setup-cdk/module-two/#Bootstrapping_your_AWS_Account)を行います。
```powershell
npx cdk bootstrap --profile enggrp
```

## 追加パッケージのインストール
今回の実装や作業で使用するnpmパッケージを追加していきます。

### [aws-cdk/aws-appsync-alpha](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-appsync-alpha-readme.html)
今回AppSyncを構築するにあたって、[L2コンストラクト](https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/constructs.html)を使用したいです。
CDKはV2から、安定版(stable)のAPIは`aws-cdk-lib`パッケージに全リソースのものが含まれています。
一方で、実験的な(experimental)APIは、`aws-cdk-lib`には含まれず、別のパッケージで提供されます。([参考](https://aws.amazon.com/jp/blogs/news/experimental-construct-libraries-are-now-available-in-aws-cdk-v2/))
AppSyncのL2コンストラクトはまだ、experimentalなものしかないので、そちらを追加でインストールします。
```powershell
npm install @aws-cdk/aws-appsync-alpha
```
このように末尾に`-alpha`と付いているものが、experimental apiになります。

### esbuild
[NodejsFunctionコンストラクト](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html)が、TypeScriptで実装したLambdaHandlerをトンランスコンパイルやバンドルをするために使用します。

今回、LambdaについてもTypeScriptを使用して実装します。
CDKのNodejsFunctionコンストラクトを使うとTypeScriptで実装したコードに対して、自動でトランスコンパイルやバンドルをしたLambda関数を作成してくれるのです。楽ちんです。

そのコンパイル作業をDockerコンテナ内で実行させることもできるのですが、今回はローカルにesbuildをインストールして使用して行います。
esbuildがインストールされている場合、自動でこちらを使用します。

```powershell
npm install -D esbuild
```

### types/aws-lambda
TypescriptでLambda関数ハンドラーを扱うための型定義をインストールします。
```powershell
npm install -D @types/aws-lambda
```
###  aws-sdk dynamodb関連パッケージ
今回作成するLambda関数の中でDynamoDBを扱うため、
DynamoDB操作のクライアントとユーティリティをインストールします
```powershell
npm install @aws-sdk/client-dynamodb @aws-sdk/util-dynamodb
```
### axios
HTTPクライアントとしてaxiosをインストールします。
Lambda関数内で、GoogleBookAPIとのやり取りをするために使用します。
```powershell
npm install axios
````
### コードジェネレータ
GraphQLスキーマからTypeScriptの型定義のコードを作成するために使用するパッケージをインストールします。
```powershell
npm install -D @graphql-codegen/cli @graphql-codegen/typescript
```


# GraphQLスキーマの定義
まず、今回AppSyncを使って作成するGraphQL APIのスキーマ定義を外部ファイルとして作成しておきます。
CDKでリソースを作成する際に、こちらのファイルを読み込んでAppSyncのスキーマ定義を実装します。
また、Lambdaを作成する際に、スキーマ定義からTypeScriptの型定義を生成するためにもこのファイルを使用します。
```graphql
# graphql/schema.graphql

scalar AWSDate

type Book {
  id: ID!
  title: String!
  subtitle: String
  isbn: String
  authors: [String]
  publishedDate:AWSDate
  thumbnailLink: String
}

type Mutation {
  addBookFromIsbn(isbn: String!): Book
}

type Query {
  allBooks: [Book]
}
```

`AWSDate`はAppSyncでデフォルトで使用できる日付型のスカラー定義です。  
スキーマ定義上で型として使用する分には、いちいちスカラー定義を再度しなくても使用できるのですが、今回は後述で出てくるコードジェネレータに認識させるために定義をしています。  
AppSyncで使用できるスカラー型は[こちら](https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/scalars.html)に記載があります。


# LambdaHandlerの実装
次にLambda関数としてデプロイするLambdaHandlerを実装してしまいます。
今回のLambdaは、受け取ったISBNをもとに、Google Books APIに問い合わせをして書籍情報を取得してDynamoDBに登録する処理を行います。

## GraphQLスキーマからTypeScriptの型定義を作成
まず、コードジェネレータというツールを使って、GraphQLスキーマからTypeScriptの型定義のコードを作成します。

### 設定ファイルの作成
コードジェネレータの挙動を決める設定ファイルをプロジェクトルートに作成します。
作成したソースは下記になります。
<details><summary>codegen.ts</summary><div>

```typescript  
import { CodegenConfig } from "@graphql-codegen/cli";

const config: CodegenConfig = {
  overwrite: true,
  schema: "./graphql/schema.graphql", 
  generates: {
    "./lambda/graphql.ts": { 
      plugins: ["typescript"], 
    },
  },
  config: {
    skipTypename: true,
    useTypeImports: true,
    scalars: {
      AWSDate: "Date",　
    },
  },
};

export default config;
```
</div></details>

　
少しピックアップして見てみます。
    
#### スキーマ情報の取得先パスを指定    
```typescript
  schema: "./graphql/schema.graphql", 
```
今回は、スキーマ情報の取得元として、前の手順で作成したGraphQLのスキーマ定義を記載しているファイルを指定します。  

本来は、AppSyncを先に作成して公開したエンドポイントを取得元に指定して、各種サーバやクライアント側で使用する型定義を生成するのが正しそうですが、今回はLambdaも同時にデプロイする都合上、ローカルのファイルに定義しているスキーマ情報から型を生成します。

#### 生成されるファイルパスと生成に使用するプラグインの指定
```typescript
  generates: {
    "./lambda/graphql.ts": { 
      plugins: ["typescript"], 
    },
  },      
```    
  
generatesには、生成されるファイルのパスをキーに、使用するプラグインの情報などを記載していきます。
つまり、複数のプラグインを使って別々のファイルを同時に生成することも可能です。
プラグインについては、今回Lambdaの中ではスキーマに基づく型定義しか使用しないので、[@graphql-codegen/typescript](https://the-guild.dev/graphql/codegen/plugins/typescript/typescript) のみを使用します。

#### オプション設定を指定
```typescript
  config: {
    skipTypename: true,
    useTypeImports: true,
    scalars: {
      AWSDate: "Date",　
    },
  },    
```    
生成にかかわるオプション設定を指定していきます。    

ポイントになるのは`scalars`で、これは定義したスカラーをTypeScript上のどの型に変換するかを指定します。  
今回はコードジェネレータがデフォルトで認識できない、AWSDateをDate型に変換するように指示しています。他の型はデフォルトで変換される型が決まっているので特に指定不要です。

### ソースコードの生成を実行
設定ファイルが準備できたら、下記コマンドを使用して、コードを生成を実行します。
```powershell
npx graphql-codegen --config codegen.ts　
```
実行の結果*lambda/graphql.ts*が生成されます。この中にTypeScriptで使用できる型定義が含まれているので、LambdaHandlerのソースコードにインポートして使用します。

## Lamdba Handlerの実装
次にLambda  Handlerとして使用するソースコードを作成します。
Lambda  Handlerのソースは、今回はCDKのプロジェクト内に作成して、後々スタックのコードから相対パスで指定します。

作成したソースは下記になります。
<details><summary>lambda/appsync-resolver.ts
</summary><div>

```typescript
import { AppSyncResolverHandler } from "aws-lambda";
import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";
import { marshall } from "@aws-sdk/util-dynamodb";
import axios from "axios";
import { randomUUID } from "crypto";
import { Book, MutationAddBookFromIsbnArgs } from "./graphql";

const httpClient = axios.create({
  baseURL: "https://www.googleapis.com/books",
});
const dbClient = new DynamoDBClient({});

export const handler: AppSyncResolverHandler<
  MutationAddBookFromIsbnArgs,
  Book
> = async (event, context) => {
  try {
    const response = await httpClient.get<GoogleApiResponseData>(
      "/v1/volumes",
      {
        params: {
          q: `isbn:${event.arguments.isbn}`,
        },
      }
    );
    const responseData = response.data.items[0];
    if (!responseData) {
      throw new Error("GoogleBookApiからデータが取得できません");
    }
    // DynamoDbに登録
    let data: Book = {
      id: randomUUID(),
      title: responseData.volumeInfo?.title,
      subtitle: responseData.volumeInfo?.subtitle ?? "",
      isbn: event.arguments.isbn,
      authors: responseData.volumeInfo?.authors ?? [],
      publishedDate: responseData.volumeInfo?.publishedDate ?? null,
      thumbnailLink: responseData.volumeInfo?.imageLinks?.thumbnail ?? "",
    };
    await dbClient.send(
      new PutItemCommand({
        TableName: "books",
        Item: marshall(data),
      })
    );
    return data;
  } catch (err) {
    console.error(err);
    throw new Error(`データ登録に失敗しました。`);
  }
};
interface GoogleApiResponseData {
  items: BookItem[];
  kind: string;
  totalItems: number;
}

interface BookItem {
  id: string;
  volumeInfo: {
    title: string;
    subtitle?: string;
    authors?: string[];
    publishedDate?: Date;
    imageLinks?: {
      thumbnail?: string;
    };
  };
}
```

</div></details>  

内容については、今回のメインから外れるので割愛しますが、
ポイントとしては、コードジェネレータを使用して生成した型を使うことで、handlerの引数と戻り値の型定義を、スキーマで定義した *addBookFromIsbn* Mutationの型定義と一致させられることです。

```typescript
export const handler: AppSyncResolverHandler<MutationAddBookFromIsbnArgs, Book> 
```
handerとしてexportする関数をAppSyncResolverHandler型で定義して、1つ目の型引数にMutationの引数の型を、2つ目に戻り値の型を指定しています。
    
# スタックの実装
いよいよ[スタック](https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/stacks.html)のソースコードに対して、作成するリソースの定義を追加していきます。
作成したソースは下記になります。
<details><summary>lib/eng-grp-cdk-appsync-stack.ts
</summary><div>

```typescript
import {
  Duration,
  Expiration,
  RemovalPolicy,
  Stack,
  StackProps,
} from "aws-cdk-lib";
import {
  AuthorizationType,
  FieldLogLevel,
  GraphqlApi,
  MappingTemplate,
} from "@aws-cdk/aws-appsync-alpha";
import { Construct } from "constructs/lib";
import { AttributeType, Table } from "aws-cdk-lib/aws-dynamodb";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
import { RetentionDays } from "aws-cdk-lib/aws-logs";
import { SchemaFile } from "@aws-cdk/aws-appsync-alpha/lib/schema";

export class EngGrpCdkAppsyncStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // DynamoDB
    const table = new Table(this, "DynamoDbTable", {
      tableName: "books",
      partitionKey: {
        name: "id",
        type: AttributeType.STRING,
      },
      removalPolicy: RemovalPolicy.DESTROY,
    });

    // Lambdaを定義
    const lambdaHandler = new NodejsFunction(this, "LambdaHandler", {
      entry: "lambda/appsync-resolver.ts",
      logRetention: RetentionDays.ONE_DAY,
    });
    // dynamoDbのread/write権限を付与
    table.grantReadWriteData(lambdaHandler);

    // AppSyncリソースを定義
    const api = new GraphqlApi(this, "GraphqlApi", {
      name: "eng_group_cdk_demo",
      schema: SchemaFile.fromAsset("./graphql/schema.graphql"), // プロジェクトルートからの相対パス
      authorizationConfig: {
        defaultAuthorization: {
          authorizationType: AuthorizationType.API_KEY,
          apiKeyConfig: {
            expires: Expiration.after(Duration.days(365)),
          },
        },
      },
      logConfig: {
        fieldLogLevel: FieldLogLevel.ALL,
        retention: RetentionDays.ONE_DAY,
      },
    });

    // Query「allBooks」とDynamoDBを関連付けるデータソースを作成
    const dataSource1 = api.addDynamoDbDataSource("DataSource1", table);
    // リゾルバを定義
    dataSource1.createResolver("Resolver1", {
      typeName: "Query",
      fieldName: "allBooks",
      requestMappingTemplate: MappingTemplate.dynamoDbScanTable(),
      responseMappingTemplate: MappingTemplate.dynamoDbResultList(),
    });

    // Mutation「addBookFromIsbn」とLambdaを関連付けるデータソースを作成
    const dataSource2 = api.addLambdaDataSource("DataSource2", lambdaHandler);
    // リゾルバを定義
    dataSource2.createResolver("Resolver2", {
      typeName: "Mutation",
      fieldName: "addBookFromIsbn",
    });
  }
}
```

</div></details>    
   
 　
分解して見ていきます。

## DynamoDBの定義を追加
*book* という名前のテーブルを定義します。
```typescript
const table = new Table(this, "DynamoDbTable", {
  tableName: "books",
  partitionKey: {
    name: "id",
    type: AttributeType.STRING,
  },
  removalPolicy: RemovalPolicy.DESTROY,
});
```

## Lambda関数の定義を追加
前述したとおり、
[NodejsFunctionコンストラクト](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html)を使用することで、TypeScriptで実装したLambdaHandlerのソースコードをトンランスコンパイルやバンドルした上でLambda関数としてデプロイしてくれます。

`entry`には、前述のステップで作成したLambda Handlerのソースコード(`lambda/appsync-resolver.ts`)のパスをプロジェクトルートからの相対パスで記載します。
```typescript
const lambdaHandler = new NodejsFunction(this, "LambdaHandler", {
  entry: "lambda/appsync-resolver.ts",
  logRetention: RetentionDays.ONE_DAY,
});
```

## DynamoDbテーブルのRead/Write権限をLambdaのロールに付与
今回Lambda関数からDynamoDBへの操作が必要なため、
作成した *book*テーブルの読み取り/書き込み権限をLamdba関数のロールに付与します。
ここまで特にロールの定義は出てきていませんが、Lambda関数の定義時にロールの指定を省略した場合は自動で作成されています。
```typescript
table.grantReadWriteData(lambdaHandler);
```

## AppSync GraphQL APIの定義を追加
GraphQL APIには、スキーマの定義を持たせる必要があります。
今回は前述のステップで作成した定義ファイル(`graphql/schema.graphql`)を読み込んで設定するようにしています。
    
また、今回APIの認証方式としては、APIキー方式を使用しています。
他にもAWS Cognitoと連携して認証を行うことなども可能です。

```typescript
const api = new GraphqlApi(this, "GraphqlApi", {
  name: "eng_group_cdk_demo",
  schema: SchemaFile.fromAsset("./graphql/schema.graphql"), 
  authorizationConfig: {
    defaultAuthorization: {
      authorizationType: AuthorizationType.API_KEY,
      apiKeyConfig: {
        expires: Expiration.after(Duration.days(365)),
      },
    },
  },
  logConfig: {
    fieldLogLevel: FieldLogLevel.ALL,
    retention: RetentionDays.ONE_DAY,
  },
});
```

## *allBooks*クエリとDynamoDBを関連付けるリゾルバを作成
スキーマで定義した*allBooks*クエリとデータソースとして指定するDynamoDBの*book*テーブルを解決するためのリゾルバを定義します。
    
リゾルバでは、VTL言語を使用して[マッピングテンプレート](https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/resolver-mapping-template-reference-overview.html) という、GraphQL リクエスト及びレスポンスとバックエンドのデータを変換する方法の定義を記載しないといけないのですが、AppSyncのL2コンストラクタは、単純なものであれば記述なしでマッピングテンプレートを生成することも可能です。楽ちんです。
```typescript
const dataSource1 = api.addDynamoDbDataSource("DataSource1", table);
dataSource1.createResolver("Resolver1", {
  typeName: "Query",
  fieldName: "allBooks",
  requestMappingTemplate: MappingTemplate.dynamoDbScanTable(),
  responseMappingTemplate: MappingTemplate.dynamoDbResultList(),
});

```
    
## *addBookFromIsbn*ミューテーションとDynamoDBを関連付けるリゾルバを作成
スキーマで定義した*addBookFromIsbn*ミューテーションとデータソースとして指定するLambda関数を解決するためのリゾルバを定義します。
    
データソースにLambda関数を使用する場合は、[Direct Lambda Resolvers](https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/direct-lambda-reference.html) を使用することで、マッピングテンプレート定義を省略できるので、テンプレートの指定は不要です。    
```typescript
const dataSource2 = api.addLambdaDataSource("DataSource2", lambdaHandler);
dataSource2.createResolver("Resolver2", {
  typeName: "Mutation",
  fieldName: "addBookFromIsbn",
});
```    
    
## リソースの作成を実行    
ここまでで必要なソースコードは揃ったので、`cdk deploy`コマンドを使用して実際にAWS上にリソースを作成します。
```powershell
npx cdk deploy --profile enggrp
```    

コマンドを実行するとプロンプト上に作成されるリソースの変更差分と、
`Do you wish to deploy these changes (y/n)? `のメッセージが表示されます。
問題なければ、`y`を入力してエンターを押下します。
    
特にエラーが発生しなければ、これで定義したリソースがAWS上に構築されます。
    
# ローカル端末からリクエストを送信
最後に動作確認として、ローカル端末からリクエストを送ってみます。
リクエストの送信にはPowershell Coreの`Invoke-RestMethod`コマンドレットを使用します。

まず、作成したGraphQLAPIのエンドポイントURIとAPIキーをAWS CLIを使用して確認します。
```powershell
# API IDとUrlを確認
aws appsync list-graphql-apis --query "graphqlApis[?name=='eng_group_cdk_demo'].{apiId:apiId,uri:uris.GRAPHQL}"  --profile enggrp
# API IDを使用してAPI KEYを確認
aws appsync list-api-keys --api-id ${取得したAPI ID} --profile enggrp      
```

確認したURIとAPIキーを埋め込みつつ、下記スクリプトをPowershellで実行します
   
```powershell
$uri = "${確認したAPI URL} "
$headers = @{
    "X-API-KEY" = "${確認したAPI KEY} "
}
$contentType = "application/json"

$allBooksQuery = @{"query" = @"
query {
    allBooks {
      id
      title
      subtitle
      isbn
      authors
      publishedDate
      thumbnailLink
    }
}
"@
} | ConvertTo-Json

$addBookFromIsbn = @{"query" = @"
mutation {
    addBookFromIsbn(isbn:"@isbn") {
      id
      title
      subtitle
      isbn
      authors
      publishedDate
      thumbnailLink
    }
  }
"@
} | ConvertTo-Json 

# 2つのISBNコードで書籍登録Mutationを実行
Invoke-RestMethod -Method Post -Uri $uri -ContentType $contentType -Headers $headers -Body ($addBookFromIsbn -replace "@isbn","9784822262969")  > $null
Invoke-RestMethod -Method Post -Uri $uri -ContentType $contentType -Headers $headers -Body ($addBookFromIsbn -replace "@isbn","9784295010654")  > $null

# 全書籍取得Queryを実行して結果を表示
(Invoke-RestMethod -Method Post -Uri $uri -ContentType $contentType -Headers $headers -Body $allBooksQuery) | ConvertTo-Json -Depth 10
```    
    
実行して、allBooksクエリが下記結果を返したことを確認できました。
<details><summary>allBooksクエリの結果
</summary><div>   
 
```json
{
  "data": {
    "allBooks": [
      {
        "id": "751494de-0769-454a-a4dc-466c479d7bd5",
        "title": "Amazon Web Services基礎からのネットワーク&サーバー構築",
        "subtitle": "さわって学ぶクラウドインフラ",
        "isbn": "9784822262969",
        "authors": [
          "玉川憲",
          "片山暁雄",
          "今井雄太"
        ],
        "publishedDate": "2014-07-22",
        "thumbnailLink": "http://books.google.com/books/content?id=zMzgoAEACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api"
      },
      {
        "id": "0aac1d4a-f622-4b60-a75c-c9b1e151562d",
        "title": "改訂新版 徹底攻略 AWS認定 ソリューションアーキテクト − アソシエイト教科書［SAA-C02］対応",
        "subtitle": "",
        "isbn": "9784295010654",
        "authors": [
          "鳥谷部 昭寛",
          "宮口 光平",
          "菖蒲 淳司"
        ],
        "publishedDate": "2021-01-08",
        "thumbnailLink": "http://books.google.com/books/content?id=a7USEAAAQBAJ&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api"
      }
    ]
  }
}
```    

</div></details>        
     　
また、DynamoDBにデータが登録されているかをAWS CLIで確認します

```powershell
aws dynamodb scan --table-name "books" --profile enggrp
````

こちらもDBにデータが投入されていることを確認できました。
<details><summary>aws dynamodb scanの結果
</summary><div>    

```json
{
    "Items": [
        {
            "thumbnailLink": {
                "S": "http://books.google.com/books/content?id=zMzgoAEACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api"
            },
            "isbn": {
                "S": "9784822262969"
            },
            "subtitle": {
                "S": "さわって学ぶクラウドインフラ"
            },
            "id": {
                "S": "751494de-0769-454a-a4dc-466c479d7bd5"
            },
            "publishedDate": {
                "S": "2014-07-22"
            },
            "title": {
                "S": "Amazon Web Services基礎からのネットワーク&サーバー構築"
            },
            "authors": {
                "L": [
                    {
                        "S": "玉川憲"
                    },
                    {
                        "S": "片山暁雄"
                    },
                    {
                        "S": "今井雄太"
                    }
                ]
            }
        },
        {
            "thumbnailLink": {
                "S": "http://books.google.com/books/content?id=a7USEAAAQBAJ&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api"
            },
            "isbn": {
                "S": "9784295010654"
            },
            "subtitle": {
                "S": ""
            },
            "id": {
                "S": "0aac1d4a-f622-4b60-a75c-c9b1e151562d"
            },
            "publishedDate": {
                "S": "2021-01-08"
            },
            "title": {
                "S": "改訂新版 徹底攻略 AWS認定 ソリューションアーキテクト － アソシエイト教科書［SAA-C02］対応"
            },
            "authors": {
                "L": [
                    {
                        "S": "鳥谷部 昭寛"
                    },
                    {
                        "S": "宮口 光平"
                    },
                    {
                        "S": "菖蒲 淳司"
                    }
                ]
            }
        }
    ],
    "Count": 2,
    "ScannedCount": 2,
    "ConsumedCapacity": null
}
```    

</div></details>       
    
# あとがき
AppSyncを構築する方法を調べるとよく出てくるのはAmplify CLIですが、    
L2コンストラクトを使用すれば、CDKでもかなり簡単に実装できることがわかりました。
これだけ簡単に実装できれば、APIとしてGraphQLを選択する敷居は大分下がるのではないでしょうか。
これから実際のアプリに組み込んだりして使いこなしていこうと思います。