Amazon API Gateway + Step Functions Express Workflowで同期実行可能なREST APIを作ってみた(AWS CDK v2)

2022.07.04

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

こんにちは、CX事業本部 IoT事業部の若槻です。

Amazon API GatewayのAWS Integrationでは、Lambda以外にも様々なAWS ServiceをAPIに統合して、公開することが可能です。

今回は、Amazon API GatewayとStep Functions Express Workflowを統合して同期実行可能なREST APIを作ってみました。

やってみた

下記のような構成を作成します。

実装

AWS CDK v2(TypeScript)で次のCDKスタックを作成します。

lib/cdk-app-stack.ts

import { Construct } from 'constructs';
import {
  aws_apigateway,
  aws_stepfunctions,
  Stack,
  StackProps,
} from 'aws-cdk-lib';

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

    const stateMachine = new aws_stepfunctions.StateMachine(
      this,
      'stateMachine',
      {
        stateMachineName: 'expressWorkflow',
        stateMachineType: aws_stepfunctions.StateMachineType.EXPRESS,
        definition: new aws_stepfunctions.Pass(this, 'pass', {
          parameters: {
            key1: 'val1',
            key2: aws_stepfunctions.JsonPath.stringAt('$'),
          },
        }),
      },
    );

    const restApi = new aws_apigateway.RestApi(this, 'restApi');

    restApi.root.addMethod(
      'GET',
      aws_apigateway.StepFunctionsIntegration.startExecution(stateMachine),
    );

    restApi.root
      .addResource('{id}')
      .addMethod(
        'POST',
        aws_apigateway.StepFunctionsIntegration.startExecution(stateMachine),
      );
  }
}
  • aws-cdk-lib » aws_apigatewayに用意されているStepFunctionsIntegrationというClassを使えば、REST APIのMethodにStepFunctionsのIntegrationを簡単に設定することができます。
  • WorkflowのOutputは、key1は固定値val1key2はInputを指定して返すようにしています。

上記をCDK Deployしてスタックをデプロイします。

動作確認

API GatewayのコンソールからREST APIにリクエストをしてみます。

まず/に対してGETリクエストを行うと、Response BodyでWorkflow実行のOutputを同期的に取得できていますね。

次に/{id}に対してPOSTリクエストを行います。Pathパラメータ、QueryStringパラメータおよびBodyがWorkflowのInputとして使用できていますね。

StepFunctionsIntegration Classにより設定されるMapping Template

今回AWS CDKのStepFunctionsIntegration Classを使用しましたが、これによりMethodにMapping Templateが設定され、REST APIとStep Functionsの間のリクエスト/レスポンス時の入出力の変換を適切に行なってくれるようになります。

Integration Requestでは、次のようなMapping Templateが作成されます。リクエスト時のbody、header、pathそしてquerystringがStep Functionsにシンプルな形式で渡され、WorkflowのInputとして使えるようになります。

Integration Request

## Velocity Template used for API Gateway request mapping template
##
## This template forwards the request body, header, path, and querystring
## to the execution input of the state machine.
##
## "@@" is used here as a placeholder for '"' to avoid using escape characters.

#set($inputString = '')
#set($includeHeaders = false)
#set($includeQueryString = true)
#set($includePath = true)
#set($includeAuthorizer = false)
#set($allParams = $input.params())
{
    "stateMachineArn": "arn:aws:states:ap-northeast-1:XXXXXXXXXXXX:stateMachine:expressWorkflow",

    #set($inputString = "$inputString,@@body@@: $input.body")

    #if ($includeHeaders)
        #set($inputString = "$inputString, @@header@@:{")
        #foreach($paramName in $allParams.header.keySet())
            #set($inputString = "$inputString @@$paramName@@: @@$util.escapeJavaScript($allParams.header.get($paramName))@@")
            #if($foreach.hasNext)
                #set($inputString = "$inputString,")
            #end
        #end
        #set($inputString = "$inputString }")
        
    #end

    #if ($includeQueryString)
        #set($inputString = "$inputString, @@querystring@@:{")
        #foreach($paramName in $allParams.querystring.keySet())
            #set($inputString = "$inputString @@$paramName@@: @@$util.escapeJavaScript($allParams.querystring.get($paramName))@@")
            #if($foreach.hasNext)
                #set($inputString = "$inputString,")
            #end
        #end
        #set($inputString = "$inputString }")
    #end

    #if ($includePath)
        #set($inputString = "$inputString, @@path@@:{")
        #foreach($paramName in $allParams.path.keySet())
            #set($inputString = "$inputString @@$paramName@@: @@$util.escapeJavaScript($allParams.path.get($paramName))@@")
            #if($foreach.hasNext)
                #set($inputString = "$inputString,")
            #end
        #end
        #set($inputString = "$inputString }")
    #end
    
    #if ($includeAuthorizer)
        #set($inputString = "$inputString, @@authorizer@@:{")
        #foreach($paramName in $context.authorizer.keySet())
            #set($inputString = "$inputString @@$paramName@@: @@$util.escapeJavaScript($context.authorizer.get($paramName))@@")
            #if($foreach.hasNext)
                #set($inputString = "$inputString,")
            #end
        #end
        #set($inputString = "$inputString }")
    #end

    #set($requestContext = "")
    ## Check if the request context should be included as part of the execution input
    #if($requestContext && !$requestContext.empty)
        #set($inputString = "$inputString,")
        #set($inputString = "$inputString @@requestContext@@: $requestContext")
    #end

    #set($inputString = "$inputString}")
    #set($inputString = $inputString.replaceAll("@@",'"'))
    #set($len = $inputString.length() - 1)
    "input": "{$util.escapeJavaScript($inputString.substring(1,$len))}"
}

Integration Responseでも、次のようなMapping Templateが作成されます。これによりoutputのみがResponse Bodyとして返るようになります。

Integration Response

#set($inputRoot = $input.path('$'))
#if($input.path('$.status').toString().equals("FAILED"))
#set($context.responseOverride.status = 500)
{
"error": "$input.path('$.error')",
"cause": "$input.path('$.cause')"
}
#else
$input.path('$.output')
#end

StepFunctionsIntegration Class、めちゃくちゃ優秀ですね!

おわりに

Amazon API Gateway + Step Functions Express WorkflowでREST APIを作ってみました。

Step Functionsは9000以上のAWS API Actionを直接実行できるので、今回の実装を活用すればLambda要らずがさらに加速しますね!

参考

以上