銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

AWS SAMテンプレートでREST APIの複数メソッドをひとつのLambdaに統合するには?

f:id:takamints:20191027090344j:plain photo credit: Andreas Komodromos Urban Nights - Hudson Yards, New York City via photopin (license)

ホントちょっとしたことなんですが、SAMのHelloWorldのチュートリアルをやっただけではハッキリわからなくて、少しだけ試行錯誤が必要でしたので書いておきます。

API Gateway REST APIの複数メソッド(GET,PUT,POST,DELETEなど)を、ひとつのLambda関数に統合(Lambdaプロキシ統合)する場合の AWS SAMのテンプレートの書き方です。

API Gatewayのコンソールでは、各メソッドの設定で、同じLambda関数を指定するだけですが、じゃあSAMのテンプレートではどう書くのでしょうか?ってところです。

目次

みんなのobniz入門
みんなのobniz入門
posted with amazlet at 19.10.27
古籏 一浩
リックテレコム (2019-11-08)
売り上げランキング: 24,382

SAMテンプレートの書き方(例)

以下のYamlでリソースパス /hello に対する GETリクエストとPUTリクエストを、Lambda関数 HelloWorldFunction に統合しています。

sam-app/template.yaml:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      Events:
        #ココ↓と
        HelloWorldGet:
          Type: Api
          Properties:
            Path: /hello
            Method: get
        #ココ↓
        HelloWorldPut:
          Type: Api
          Properties:
            Path: /hello
            Method: put

一般化すると、AWS::Serverless::FunctionProperties/Events以下にメソッド単位で別々のApiとして宣言すればよいのです。 APIのリソースパスが同じでもメソッドが別なら複数のApi定義が必要ということですね。 SAMテンプレートでは独立したREST APIを宣言しているのではなくて、あくまでもサーバーレス関数の定義の一部なのでこうなるのでしょう(多分)。

今となっては上の定義が理解できていますが、当初「Events 以下のエントリがAPIのリソースパスを一意に表している」と思い込んでいて、Methodを配列にしてみたりして失敗しました。

Lambdaプロキシ統合のパラメータで分岐する

Lambda関数側では、引数のeventのhttpMethod にHTTPメソッドが文字列で格納されていますので、これによって分岐します。

exports.lambdaHandler = async (event) => {
    try {
        const response = { statusCode: 200 };
        switch(event.httpMethod) {
        case "PUT":
            await put(JSON.parse(event.body));
            break;
        case "GET":
            await get(event.queryStringParameters);
            break;
        }
        return response;
    } catch (err) {
        console.log(err);
        return err;
    }
};

リクエストパラメータは、同じくeventのqueryStringParameterbodyに格納されています。 queryStringParameterはObjectですがbodyは文字列(JSON)なので気を付けましょう。 これらパラメータの使い方はメソッドの設計に依存するので一概に「こうしましょう」とは言えません。 他にも pathParametersheaders を利用することがあるかもしれませんね。

Lambdaプロキシ統合されたLambda関数の入力形式については、以下の公式ページに説明があります。 実際に動かしてみて把握する必要はあるかもしれませんよ。

docs.aws.amazon.com

あとがき

普通はメソッドごとにLambda関数を分けることが多いかもしれません。 そのほうがLambdaでの分岐が不要で多少はパフォーマンスが良いはずですし。

しかし、各メソッド間で同じデータモデルを扱うなど共通処理が必要な場合は一つにまとめるとスッキリすると思います。

共通処理についてはLambda Layersを使うのも一つの解です。 ここでは、「とりあえず手軽にやるには?」という観点でサクッと調べた結果を書いておきました。