【AWS-CDK】認証付きWebAPIをSolutions Constructsを使って手軽に構築

## Solutions ConstructsでAWSインフラ構築
先日、[AWS Solutions Constructs](https://aws.amazon.com/jp/blogs/news/aws-solutions-constructs-a-library-of-architecture-patterns-for-the-aws-cdk/)が発表されました。これはAWS-CDKにおいて、抽象化されたリソースを表す`Constructs`のよく使う組み合わせをWell-Architected準拠の設定込みで一発で構築できるライブラリです。例えば`Lambda + DynamoDB`や`CloudFront + S3`などの組み合わせのパターンが25個(2020/7月現在)提供されています。  

+ https://docs.aws.amazon.com/solutions/latest/constructs/api-reference.html

これをつかってパパっとAWS上にWebAPIを構築したいと思います。

## 注意
2020/7/6現在、Solutions Constructsのstabilityはすべて**Experimental**となっています。
本番環境などでの使用はよく検討した上で行ってください。

## 環境

+ AWS-CDK CLI v1.47.0
  + ※ Solutions ConstrucsはCDK v1.46.0以上に対応しています。
+ TypeScript v3.9.2
+ npm v6.14.5
+ node.js v12.18.0

## 使用するConstructs

今回は以下のSolutions Constructsを使用します。

+ [aws-cognito-apigateway-lambda](https://docs.aws.amazon.com/solutions/latest/constructs/aws-cognito-apigateway-lambda.html)
+ [aws-lambda-dynamodb](https://docs.aws.amazon.com/solutions/latest/constructs/aws-lambda-dynamodb.html)

構成図は以下のとおりです。

![aws-web-api-infra.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119251/e6d3ab36-7919-f6e2-5a37-fad3abd4065f.png)


CDKによって、API Gateway, Lambda, Cognito, DynamoDBテーブルを作成します。
さらに、各リソースのオプションはデフォルトで下記のWell-Architected準拠の設定が施されています。

+ Cognito
    + ユーザープールに対するパスワードポリシーの設定
    + ユーザープールに対するアドバンスドセキュリティの有効化
+ API Gateway
    + エンドポイントのエッジ最適化設定
    + アクセスログの有効化
    + 最小限のIAMロールの設定
    + すべてのメソッドに対するIAM認証設定
+ Lambda
    + 最小限のIAMロールの設定
    + ログ出力の有効化
    + keep-alive時のコネクション再利用設定(Node.jsファンクション)
+ DynamoDB
    + オンデマンドモードの設定
    + AWSマネージドCMKによる暗号化設定

## 作成手順

AWS-CDK CLIをグローバルインストールします。

```
npm install -g aws-cdk
```

インストール後にプロジェクトを作成します。

```
cdk init app --language=typescript
```

今回使用するライブラリをインストールします。(SolutionsConstructsの最新verが1.47.0なので統一)

```
npm install -s @aws-cdk/aws-lambda@1.47.0 @aws-cdk/aws-apigateway@1.47.0 @aws-cdk/aws-dynamodb@1.47.0 @aws-cdk/aws-cognito@1.47.0 @aws-solutions-constructs/aws-cognito-apigateway-lambda@1.47.0 @aws-solutions-constructs/aws-lambda-dynamodb@1.47.0
```

Constructsを使ってCDKを記述します。

```typescript
import * as cdk from "@aws-cdk/core";
import * as lambda from "@aws-cdk/aws-lambda";
import { CognitoToApiGatewayToLambda } from "@aws-solutions-constructs/aws-cognito-apigateway-lambda";
import { LambdaToDynamoDB } from "@aws-solutions-constructs/aws-lambda-dynamodb";
import {
  AttributeType,
  BillingMode,
  TableEncryption,
} from "@aws-cdk/aws-dynamodb";

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

    // The code that defines your stack goes here
    const apigwToLambda = new CognitoToApiGatewayToLambda(
      this,
      "CognitoToApiGatewayToLambdaPattern",
      {
        deployLambda: true,
        lambdaFunctionProps: {
          runtime: lambda.Runtime.NODEJS_12_X,
          handler: "index.handler",
          code: lambda.Code.asset(`${__dirname}/lambda`),
        },
      }
    );

    new LambdaToDynamoDB(this, "LambdaToDynamoPattern", {
      deployLambda: false,
      existingLambdaObj: apigwToLambda.lambdaFunction, // 生成済みのLambdaを使用
      dynamoTableProps: {
        partitionKey: { name: "id", type: AttributeType.STRING },
        // デフォルトの設定は以下の通り
        //   キャパシティーモード: オンデマンド
        //   暗号化タイプ: KMS - AWSマネージドCMK
        // 無料枠で使用するために設定を変更している。
        billingMode: BillingMode.PROVISIONED,
        readCapacity: 5,
        writeCapacity: 5,
        encryption: TableEncryption.DEFAULT,
      },
    });
  }
}
```

lib/lambdaディレクトリにはlambdaのソースを配置します。

```index.js
const { DynamoDB } = require("aws-sdk");

exports.handler = async function (event) {
  console.log("request:", JSON.stringify(event, undefined, 2));

  // create AWS SDK clients
  const dynamo = new DynamoDB();

  // APIのパスをパーティションキーとして、アクセス回数を記録する
  await dynamo
    .updateItem({
      TableName: process.env.DDB_TABLE_NAME,
      Key: { id: { S: event.path } },
      UpdateExpression: "ADD hits :incr",
      ExpressionAttributeValues: { ":incr": { N: "1" } },
    })
    .promise();

  return {
    statusCode: 200,
    headers: { "Content-Type": "text/plain" },
    body: `Hello, AWS Solutions Constructs! You've hit ${event.path}\n`,
  };
};

```

以下のコマンドでインフラをデプロイできます

```
npm run build && cdk deploy
```

`cdk deploy`実行時にリソースのデプロイ確認があるので、yを入力してデプロイを開始しましょう。

API Gatewayのコンソールから`/foo`メソッドのテストをおこなうと正常にレスポンスが返されることが確認できます。

<img width="1436" alt="スクリーンショット 2020-07-08 00.40.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119251/0686483e-7ac0-3e1a-8e57-7903f5c04a4a.png">

また、DynamoDBに、アクセスしたパスの文字列とアクセス回数が記録されていることが確認できます。

<img width="1436" alt="スクリーンショット 2020-07-08 00.41.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119251/e720f9bf-8034-75d0-aa5a-d3fbec3b9fcd.png">

## まとめ

このように、Solution ConstructsではAWSでよく使われるリソースの組み合わせをConstructsをして提供してくれています。それを利用することで、爆速で今までよりもすばやく、かつ、Well-Architectedに準拠したインフラを構築することが可能になりました。  

## 参考

+ [AWS Solutions Constructs - Walkthrough - Part 1](https://docs.aws.amazon.com/solutions/latest/constructs/walkthrough-part-1.html)
+ [AWS Solutions Constructs - Walkthrough - Part 2](https://docs.aws.amazon.com/solutions/latest/constructs/walkthrough-part-2.html)
+ [【AWS CDK】AWS のアーキテクトが作成した Contructs が使える AWS Solutions Constructs が出ました！](http://blog.serverworks.co.jp/tech/2020/06/24/aws-solutions-constructs/)