tomoima525's blog

Androidとか技術とかその他気になったことを書いているブログ。世界の秘密はカレーの中にある!サンフランシスコから発信中。

AmplifyでひとつのResource内に複数のLambda functionを作る

背景

Amplify で新しい Function を追加すると、CloudFormation 上に新しいスタックが作成されます。プロジェクトの立ち上げ期はどんどん機能追加していくものですが、プロジェクトやチームの規模が大きくなると問題になる可能性があります。CloudFormation では 1 つの AWS アカウントにつき最大 200 のスタックしか作成できないためです。

この問題を回避する方法について少々頭をひねったところ、1 つのリソースで複数の Function をデプロイできるんじゃないかと気づきました。Amplify はさまざまなことを自動化してくれていますが、中身は実質 CloudFormation のテンプレートを生成し実行しているだけなのです。

このアイデアにはもう一つの利点があります。各 Function が同じドメイン(例えば決済など)である場合、同じポリシー/実行ロールやユーティリティ関数を共有し管理しやすくできることです。

以下が実行した結果のスクリーンショットです。2つの Function がひとつのリソース上に生成されていることが確認できます。

f:id:tomoima525:20210430161014p:plain
f:id:tomoima525:20210430161020p:plain

手順

では実際にどうやるか説明していきます。

ディレクトリ構造

ディレクトリ構造は以下のようになります。

├── function
│   └── multiplefunctionec0b1d53
│       ├── function-parameters.json
│       ├── multiplefunctionec0b1d53-cloudformation-template.json
│       └── src
│           ├── functionOne
│           │   ├── event.json
│           │   └── index.js
│           ├── functionTwo
│           │   ├── event.json
│           │   └── index.js
│           ├── package-lock.json
│           └── package.json

各 function 単位ではなく、src 配下に package.json が置かれているのは、 amplify push で build ファイルを生成する際にエラーが発生するからです。Amplify cli が npmを見つけられてないのが原因のようなのですが、いま時点で解決方法はわかってません。

テンプレートの更新

まずは Function を追加したいリソースの cloud-formation.jsonResource セクションに以下を追加します。ここでは LambdaFunctionTwo という名前でリソースを追加していきます。

"Resource": {
    ...,
    "LambdaFunctionTwo": {
      "Type": "AWS::Lambda::Function",
      "Metadata": {
        "aws:asset:path": "./src",
        "aws:asset:property": "Code"
      },
      "Properties": {
        "Code": {
          "S3Bucket": {
            "Ref": "deploymentBucketName"
          },
          "S3Key": {
            "Ref": "s3Key"
          }
        },
        "Handler": "functionTwo/index.handler",  // You must set the path to the actual function
        "FunctionName": {
          "Fn::If": [
            "ShouldNotCreateEnvResources",
            "multiplefunctionec0b1d53",
            {
              "Fn::Join": [
                "",
                [
                  "multiplefunctionec0b1d53",
                  "-",
                  "functionTwo-", // function name should be unique
                  {
                    "Ref": "env"
                  }
                ]
              ]
            }
          ]
        },
        "Environment": {
          "Variables": {
            "ENV": {
              "Ref": "env"
            },
            "REGION": {
              "Ref": "AWS::Region"
            }
          }
        },
        "Role": {
          "Fn::GetAtt": [
            "LambdaExecutionRole",
            "Arn"
          ]
        },
        "Runtime": "nodejs14.x",
        "Layers": [],
        "Timeout": "25"
      }
    }
}

ポイントは Function 名をユニークにすることです。

次に必要なのはこの Function のログの流し先です。以下を lambdaexecutionpolicy に追加します。Reference を LambdaFunctionTwo にすることを忘れないでください。

"lambdaexecutionpolicy": {
  "PolicyDocument": {
    "Resource": [
      {...},
      {
        "Fn::Sub": [
          "arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:*",
          {
            "region": {
              "Ref": "AWS::Region"
            },
            "account": {
              "Ref": "AWS::AccountId"
            },
            "lambda": {
              "Ref": "LambdaFunctionTwo"  // reference to LambdaFunctionTwo
            }
          }
        ]
      }
    ]

あとはオプショナルですが、この Function の名前と Arn を参照できるように Outputs に以下を追加します。

"Outputs": {
  ...,
  "FunctionTwoName": {
    "Value": {
      "Ref": "LambdaFunctionTwo"
    }
  },
  "FunctionTwoArn": {
    "Value": {
      "Fn::GetAtt": [
        "LambdaFunctionTwo",
        "Arn"
      ]
    }
  },
}

一通りの作業が終わったら、 amplify env checkout {今の環境} を実行します。これによって Amplify のバックエンド構成情報が格納されている amplify-meta.json が更新されます。最後に amplify push --y を実行して CloudFormation を展開します。

留意事項

  • 既存のリソースに Function を追加する場合は、既存のリソースの LogicalID(例. LambdaFunction)は変更できないようです。
  • Function を消去する場合は上記で書いた設定をすべて削除すれば OK です。

ソースコード

github.com