AWS CDKでAPIGateway/SQSと連携した2層LambdaをTypeScriptで開発してみる

Lambda/API GatewayをAWS SAMを利用して定義していた所に代わり、CDKを使ったプロジェクトがもっと普及してもいいんじゃないかな?と思い、AWS CDK(Typescript)を利用してサーバーレスアプリケーションを開発してみました。
2020.05.05

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

どうも、もこ@札幌オフィスです。

最近CDKにお熱で、Lambda/API GatewayをAWS SAMを利用して定義していた所に代わり、CDKを使ったプロジェクトがもっと普及してもいいんじゃないかな?と思い、AWS CDK(Typescript)を利用してサーバーレスアプリケーションを開発してみました。

作る物

APIGateway / Lambdaを利用して、SQSにキューイングする一般的なサーバーレスアプリケーションを作ってみます。

API Gateway/Lambdaの構成だけでは味気ないので、SQSをイベントソースに処理用のLambdaを実行する部分も作成し、LambdaはTypeScriptで開発できるようにしてきます!

利用するパッケージ

利用するパッケージはこんな感じです。 @aws-cdk/aws-lambda-nodejs でTypeScriptのLambda関数を扱い、 @aws-cdk/aws-lambda-event-sources でSQSからのイベントでLambdaが実行されるように設定します。

@aws-cdk/core: CDKのコア

@aws-cdk/aws-sqs: SQSを作るのに使用
@aws-cdk/aws-apigateway: API Gatewayを作成するのに利用
@aws-cdk/aws-lambda: Lambdaのランタイム指定に使用

@aws-cdk/aws-lambda-nodejs: CDKでLambdaを扱うために使用 まだexperimentalなので注意
@aws-cdk/aws-lambda-event-sources: SQSをイベントソースにしたLambdaを作成するために利用

@aws-cdk/aws-iam: IAMのポリシーを定義するのに利用

プロジェクトツリー

作成したプロジェクトのツリーはこんな感じになりました。

Lambda関数ごとにディレクトリを作成しています。

.
├── README.md
├── bin
│   └── cdk-event.ts
├── lambda # Lambdaは関数ごとにディレクトリを変えて保存、関数ごとにpackage.jsonを保持
│   ├── backend
│   │   ├── index.ts
│   │   ├── package-lock.json
│   │   └── package.json
│   └── front
│       ├── index.ts
│       ├── package-lock.json
│       └── package.json
├── lib
│   └── cdk-event-stack.ts # CDKのメイン部分
├── package-lock.json
├── package.json
├── cdk.json
└── tsconfig.json

CDK

CDKのメイン部分はこんな感じで、コメントアウトを入れているので多く見えますが、実態は数十行ちょっとで定義しています。

コードの詳細はコメントアウトをご参照ください。

import { Stack, Construct, StackProps } from '@aws-cdk/core';
import { Queue } from '@aws-cdk/aws-sqs';
import { Runtime } from '@aws-cdk/aws-lambda';
import { NodejsFunction } from '@aws-cdk/aws-lambda-nodejs';
import { SqsEventSource } from '@aws-cdk/aws-lambda-event-sources';
import { LambdaRestApi } from '@aws-cdk/aws-apigateway';
import { PolicyStatement } from '@aws-cdk/aws-iam';

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

    // フロントのLambda
    const frontLambda = new NodejsFunction(this, 'FrontLambda', {
      entry: 'lambda/front/index.ts',
      handler: 'handler',
      runtime: Runtime.NODEJS_12_X
    });
    // REST API作成、Lambda指定
    const RestAPI = new LambdaRestApi(this, 'FrontAPI', {
      handler: frontLambda
    });

    // SQS作成
    const queue = new Queue(this, 'queue', {});
    // バックエンドのLambda
    const backendLambda = new NodejsFunction(this, 'BackendLambda', {
      entry: 'lambda/backend/index.ts',
      handler: 'handler',
      runtime: Runtime.NODEJS_12_X
    });
    // フロントのLambdaにSQSのURLを環境変数で渡す
    frontLambda.addEnvironment('QUEUE_URL', queue.queueUrl);
    // SQSポリシー追加
    frontLambda.addToRolePolicy(new PolicyStatement({ actions: ['sqs:SendMessage'], resources: [queue.queueArn] }));
    // SQSをイベントソースにLambdaを実行するよう設定
    const eventSource = backendLambda.addEventSource(new SqsEventSource(queue));
  }
}

@aws-cdk/aws-apigateway でAPI Gateway, Lambdaの紐付けも数行で簡単にでき、 @aws-cdk/aws-lambda-nodejs でTypeScriptのトランスコンパイルまでまるっとやってくれて、 @aws-cdk/aws-lambda-event-sources で1行でSQSイベントソース設定ができます。

便利な時代になりましたね。

Lambda関数をTypeScriptにするメリット

TypeScriptを使えるようになったことで、 @types/aws-lambda を使って型がある平和な世界に来ることができました。

import { APIGatewayProxyEvent, APIGatewayEventRequestContext, APIGatewayProxyResult } from 'aws-lambda';
import * as AWS from 'aws-sdk';
const SQS = new AWS.SQS({ region: 'ap-northeast-1' });
const QueueUrl = process.env.QUEUE_URL || '';

export async function handler(
  event: APIGatewayProxyEvent,
  context: APIGatewayEventRequestContext
): Promise<APIGatewayProxyResult> {
  try {
    const sendMessage = await SQS.sendMessage({ MessageBody: JSON.stringify(event.body), QueueUrl }).promise();
    return {
      statusCode: 200,
      body: JSON.stringify(sendMessage)
    };
  } catch (error) {
    console.log(error);
    return { statusCode: 502, body: JSON.stringify({ message: error }) };
  }
}

aws-lambda-nodejsの仕組みについてはAWS CDKでLambda Function用のTypeScriptのバンドルを簡単に行うをご参照ください。

@aws-cdk/aws-lambda-event-sourcesで使えるイベントソース

CDKと@aws-cdk/aws-lambda-event-sources を使うと何も考えなくても連携でて最高です。

2020年5月時点で下記の主要5個のサービスが対応しており、最終的にはサポートされているイベントソースすべてで対応することを目標にしているそうです!

  • SQS
  • S3
  • SNS
  • DynamoDB Streams
  • Kinesis

まとめ

これまでAWS SAMを利用してきましたが、(もちろん状況によるとは思いますが)AWS CDKの方が簡単に、わかりやすく柔軟にサーバーレスアプリケーションを管理、開発、デプロイすることができるようになりました。

LambdaのTypeScript化もトランスコンパイルするスクリプトを別で実行して、、、などの組み込みが不要になり、より簡単にモダンな感じで開発していくことができます。

めちゃめちゃ簡単に出来てしまって内容が短くなってしまいましたが、今回作成したものはGitHubで公開しているので、どんな感じで管理できるのかを試してみたい方は、是非一度ご覧ください!

https://github.com/mokocm/cdk-sqs-serverless-project

参考

@aws-cdk/aws-lambda-event-sources

aws-samples/aws-cdk-examples