データアナリティクス事業本部の笠原です。
みなさん、AWS CDKでStep Functionsのステートマシンを作る際、どのように作りますか?
一般的には、以下のようなコードで DefinitionBody.fromChainable
を使ってコードで記述するケースが多いかと思います。
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as sfn from 'aws-cdk-lib/aws-stepfunctions';
import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks';
import * as lambda from 'aws-cdk-lib/aws-lambda';
declare const submitLambda: lambda.Function;
declare const getStatusLambda: lambda.Function;
export class SampleStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const submitJob = new tasks.LambdaInvoke(this, 'Submit Job', {
lambdaFunction: submitLambda,
// Lambda's result is in the attribute `guid`
outputPath: '$.guid',
});
const waitX = new sfn.Wait(this, 'Wait X Seconds', {
time: sfn.WaitTime.secondsPath('$.waitSeconds'),
});
const getStatus = new tasks.LambdaInvoke(this, 'Get Job Status', {
lambdaFunction: getStatusLambda,
// Pass just the field named "guid" into the Lambda, put the
// Lambda's result in a field called "status" in the response
inputPath: '$.guid',
outputPath: '$.status',
});
const jobFailed = new sfn.Fail(this, 'Job Failed', {
cause: 'AWS Batch Job Failed',
error: 'DescribeJob returned FAILED',
});
const finalStatus = new tasks.LambdaInvoke(this, 'Get Final Job Status', {
lambdaFunction: getStatusLambda,
// Use "guid" field as input
inputPath: '$.guid',
outputPath: '$.Payload',
});
const definition = submitJob
.next(waitX)
.next(getStatus)
.next(new sfn.Choice(this, 'Job Complete?')
// Look at the "status" field
.when(sfn.Condition.stringEquals('$.status', 'FAILED'), jobFailed)
.when(sfn.Condition.stringEquals('$.status', 'SUCCEEDED'), finalStatus)
.otherwise(waitX));
new sfn.StateMachine(this, 'StateMachine', {
definitionBody: sfn.DefinitionBody.fromChainable(definition),
timeout: Duration.minutes(5),
comment: 'a super cool state machine',
});
}
}
私はASL YAMLで定義をゴリゴリ書きたい人なので、CDKでもASL YAMLで定義できれば最高です。
VS Codeで書ければ、ステートマシンのプレビューも簡単に確認できます。
DevelopersIOでもCDKにASL YAMLファイルを読み込んでステートマシンを構築している記事がありますが、 aws_stepfunctions.CfnStateMachine()
を使ったり、 AWS SAMの aws_sam.CfnStateMachine()
を使ったりしてます。これらは、いずれもL1コンストラクトで定義してます。
私は、L2コンストラクトである aws_stepfunctions.StateMachine()
を使っています。
あまり記事になっていないようなので、今回はL2コンストラクトでの読み込み方を示します。
実行環境
- Node.js: 20.13.1
- aws-cdk: 2.142.1
- typescript: 5.4.5
CDKでの記述
こんな感じで書けます。
lib/sample-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { StateMachine, DefinitionBody } from 'aws-cdk-lib/aws-stepfunctions';
export class SampleStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Lambda
const fn = new lambda.Function(this, 'myFunction', {
// 省略
});
// State Machine
const mainStateMachine = new StateMachine(this, 'mainStateMachine', {
stateMachineName: 'mainStateMachine',
definitionBody: DefinitionBody.fromFile(
'src/step_functions/state_machines/main_state_machine.asl.yaml',
),
definitionSubstitutions: {
myFunctionArn: fn.functionArn,
},
});
}
}
StateMachine()
クラスのInitializerで、Construct Propsの definitionBody
プロパティと definitionSubstitutions
プロパティを使います。
definitionBody
プロパティでは DefinitionBody.fromFile()
メソッドを使って、ASL YAMLファイルを読み込めます。 fromFile()
メソッドでは、ASL JSONファイルも読み込めます。
definitionSubstitutions
プロパティでは ASL YAMLファイル内に記載されたプレースホルダーを置換するマッピングをkey-valueで定義します。
ちなみに、 DefinitionBody.fromFile()
の他に DefinitionBody.fromString()
メソッドもあります。
こちらはASL JSON文字列を渡すことができますが、残念ながらASL YAML文字列を渡すとエラーになります。
ASL YAMLでの記述
ASL YAMLファイルは以下のように、記述します。
プレースホルダーは ${}
で定義します。
src/step_functions/state_machines/main_state_machine.asl.yaml
Comment: >-
Main State Machine
StartAt: MainExec
States:
MainExec:
Type: Task
Resource: ${myFunctionArn}
End: true
まとめ
意外と簡単にL2コンストラクトでも使えます。
どうしてもL1コンストラクトで定義しないといけない場合を除いて、できるだけL2コンストラクトで定義するとコードがスッキリしますね。