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
});
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");
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]
})
]
})
}
});
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"
};
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);
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");
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!