見出し画像

CDKを使ってAPI GatewayとLambdaを作成する


はじめに

前回はCDKの基本的な実行方法を整理しました。
今回はAPIGatwayとLambdaを作成して連携させるCDKを記載します。

作成する構成

APIGatwayのリソースを2つ用意し、/syncの場合はLambdaを同期実行、/asyncの場合は非同期実行とする構成とします


スタックの作成

Lambdaはsrc/sample-lambda.tsで定義します。
APIGatwayについて、同期/非同期で紐付け方が変わります。
違いについてはコードの下に記載します。

import * as cdk from 'aws-cdk-lib'
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Construct } from 'constructs';
import * as path from 'path'

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

    const lambda = new NodejsFunction(this, 'SampleLambda', {
      functionName: 'SampleLambda',
      entry: path.join(__dirname, '../src/sample-lambda.ts'),
      handler: 'handler',
      runtime: cdk.aws_lambda.Runtime.NODEJS_18_X,
      timeout: cdk.Duration.seconds(180),
      bundling: {
        forceDockerBundling: false,
      }
    })

    const api = new apigateway.RestApi(this, 'SampleLambdaApi', { cloudWatchRole: false })

    // 同期呼び出しの設定
    api.root.addResource('sync').addMethod(
      'POST', new apigateway.LambdaIntegration(lambda))

    // 非同期呼び出しの設定
    api.root.addResource('async').addMethod(
      'POST', new apigateway.LambdaIntegration(lambda,
        {
          proxy: false,
          requestParameters: {
            'integration.request.header.X-Amz-Invocation-Type': "'Event'"
          },
          integrationResponses: [
            { statusCode: '202', },
            { selectionPattern: '5\\d{2}', statusCode: '500', }
          ]
        }),
      {
        methodResponses: [
          { statusCode: '202', },
          { statusCode: '500', }
        ],
      })
  }
}

非同期の場合、4点を設定する必要があります。

(1) proxyをfalseにします。これによってAPIGatwayの統合リクエスト設定にあるLambdaプロキシ統合がfalseになります。

(2) requestParametersでLambdaに渡すヘッダにintegration.request.header.X-Amz-Invocation-Typeを'Event'を追加します。この設定によりLambdaが非同期実行されます。 ※(1)を設定しないと本設定でLambdaを実行するとエラーになります

(3) integrationResponsesで202を設定します。同期の場合はLambdaのレスポンスを返せば良いのですが、非同期の場合は自分でレスポンスを設定する必要があり、本設定によって統合レスポンスが設定されます。
なおHTTPで202はリクエスト受理を表し、ここではLambda実行まで行なったことを表します。

(4) methodResponsesで202を設定します。メソッドレスポンスの設定になり、(3)の内容と整合性を取ります。

Lambdaの作成

Lambdaの内部にスリープを入れて処理を送らせることで同期・非同期を区別しやすいコードを作成します。

import { APIGatewayEvent, APIGatewayProxyResult, Context } from 'aws-lambda'

export const handler = async (event: APIGatewayEvent, context: Context): Promise<APIGatewayProxyResult> => {
    console.log(`Event: ${JSON.stringify(event, null, 2)}`)
    console.log(`Context: ${JSON.stringify(context, null, 2)}`)

    const sleep = (time: number) => new Promise((resolve) => setTimeout(resolve, time));
    const log = async () => {
        await sleep(500);
        for (let i = 0; i < 10; i++) {
            console.log('i=' + i);
            await sleep(500);
        }
        return 'test end!'
    };

    // 処理
    const res = await log();

    console.log('return response');
    return {
        statusCode: 200,
        body: JSON.stringify({
            message: 'Finish!',
        }),
    }
}

CDKでデプロイ

cdk deploy --profile プロファイル名

動作確認

同期実行を以下で確認します。

curl -i -X POST エンドポイント/sync -d '{"id": 123456789, "name":"Sample"}'

curlコマンドのレスポンスは以下となり、Cloud WatchからもLambda完了後にレスポンスを返していることが分かります。

HTTP/2 200
content-type: application/json
content-length: 21
date: Sun, 20 Oct 2024 05:06:10 GMT
以下省略

次に非同期実行を確認します。

curl -i -X POST エンドポイント/async -d '{"id": 123456789, "name":"Sample"}'

curlコマンドのレスポンスは以下となり、APIGatwayのレスポンスは即時であり、非同期でLambdaが実行されていることが分かります。

HTTP/2 202
content-type: application/json
content-length: 0
date: Sun, 20 Oct 2024 05:10:55 GMT

まとめ

CDKの構築でLambdaの実行を同期/非同期に切り替えられることが分かりました。
そもそもにおいてLambdaは内部で非同期となるコードを書いてもLambdaが応答を返すタイミングで処理が終了してしまい、非同期側も止まってしまいます。
一方でAPIGatwayはリクエストから29秒以内にレスポンスを返さないとエラーになる制約があり、Lambdaが完了するまで待ち続けることができません。
そのため今回のようにAPIGatwayからLambdaを非同期で実行するか、APIGatway→Lambda→SQS→Lambdaの用にSQSを間に挟む構成になります。
※APIGatway→Lambda→Lambdaは拡張性の面で望ましくない
今回は単純にLambdaが実行できれば良い場合に用いる方法であり、逆に言えばLambdaを実行しっぱなしになるため、業務で必要な事前チェックなどは実現できません。そのため必要に応じてSQSを挟む構成との選択が必要となります。

いいなと思ったら応援しよう!