AWS Amplify + Reactで既存のLambdaを呼び出す

2022.05.06

既存のLambdaをAWS Amplify + Reactから呼び出してみました。

背景

Step Functionsなどを用いて、Lambdaを単機能で疎結合な構成で実装している場合、他のインタフェースからも呼び出して再利用したい場合があるかと思います。AWS AmplifyにはLambdaを追加定義するamplify add functionコマンドとamplify add apiコマンドで追加したAPIからLambdaを呼び出す@functionディレクティブがありますが、今回はシンプルにReactからaws-sdkLambda Clientを使ってLambdaを呼び出してみました。

解法

import React, { useState } from 'react'
import { Auth } from 'aws-amplify'
import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda'
import Box from '@mui/material/Box'
import TextField from '@mui/material/TextField'
import Button from '@mui/material/Button'

const initialState = {
  lambdaArgs1: '',
  lambdaArgs2: ''
}

const SomeLambdaInvoker = () => {
  const [formState, setFormState] = useState(initialState)

  const setInput = (key, value) => {
    setFormState({ ...formState, [key]: value })
  }

  const invoke = async() => {
    const credentials = await Auth.currentCredentials()
    const client = new LambdaClient({
      credentials: Auth.essentialCredentials(credentials),
      region: process.env.REACT_APP_LAMBDA_REGION
    })
    const input = {
      FunctionName: 'some-existing-lambda-' + process.env.REACT_APP_STAGE,
      Payload: JSON.stringify(formState)
    }
    const command = new InvokeCommand(input)
    const response = await client.send(command)
    if (response) {
      setFormState(initialState)
    } else {
      console.error(response.error)
    }
  }

  return (
    <Box
      sx={{
        p: 2,
        bgcolor: 'background.default',
        display: 'grid',
        gridTemplateColumns: { md: '1fr 1fr' },
        gap: 2,
      }}
    >
      <TextField
        required
        id="lambda-args1"
        label="Lambda関数への引数1"
        type="text"
        onChange={event => setInput('lambdaArgs1', event.target.value)}
        value={formState.lambdaArgs1}
      />
      <TextField
        required
        id="lambda-args2"
        label="Lambda関数への引数2"
        type="text"
        onChange={event => setInput('lambdaArgs2', event.target.value)}
        value={formState.lambdaArgs2}
      />
      <Button
        variant="contained"
        onClick={invoke}
      >
        Submit
      </Button>
    </Box>
  )
}
export default SomeLambdaInvoker

some-existing-lambda-{dev|prod}という名前の既存のLambdaを呼び出す例になっています。

LambdaClientコンストラクタにはAmplifyのAuthモジュールから得たクレデンシャルを指定します。Amplifyの認証を通ったユーザのみにLambdaの呼び出しを許可しています。なお、Amplifyの実行ロールにはlambda:InvokeFunction権限が必要です。ポリシー設定は下記のように行います。Resourceで実行を許可するLambda関数のARNを厳密に絞り込むことをオススメします。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "LambdaInvokeSetting",
            "Effect": "Allow",
            "Action": "lambda:InvokeFunction",
            "Resource": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:some-existing-lambda-dev"
        }
    ]
}

InvokeCommandコンストラクタにFunctionNameで呼び出したいLambda名、PayloadにLambdaで与えたいパラメータを指定し、LambdaClient.send(command)メソッドでLambda呼び出しを実行します。

なお、.envファイルには下記のような設定をしています。環境に合わせて書き換えてください。

REACT_APP_STAGE=dev
REACT_APP_LAMBDA_REGION=ap-northeast-1

Lambdaの方は次のように書かれている想定です。

def lambda_handler(event, context):
    print(event['lambdaArgs1']) # lambdaArgs1にアクセス
    print(event['lambdaArgs2']) # lambdaArgs2にアクセス
    """
     :
     いろいろな処理をする
     :
    """

if __name__ == "__main__":
    lambda_handler({"lambdaArgs1": "", "lambdaArgs2": ""}, {})

まとめ

aws-sdkLambda Clientを使って既存のLambdaを呼び出してみました。Amplifyの実行ロールの権限を絞って、かつ、AmplifyのAuthモジュールと組み合わせることでCognitoで認証したユーザーに限定して既存の特定のLambdaだけを実行させることができます。参考になれば幸いです。