DEV Community

loading...
Cover image for Starting Synchronous Express Workflows with API Gateway and CDK

Starting Synchronous Express Workflows with API Gateway and CDK

wojciechmatuszewski profile image Wojciech Matuszewski ・3 min read

Many things were announced before and during this year's AWS re:Invent, but one thing really caught my attention. Mainly the ability to invoke Synchronous Express Workflows directly by using API Gateway HTTP APIs.

This is very exciting as it unlocks orchestration capabilities for synchronous invocations. As a developer, you no longer have to write logic to pool the results of your Step Functions invocations.

While there are many examples of implementing this workflow with AWS SAM, I could not find any for AWS CDK.

Since I'm more of an AWS CDK guy nowadays, I decided to take on the challenge and write a simple implementation myself.

During the implementation, I encountered a few gotchas that caused me some trouble, so I decided to share the process I went through here in hopes of saving you a few minutes out of your day.

This post assumes knowledge of AWS CDK. I will not be explaining the basics concepts around it.

The state machine

Since this is an example, we will keep it as simple as possible. Our state machine will have one Pass state, which returns a static response.

const machineDefinition = new sfn.Pass(this, "passState", {
  result: { value: "Hi there!" }
});

const machine = new sfn.StateMachine(this, "machine", {
  definition: machineDefinition,
  stateMachineType: sfn.StateMachineType.EXPRESS
});
Enter fullscreen mode Exit fullscreen mode

The HTTP API

We need 2 things here—first, a very basic definition of an AWS API Gateway HTTP APIs API.

const api = new apigwv2.HttpApi(this, "api");
Enter fullscreen mode Exit fullscreen mode

Next, something specific to the architecture we are building – an IAM role that will be assumed by the API. The role will grant the API we just created the ability to start synchronous execution of the state machine.

new iam.Role(this, "sfnRole", {
  assumedBy: new iam.ServicePrincipal("apigateway.amazonaws.com"),
  inlinePolicies: {
    AllowSFNExec: new iam.PolicyDocument({
      statements: [
        new iam.PolicyStatement({
          actions: ["states:StartSyncExecution"],
          effect: iam.Effect.ALLOW,
          resources: [machine.stateMachineArn]
        })
      ]
    })
  }
});
Enter fullscreen mode Exit fullscreen mode

As a side-note, we create an IAM role because the AWS Step Functions service does not support Resource-based policies.

The OpenAPI definition

As I eluded earlier, the integration does not require us to write vtl code. What we need to do instead is to provide an OpenAPI definition containing AWS API Gateway integration that wires everything together.

const definition = {
  openapi: "3.0.1",
  info: {
    title: "openapi-definition",
    version: "2020-12-19 12:06:00UTC"
  },
  paths: {
    "/": {
      post: {
        responses: {
          default: {
            description: "Default response for POST /"
          }
        },
        "x-amazon-apigateway-integration": {
          integrationSubtype: "StepFunctions-StartSyncExecution",
          credentials: apiRole.roleArn,
          requestParameters: {
            StateMachineArn: machine.stateMachineArn
          },
          payloadFormatVersion: "1.0",
          type: "aws_proxy",
          connectionType: "INTERNET",
          timeoutInMillis: 30000
        }
      }
    }
  },
  "x-amazon-apigateway-cors": {
    allowMethods: ["*"],
    maxAge: 0,
    allowCredentials: false,
    allowOrigins: ["*"]
  },
  "x-amazon-apigateway-importexport-version": "1.0"
};
Enter fullscreen mode Exit fullscreen mode

Be very careful here. If you make a spelling mistake for any of the properties, the integration will not be created.

The overrides

This is the part where I spent most of my time. As of writing this, the HttpApi construct does not support the Body parameter.
Since the Body is where our definition should be specified, we have to override that parameter manually.

const cfnApi = api.node.defaultChild as apigwv2.CfnApi;
cfnApi.addPropertyOverride("Body", definition);
Enter fullscreen mode Exit fullscreen mode

If you try to deploy the stack now, your deployment will fail, with errors telling you that some properties of the AWS::ApiGatewayV2::Api resource are redundant. The Name and the ProtocolType are populated by the HttpApi construct, but should not be specified if the Body contains OpenAPI definition. Let us remove those.

cfnApi.addPropertyDeletionOverride("Name");
cfnApi.addPropertyDeletionOverride("ProtocolType");
Enter fullscreen mode Exit fullscreen mode

Summary

And that's it. This is all you need to deploy a very simple API Gateway HTTP APIs API with a route that synchronously invokes an express workflow.

I hope you found it useful. Have a great day!

Discussion (0)

pic
Editor guide