- 1 Step Functions Express vs Standard完全ガイド — 同一処理で比較するワークフロータイプ選択術
- 1.1 Section 1: Express vs Standard Workflows 概念編
- 1.2 Section 2: アーキテクチャ解説
- 1.3 Section 3: AWSコンソールでのハンズオン
- 1.4 3-1. 前提条件
- 1.5 3-2. Lambda 3関数のデプロイ(モック実装)
- 1.6 3-3. SNSトピック作成
- 1.7 3-4. IAMロール作成
- 1.8 3-5. 段階的構築
- 1.9 まとめ(Section 3)
- 1.10 Section 4: TerraformでのStandard/Express構築
- 1.11 4-1. 前提条件
- 1.12 4-2. ディレクトリ構成
- 1.13 4-3. Lambda ソースコード(コンソール版と同一ロジック)
- 1.14 4-4. statemachine/definition.json.tpl
- 1.15 4-5. variables.tf
- 1.16 4-6. main.tf(完全なコード)
- 1.17 4-7. outputs.tf
- 1.18 4-8. デプロイ手順
- 1.19 4-9. 動作確認(Standard版)
- 1.20 4-10. 動作確認(Express版)
- 1.21 4-11. Standard vs Express の比較確認
- 1.22 クリーンアップ
- 1.23 まとめ(Section 4)
- 1.24 Section 5: 実践Tips(Express vs Standard設計ガイド)
- 1.25 Section 6: ハンズオン後の削除手順
- 1.26 Section 7: まとめと次のステップ
Step Functions Express vs Standard完全ガイド — 同一処理で比較するワークフロータイプ選択術
公開日: 2026-04-13
難易度: 中級
所要時間: 約90分
シリーズ: [Step Functions シリーズ 第7回]
- Standard Workflows と Express Workflows の本質的な違い(実行保証・履歴・課金モデル)
- 同期Express(StartSyncExecution)と非同期Express(StartExecution)のユースケース
- 同一ASLをtype変更だけでStandard/Express両方に適用する実践テクニック
- コスト比較: 月1万回実行でExpressが$0.031 vs Standard $0.75(24倍差)
- Terraform での完全構築(コンソール版とASL完全一致)
Section 1: Express vs Standard Workflows 概念編
本記事では、同一の注文処理ワークフローを Standard と Express の両方で実装し、違いを体験しながら学ぶ。まず概念編として、2つのワークフロータイプの特徴・課金モデル・選択基準を整理する。
1-1. Standard Workflows とは
Standard Workflows は、AWS Step Functions の「デフォルト」タイプである。長時間・重要なビジネスロジックの実行に最適化されている。
主な特徴:
| 項目 | 仕様 |
|---|---|
| 実行保証 | Exactly-once(正確に1回) |
| 最大実行時間 | 1年 |
| 実行履歴 | コンソール・API で参照可能(90日間保存) |
| 課金モデル | 状態遷移数 × $0.025 / 1,000遷移 |
| 無料枠 | 4,000遷移 / 月(永続無料枠) |
向いているユースケース:
– 長時間処理(数分〜数時間)
– 実行履歴・監査ログが必要なフロー
– 人間承認(Human Approval)が含まれるフロー
– 重要なビジネスロジック(注文処理・決済フロー)
1-2. Express Workflows とは
Express Workflows は、高スループット・短時間処理に特化したタイプである。Standard と比べて大幅にコストを削減できる反面、実行保証や実行履歴の扱いが異なる。
主な特徴:
| 項目 | 仕様 |
|---|---|
| 実行保証 | At-least-once(少なくとも1回) |
| 最大実行時間 | 5分 |
| 実行履歴 | CloudWatch Logs のみ(コンソールでは実行一覧は確認可能だが詳細履歴なし) |
| 課金モデル | 実行数 × $1 / 100万実行 + 実行時間(GB秒課金) |
| 無料枠 | 100万実行 / 月 + 100 GB秒 / 月(永続無料枠) |
向いているユースケース:
– 高スループット処理(月間数十万〜数百万回の実行)
– 短時間処理(秒〜分単位)
– イベント駆動アーキテクチャ(S3イベント・SNS・Kinesis連携)
– IoT データ処理・ストリーム処理
1-3. 同期Express vs 非同期Express
Express Workflows には「同期(Sync)」と「非同期(Async)」の2つの呼び出し方がある。
| 項目 | 同期Express (Sync) | 非同期Express (Async) |
|---|---|---|
| 呼び出し API | StartSyncExecution | StartExecution |
| 応答タイミング | 実行完了まで待機(最大5分) | 即座に実行ARNを返す |
| 結果取得 | レスポンスボディに含まれる | CloudWatch Logs を別途確認 |
| ユースケース | API バックエンド・同期処理 | 非同期バッチ・イベント処理 |
| 呼び出し元の制約 | API Gateway / Lambda / SDK経由のみ | 制約なし |
| 実行保証 | At-most-once(高々1回) | At-least-once(少なくとも1回) |
同期Express の典型的なアーキテクチャ:
クライアント → API Gateway → Step Functions(StartSyncExecution)
↓(最大5分待機)
レスポンスをそのまま返す
非同期Express の典型的なアーキテクチャ:
S3 イベント → EventBridge → Step Functions(StartExecution)
↓(即座にARN返却)
処理はバックグラウンドで継続
1-4. 比較表(Standard vs Express)
| 項目 | Standard | Express |
|---|---|---|
| 実行時間上限 | 1年 | 5分 |
| 実行保証 | Exactly-once | At-least-once |
| 実行履歴 | ✅ コンソール/API(90日) | CloudWatch Logs のみ |
| 課金モデル | 状態遷移数課金 | 実行数 + 継続時間課金 |
| 無料枠 | 4,000遷移/月 | 100万実行/月 + 100 GB秒/月 |
| 最大ステート数 | 上限なし | 上限なし |
| 実行中の状態確認 | ✅ API/コンソール | △ CloudWatch Logs のみ |
| べき等性の要件 | 不要 | 必須 |
| 典型ユースケース | 注文処理・承認フロー | IoT/イベント処理・高スループット |
1-5. 選択フローチャート
ワークフロータイプ選択の判断フローを以下に示す。
処理時間が5分以内?
NO → Standard 一択(Expressは5分が上限)
YES ↓
月間実行回数が多い(数万回以上)?
NO → 状態遷移課金の Standard が安い可能性あり
→ 1-6のコスト試算を参照して決定
YES ↓
実行履歴をコンソールで確認・監査したい?
YES → Standard
NO ↓
At-least-once で問題ない(冪等処理が実装可能)?
NO → Standard
YES → Express(コスト削減)
判断のポイントまとめ:
– 5分超 → Standard 確定
– 監査・コンプライアンス要件あり → Standard
– 高スループット + 短時間 + 冪等処理 → Express
1-6. コスト比較(試算例)
実際の数値でコストを比較する。
シナリオA: 月1万回実行、平均3ステート/実行、平均実行時間2秒、平均メモリ64MB
| 項目 | Standard | Express |
|---|---|---|
| 状態遷移コスト | 3万遷移 × $0.025/1,000 = $0.75 | — |
| 実行数コスト | — | 1万 × $1/100万 = $0.01 |
| 実行時間コスト | — | 1万回 × 2秒 × 64MB/1,024 = 1,250 GB秒 × $0.00001667 ≒ $0.021 |
| 合計/月 | $0.75 | $0.031 |
シナリオB: 月100万回実行(同一スペック)
| 項目 | Standard | Express |
|---|---|---|
| 合計/月 | $75 | $3.1 |
結論: 高スループット × 短時間処理では、Express の料金は Standard の 約1/24。
月間実行回数が増えるほど Express の優位性が拡大する。
1-7. 本シリーズとの関連
本記事は Step Functions シリーズ第7弾として位置づけられる。過去記事との関係を整理する。
| 記事 | テーマ | 推奨タイプ |
|---|---|---|
| 第3弾(エラーハンドリング) | Retry/Catch/Wait | Standard(長時間・再試行処理) |
| 第5弾(Callback パターン) | .waitForTaskToken | Standard(人間承認・外部サービス待機) |
| 第4弾(Distributed Map) | 大規模並列処理 | Standard / Express 両方(子実行はどちらも可) |
| 本記事(Express vs Standard) | 同一処理での比較体験 | 両方(比較目的) |
Section 2 以降の予告:
– Section 2: 同一の注文処理ワークフローを Standard で実装(Terraform + コンソール操作)
– Section 3: 同じワークフローを Express(同期)で実装・比較
– Section 4: Express(非同期)パターンの実装
– Section 5: コスト最適化の実践(実計測値で比較)
- Standard: Exactly-once・最大1年・状態遷移課金 → 長時間/重要処理向け
- Express: At-least-once・最大5分・実行数+時間課金 → 高スループット/短時間処理向け
- 同期Express は API バックエンドに、非同期Express はイベント駆動に最適
- コスト差は最大24倍以上になる場合があり、適切な選択がコスト最適化の鍵
Section 2: アーキテクチャ解説
概念を理解したところで、次はハンズオンのアーキテクチャを確認しましょう。同一ビジネスロジックをStandard/Express両方で構築することで、タイプの違いが動作にどう影響するかを直感的に体験できます。
2-1. ハンズオンシナリオ概要
本ハンズオンでは「注文処理ワークフロー(在庫確認 → 決済 → 発送 → 完了通知)」を題材に、同一ビジネスロジックをStandard型とExpress型の両方で構築し、動作・履歴管理・コストの違いを体感します。
同じ ASL(Amazon States Language)定義を使いながら、ステートマシンのタイプを切り替えるだけで挙動がどう変わるかを比較できるのがこのシナリオの特徴です。

使用リソース一覧
| リソース | Standard版 | Express版 |
|---|---|---|
| SF State Machine | order-standard(Standard型) | order-express(Express型) |
| Lambda | confirm-inventory、process-payment、arrange-shipping(共用) | 同じ関数を共用 |
| SNS | order-notification(共用) | 同じトピックを共用 |
| 実行履歴 | コンソール / API(90日保存) | CloudWatch Logs のみ |
Lambda 関数と SNS トピックは両ステートマシンで共用します。これにより「タイプが違っても ASL と処理ロジックは完全に同一」という点を直感的に理解できます。
2-2. 実行フロー比較

Standard型とExpress型の最大の違いは実行履歴の保存先と参照手段にあります。
Standard ワークフロー
StartExecution
↓
ConfirmInventory (状態遷移を履歴DB に自動記録)
↓
ProcessPayment (状態遷移を履歴DB に自動記録)
↓
ArrangeShipping(状態遷移を履歴DB に自動記録)
↓
NotifyCompletion (状態遷移を履歴DB に自動記録)
↓
SUCCEEDED
↓
[コンソール / API で全履歴参照可能 ✓]
- 各状態遷移がすべて履歴DBに記録されるため、マネジメントコンソールの Step Functions 画面から実行グラフを視覚的に確認できます。
GetExecutionHistoryAPI を呼び出せば、入出力データを含む完全なイベントログを取得できます。- 最大90日間保存されるため、後からのデバッグや監査ログとして活用できます。
Express ワークフロー
StartExecution / StartSyncExecution
↓
ConfirmInventory → CloudWatch Logs
↓
ProcessPayment → CloudWatch Logs
↓
ArrangeShipping→ CloudWatch Logs
↓
NotifyCompletion → CloudWatch Logs
↓
SUCCEEDED
↓
[実行履歴は CloudWatch Logs のみ △]
- 各状態遷移のログは CloudWatch Logs に書き込まれます(ただしログ出力は有効化設定が必要)。
- マネジメントコンソールの Step Functions 画面では実行履歴を参照できません。
StartSyncExecutionAPI を使うと同期実行(最大5分)ができ、レスポンスに直接出力が返ってきます。
2-3. 課金モデル比較

Standard の課金体系
Standard型は状態遷移数で課金されます。
| 課金要素 | 単価 |
|---|---|
| 状態遷移 | $0.025 / 1,000遷移 |
| 最初の 4,000遷移/月 | 無料枠 |
本ハンズオンのワークフロー(4状態)では 1実行あたり約3遷移が発生します(開始・各 Lambda 呼び出し・終了をカウント)。
| 月間実行数 | 状態遷移数 | 月額費用 |
|---|---|---|
| 1万回 | 3万遷移 | $0.75 |
| 10万回 | 30万遷移 | $7.50 |
| 100万回 | 300万遷移 | $75.00 |
Express の課金体系
Express型は実行数 + 継続時間(GB秒)の二軸で課金されます。
| 課金要素 | 単価 |
|---|---|
| 実行数 | $1.00 / 100万実行 |
| 継続時間 | $0.00001667 / GB秒 |
| 最初の 100万実行/月 および 300GB秒/月 | 無料枠 |
実行時間を平均2秒、メモリ64MBと仮定した場合(= 2 × 0.0625 GB = 0.125 GB秒/実行):
| 月間実行数 | 実行数課金 | 継続時間課金 | 月額合計 |
|---|---|---|---|
| 1万回 | $0.010 | $0.021 | $0.031 |
| 10万回 | $0.10 | $0.21 | $0.31 |
| 100万回 | $1.00 | $2.10 | $3.10 |
損益分岐点の考え方
Standard の $0.025/1,000遷移 と Express の実行コストを比較すると、約33,000遷移/月(=約1.1万実行/月 × 3遷移) が損益分岐点となります。
Standard コスト = Express コスト
0.025/1,000 × N遷移 = Express 月額
N ≈ 33,000 遷移/月
- 33,000遷移/月 未満: Standard の方が安い場合もあるが、そもそも無料枠内に収まる可能性大
- 33,000遷移/月 超: Express の方が圧倒的に安価(最大24倍のコスト差)
Section 3: AWSコンソールでのハンズオン
アーキテクチャを把握したところで、実際にAWSコンソールを使って構築してみましょう。StandardとExpressを段階的に作成し、その違いを手を動かしながら体験します。
このセクションでは、注文処理ワークフロー(在庫確認→決済→発送→完了通知)を題材に、Standard版→Express版の順に構築し、動作・履歴・コストの違いを実際に体験します。同一のASLを使うことで、typeの違いだけで挙動が変わることを体感できます。
3-1. 前提条件
AWSアカウントと権限
以下のIAMアクションが実行できるユーザー/ロールが必要です。
- Lambda:
lambda:CreateFunction,lambda:InvokeFunction,lambda:UpdateFunctionCode - Step Functions:
states:CreateStateMachine,states:StartExecution,states:StartSyncExecution,states:DescribeExecution - SNS:
sns:CreateTopic,sns:Subscribe,sns:Publish - IAM:
iam:CreateRole,iam:AttachRolePolicy,iam:PassRole - CloudWatch Logs:
logs:CreateLogGroup,logs:FilterLogEvents
AWS CLI
Step D(同期Express実行)で start-sync-execution コマンドを使用します。
# バージョン確認(v2.x 推奨)
aws --version
# デフォルトリージョンを ap-northeast-1 に設定
aws configure set region ap-northeast-1
3-2. Lambda 3関数のデプロイ(モック実装)
注文処理の各ステップをシミュレートするモックLambda関数を作成します。
confirm_inventory 関数
AWSコンソール → Lambda → 「関数の作成」
- 関数名:
confirm_inventory - ランタイム: Python 3.12
- アーキテクチャ: x86_64
コードエディタに以下を入力し、「Deploy」:
import json
def lambda_handler(event, context):
order_id = event.get("order_id", "unknown")
return {"available": True, "stock": 100, "order_id": order_id}
{"order_id": "ORD-001"} で実行し、{"available": true, "stock": 100, "order_id": "ORD-001"} が返ることを確認。process_payment 関数
- 関数名:
process_payment - ランタイム: Python 3.12
import json
import uuid
def lambda_handler(event, context):
return {"transaction_id": str(uuid.uuid4())[:8], "status": "approved"}
arrange_shipping 関数
- 関数名:
arrange_shipping - ランタイム: Python 3.12
import json
import uuid
def lambda_handler(event, context):
return {"tracking_id": f"TRACK-{str(uuid.uuid4())[:8].upper()}"}
関数ARNの記録
3関数を作成したら、各関数のARNをメモしておきます。後のASL設定で使用します。
confirm_inventory : arn:aws:lambda:ap-northeast-1:<ACCOUNT_ID>:function:confirm_inventory
process_payment : arn:aws:lambda:ap-northeast-1:<ACCOUNT_ID>:function:process_payment
arrange_shipping: arn:aws:lambda:ap-northeast-1:<ACCOUNT_ID>:function:arrange_shipping
3-3. SNSトピック作成
AWSコンソール → Simple Notification Service (SNS) → 「トピックの作成」
- タイプ: スタンダード
- 名前:
order-notification
作成後、「サブスクリプションの作成」でメールアドレスを登録します(任意)。
- プロトコル: Eメール
- エンドポイント: 受信したいメールアドレス
確認メールが届いたら「Confirm subscription」をクリックして有効化します。
arn:aws:sns:ap-northeast-1:<ACCOUNT_ID>:order-notification3-4. IAMロール作成
Step Functionsが各サービスを呼び出すためのロールを作成します。
AWSコンソール → IAM → 「ロールの作成」
- 信頼されたエンティティ: AWSのサービス → Step Functions
- ロール名:
sf-order-execution-role
ロール作成後、以下のインラインポリシーを追加します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": [
"arn:aws:lambda:ap-northeast-1:<ACCOUNT_ID>:function:confirm_inventory",
"arn:aws:lambda:ap-northeast-1:<ACCOUNT_ID>:function:process_payment",
"arn:aws:lambda:ap-northeast-1:<ACCOUNT_ID>:function:arrange_shipping"
]
},
{
"Effect": "Allow",
"Action": "sns:Publish",
"Resource": "arn:aws:sns:ap-northeast-1:<ACCOUNT_ID>:order-notification"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogDelivery",
"logs:GetLogDelivery",
"logs:UpdateLogDelivery",
"logs:DeleteLogDelivery",
"logs:ListLogDeliveries",
"logs:PutResourcePolicy",
"logs:DescribeResourcePolicies",
"logs:DescribeLogGroups",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
arn:aws:iam::<ACCOUNT_ID>:role/sf-order-execution-role3-5. 段階的構築
4つのステップで機能を段階的に追加しながら、StandardとExpressの違いを体感します。
Step A: Standard版を構築 — 実行履歴を体験する
ステートマシンの作成
AWSコンソール → Step Functions → 「ステートマシンの作成」
- 作成方法: コードでワークフローを記述
- タイプ: 標準(Standard)
- 名前:
order-standard-step-a - ロール:
sf-order-execution-role
ASLエディタに以下を一字一句そのまま貼り付け、<LAMBDA_INVENTORY_ARN>、<LAMBDA_PAYMENT_ARN>、<LAMBDA_SHIPPING_ARN>、<SNS_TOPIC_ARN> を実際のARNに置き換えます。
{
"Comment": "注文処理ワークフロー — Step A(基本フロー)",
"StartAt": "ConfirmInventory",
"States": {
"ConfirmInventory": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "<LAMBDA_INVENTORY_ARN>",
"Payload.$": "$"
},
"ResultSelector": {
"available.$": "$.Payload.available",
"stock.$": "$.Payload.stock"
},
"ResultPath": "$.inventory",
"Next": "ProcessPayment"
},
"ProcessPayment": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "<LAMBDA_PAYMENT_ARN>",
"Payload.$": "$"
},
"ResultSelector": {
"transaction_id.$": "$.Payload.transaction_id",
"status.$": "$.Payload.status"
},
"ResultPath": "$.payment",
"Next": "ArrangeShipping"
},
"ArrangeShipping": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "<LAMBDA_SHIPPING_ARN>",
"Payload.$": "$"
},
"ResultSelector": {
"tracking_id.$": "$.Payload.tracking_id"
},
"ResultPath": "$.shipping",
"Next": "NotifyCompletion"
},
"NotifyCompletion": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"Parameters": {
"TopicArn": "<SNS_TOPIC_ARN>",
"Message.$": "States.Format('注文 {} が完了しました', $.order_id)",
"Subject": "注文完了通知"
},
"ResultPath": null,
"End": true
}
}
}
実行と確認
「実行の開始」をクリックし、以下の入力を貼り付けます。
{"order_id": "ORD-001", "customer_id": "CUST-001", "items": [{"product": "laptop", "qty": 1}]}
実行完了後、実行詳細画面を開きます。
- グラフビュー: 各ステートが緑色に変わり、通過した経路を視覚確認できます
- イベント履歴: 各ステートの開始・終了・所要時間がタイムスタンプ付きで記録されています
- ステートごとの入出力: 各ステートをクリックすると、入力JSONと出力JSONを確認できます
Step B: Express版を構築 — CloudWatch Logsのみ
ステートマシンの作成
同じASL(Step A)を使って今度はExpressステートマシンを作成します。
AWSコンソール → Step Functions → 「ステートマシンの作成」
- タイプ: 高速(Express)← ここだけが違います
- 名前:
order-express-step-b - ロール:
sf-order-execution-role - ログレベル: ALL(CloudWatch Logsへの出力を有効化)
- ロググループ:
/aws/states/order-express-step-b(自動作成または手動指定)
ASLはStep Aと全く同一のJSONを貼り付け、ARNを同様に置き換えます。
実行と確認
同じ入力で実行します。
{"order_id": "ORD-002", "customer_id": "CUST-001", "items": [{"product": "laptop", "qty": 1}]}
実行一覧には表示されますが、「実行の詳細」ボタンが表示されない(または詳細が取得できない)ことを確認します。
Expressの実行ログはCloudWatch Logsで確認します。
aws logs filter-log-events \
--log-group-name /aws/states/order-express-step-b \
--start-time $(date -d "10 min ago" +%s000)
date -d の代わりに date -v-10M +%s000 を使用してください。ログ出力例:
{
"id": "1",
"type": "ExecutionStarted",
"details": {
"input": "{\"order_id\": \"ORD-002\", ...}",
"roleArn": "arn:aws:iam::<ACCOUNT_ID>:role/sf-order-execution-role"
}
}
Step C: 比較検証 — 速度・履歴・課金の違いを体感
同一の注文処理を両タイプで実行し、違いを比較します。
| 比較項目 | Standard | Express |
|---|---|---|
| 実行完了後の履歴確認 | コンソールで即座に確認可 | CWLogs検索が必要 |
| 同一実行の重複 | 排除される(Exactly-once) | 重複の可能性あり(At-least-once) |
| 実行速度(体感) | ほぼ同じ | ほぼ同じ |
| エラー時の再実行 | 実行履歴から再試行可能 | CWLogsで調査後に再実行 |
| 実行履歴の保存期間 | 90日間 | CloudWatch Logs保存期間に依存 |
| 料金体系 | 状態遷移回数 + 期間課金 | 実行回数 + 実行時間課金 |
ポイント: この段階では実行速度はほぼ同じです。Expressの真価は高頻度・短時間実行のシナリオで発揮されます(1秒間に何千回も実行するようなユースケース)。
Step D: 同期Express体験 — StartSyncExecution API
完成形ステートマシンの作成
Step D ASL(エラーハンドリング完成形)で、StandardとExpressの両方を作成します。
Standard版: order-standard-final(タイプ: 標準)
Express版: order-express-final(タイプ: 高速)
両方に以下のASLを一字一句そのまま貼り付け、ARNを置き換えます。
{
"Comment": "注文処理ワークフロー — Step D(エラーハンドリング完成形)",
"StartAt": "ConfirmInventory",
"States": {
"ConfirmInventory": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "<LAMBDA_INVENTORY_ARN>",
"Payload.$": "$"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException",
"Lambda.TooManyRequestsException"
],
"IntervalSeconds": 2,
"MaxAttempts": 3,
"BackoffRate": 2
}
],
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "OrderFailed",
"ResultPath": "$.error"
}
],
"ResultSelector": {
"available.$": "$.Payload.available",
"stock.$": "$.Payload.stock"
},
"ResultPath": "$.inventory",
"Next": "ProcessPayment"
},
"ProcessPayment": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "<LAMBDA_PAYMENT_ARN>",
"Payload.$": "$"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException",
"Lambda.TooManyRequestsException"
],
"IntervalSeconds": 2,
"MaxAttempts": 3,
"BackoffRate": 2
}
],
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "OrderFailed",
"ResultPath": "$.error"
}
],
"ResultSelector": {
"transaction_id.$": "$.Payload.transaction_id",
"status.$": "$.Payload.status"
},
"ResultPath": "$.payment",
"Next": "ArrangeShipping"
},
"ArrangeShipping": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "<LAMBDA_SHIPPING_ARN>",
"Payload.$": "$"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException",
"Lambda.TooManyRequestsException"
],
"IntervalSeconds": 2,
"MaxAttempts": 3,
"BackoffRate": 2
}
],
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "OrderFailed",
"ResultPath": "$.error"
}
],
"ResultSelector": {
"tracking_id.$": "$.Payload.tracking_id"
},
"ResultPath": "$.shipping",
"Next": "NotifyCompletion"
},
"NotifyCompletion": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"Parameters": {
"TopicArn": "<SNS_TOPIC_ARN>",
"Message.$": "States.Format('注文 {} が完了しました(追跡番号: {})', $.order_id, $.shipping.tracking_id)",
"Subject": "注文完了通知"
},
"ResultPath": null,
"Next": "OrderCompleted"
},
"OrderCompleted": {
"Type": "Succeed"
},
"OrderFailed": {
"Type": "Fail",
"Error": "OrderProcessingFailed",
"Cause": "注文処理中にエラーが発生しました"
}
}
}
同期実行(StartSyncExecution)
ExpressステートマシンはHTTP APIから同期的に呼び出すことができます。CLI で体験しましょう。
# 同期Express実行(レスポンスに結果が直接含まれる)
aws stepfunctions start-sync-execution \
--state-machine-arn <EXPRESS_SM_ARN> \
--input '{"order_id": "ORD-SYNC-001", "customer_id": "CUST-001", "items": []}' \
--region ap-northeast-1
実行完了まで待機し、コマンドが返るまでブロックされることを確認します。
レスポンス例:
{
"executionArn": "arn:aws:states:ap-northeast-1:<ACCOUNT_ID>:express:order-express-final:...",
"stateMachineArn": "arn:aws:states:ap-northeast-1:<ACCOUNT_ID>:stateMachine:order-express-final",
"name": "...",
"startDate": "2026-04-13T07:40:00.000Z",
"stopDate": "2026-04-13T07:40:00.850Z",
"status": "SUCCEEDED",
"output": "{\"order_id\":\"ORD-SYNC-001\",\"customer_id\":\"CUST-001\",\"items\":[],\"inventory\":{\"available\":true,\"stock\":100},\"payment\":{\"transaction_id\":\"a1b2c3d4\",\"status\":\"approved\"},\"shipping\":{\"tracking_id\":\"TRACK-E5F6G7H8\"}}"
}
output フィールドに結果JSONが直接含まれる点に注目してください。
StandardのStartExecution(非同期)との比較
Standard版で同様の実行を行うと、レスポンスにはexecutionArnのみが返り、結果は別途DescribeExecution APIで取得する必要があります。
# Standard版は非同期(executionArnのみが返る)
aws stepfunctions start-execution \
--state-machine-arn <STANDARD_SM_ARN> \
--input '{"order_id": "ORD-ASYNC-001", "customer_id": "CUST-001", "items": []}' \
--region ap-northeast-1
| 観点 | StartExecution (Standard) | StartSyncExecution (Express) |
|---|---|---|
| 戻り値 | executionArnのみ | 実行結果(output)まで含む |
| 呼び出し方 | 非同期(fire & forget) | 同期(結果を待つ) |
| タイムアウト上限 | 1年 | 5分 |
| 用途 | 長時間ワークフロー、人間の承認フロー | API統合、リアルタイム処理 |
まとめ(Section 3)
このセクションで体験したこと:
| ステップ | 構築内容 | 学んだこと |
|---|---|---|
| Step A | Standard基本フロー | 実行履歴・イベント詳細がコンソールで確認できる |
| Step B | Express基本フロー(同一ASL) | 履歴非表示・CWLogsで調査する |
| Step C | 両タイプの比較 | 可視性・冗長性・課金の違い |
| Step D | エラーハンドリング完成形 + 同期実行 | StandardとExpressで同一ASLが動く、同期実行APIの使い方 |
重要ポイント: StandardとExpressで使用したASLは完全に同一です。typeの選択だけで、実行セマンティクス・可視性・料金体系が変わります。次のTerraformセクションでも、このASLをそのままコード化します。
Section 4: TerraformでのStandard/Express構築
コンソールで動作を確認したら、次はTerraformでInfrastructure as Codeとして管理しましょう。同一のASLテンプレートファイルからStandard/Expressを生成するアプローチが、IaCの強みを活かします。
このセクションでは、Section 3(コンソール版)で構築した注文処理ワークフローを Terraform でコード化します。Standard ワークフローと Express ワークフローを 同一の ASL テンプレートファイル から生成することで、「typeだけ異なり、ステートマシン定義は完全に同じ」という Express vs Standard の核心を体験できます。
4-1. 前提条件
作業を始める前に以下の環境が整っていることを確認してください。
- Terraform 1.0 以上 がインストール済み(
terraform versionで確認) - AWS CLI が設定済み(
aws configureでアクセスキー・リージョン等を設定) - 以下の AWS リソースを作成・管理できる IAM 権限:
- AWS Lambda(作成・更新・削除)
- AWS Step Functions(ステートマシン作成・更新・削除)
- Amazon SNS(トピック作成・サブスクリプション管理)
- AWS IAM(ロール・ポリシーの作成・管理)
- Amazon CloudWatch Logs(ロググループ作成・管理)
4-2. ディレクトリ構成
プロジェクトディレクトリ sf-order/ を作成し、以下の構成にします。
sf-order/
├── main.tf# メインリソース定義
├── variables.tf # 変数定義
├── outputs.tf# 出力定義
├── lambda/
│├── confirm_inventory.py # 在庫確認 Lambda
│├── process_payment.py# 決済処理 Lambda
│└── arrange_shipping.py # 発送手配 Lambda
└── statemachine/
└── definition.json.tpl # ASL テンプレート(Standard/Express 共通)
mkdir -p sf-order/lambda sf-order/statemachine
cd sf-order
4-3. Lambda ソースコード(コンソール版と同一ロジック)
Section 3 のコンソール版で作成した Lambda と同じロジックをファイルに保存します。
lambda/confirm_inventory.py
在庫確認関数。注文 ID を受け取り、在庫あり・在庫数 100 を返します。
import json
def lambda_handler(event, context):
order_id = event.get("order_id", "unknown")
return {"available": True, "stock": 100, "order_id": order_id}
lambda/process_payment.py
決済処理関数。ランダムなトランザクション ID を生成して返します。
import json, uuid
def lambda_handler(event, context):
return {"transaction_id": str(uuid.uuid4())[:8], "status": "approved"}
lambda/arrange_shipping.py
発送手配関数。TRACK-XXXXXXXX 形式の追跡番号を生成して返します。
import json, uuid
def lambda_handler(event, context):
return {"tracking_id": f"TRACK-{str(uuid.uuid4())[:8].upper()}"}
4-4. statemachine/definition.json.tpl
ステートマシン定義は テンプレートファイル として管理します。${...} の部分は Terraform の templatefile() 関数が補間します。
ポイント: このテンプレートは Standard 版と Express 版で共通 です。aws_sfn_state_machine リソースの type 属性("STANDARD" / "EXPRESS")だけが異なり、ASL 定義は一字一句同じです。これが Section 1 で解説した「Express と Standard は ASL 互換」という特性そのものです。
{
"Comment": "注文処理ワークフロー — Step D(エラーハンドリング完成形)",
"StartAt": "ConfirmInventory",
"States": {
"ConfirmInventory": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "${lambda_inventory_arn}",
"Payload.$": "$"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException",
"Lambda.TooManyRequestsException"
],
"IntervalSeconds": 2,
"MaxAttempts": 3,
"BackoffRate": 2
}
],
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "OrderFailed",
"ResultPath": "$.error"
}
],
"ResultSelector": {
"available.$": "$.Payload.available",
"stock.$": "$.Payload.stock"
},
"ResultPath": "$.inventory",
"Next": "ProcessPayment"
},
"ProcessPayment": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "${lambda_payment_arn}",
"Payload.$": "$"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException",
"Lambda.TooManyRequestsException"
],
"IntervalSeconds": 2,
"MaxAttempts": 3,
"BackoffRate": 2
}
],
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "OrderFailed",
"ResultPath": "$.error"
}
],
"ResultSelector": {
"transaction_id.$": "$.Payload.transaction_id",
"status.$": "$.Payload.status"
},
"ResultPath": "$.payment",
"Next": "ArrangeShipping"
},
"ArrangeShipping": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "${lambda_shipping_arn}",
"Payload.$": "$"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException",
"Lambda.TooManyRequestsException"
],
"IntervalSeconds": 2,
"MaxAttempts": 3,
"BackoffRate": 2
}
],
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "OrderFailed",
"ResultPath": "$.error"
}
],
"ResultSelector": {
"tracking_id.$": "$.Payload.tracking_id"
},
"ResultPath": "$.shipping",
"Next": "NotifyCompletion"
},
"NotifyCompletion": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"Parameters": {
"TopicArn": "${sns_topic_arn}",
"Message.$": "States.Format('注文 {} が完了しました(追跡番号: {})', $.order_id, $.shipping.tracking_id)",
"Subject": "注文完了通知"
},
"ResultPath": null,
"Next": "OrderCompleted"
},
"OrderCompleted": {
"Type": "Succeed"
},
"OrderFailed": {
"Type": "Fail",
"Error": "OrderProcessingFailed",
"Cause": "注文処理中にエラーが発生しました"
}
}
}
templatefile() で補間される変数一覧:
| 変数名 | 内容 |
|---|---|
${lambda_inventory_arn} | 在庫確認 Lambda の ARN |
${lambda_payment_arn} | 決済処理 Lambda の ARN |
${lambda_shipping_arn} | 発送手配 Lambda の ARN |
${sns_topic_arn} | 注文完了通知 SNS トピックの ARN |
Terraform がリソースを作成した後、実際の ARN 値をこれらの変数に埋め込んで JSON を生成します。
4-5. variables.tf
variable "aws_region" {
default = "ap-northeast-1"
}
variable "project_name" {
default = "sf-order"
}
variable "notification_email" {
description = "SNS完了通知の送信先メールアドレス"
type = string
}
4-6. main.tf(完全なコード)
すべての AWS リソースを定義します。terraform plan が通る完全なコードです。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# -----------------------------------------------------------
# Lambda: ソースコードの ZIP 化
# -----------------------------------------------------------
data "archive_file" "confirm_inventory" {
type = "zip"
source_file = "${path.module}/lambda/confirm_inventory.py"
output_path = "${path.module}/lambda/confirm_inventory.zip"
}
data "archive_file" "process_payment" {
type = "zip"
source_file = "${path.module}/lambda/process_payment.py"
output_path = "${path.module}/lambda/process_payment.zip"
}
data "archive_file" "arrange_shipping" {
type = "zip"
source_file = "${path.module}/lambda/arrange_shipping.py"
output_path = "${path.module}/lambda/arrange_shipping.zip"
}
# -----------------------------------------------------------
# IAM: Lambda 実行ロール
# -----------------------------------------------------------
resource "aws_iam_role" "lambda_role" {
name = "${var.project_name}-lambda-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
}]
})
}
resource "aws_iam_role_policy_attachment" "lambda_basic" {
role = aws_iam_role.lambda_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
# -----------------------------------------------------------
# CloudWatch Logs: Lambda 用ロググループ
# -----------------------------------------------------------
resource "aws_cloudwatch_log_group" "lambda_confirm_inventory" {
name = "/aws/lambda/${var.project_name}-confirm-inventory"
retention_in_days = 7
}
resource "aws_cloudwatch_log_group" "lambda_process_payment" {
name = "/aws/lambda/${var.project_name}-process-payment"
retention_in_days = 7
}
resource "aws_cloudwatch_log_group" "lambda_arrange_shipping" {
name = "/aws/lambda/${var.project_name}-arrange-shipping"
retention_in_days = 7
}
# -----------------------------------------------------------
# Lambda 関数
# -----------------------------------------------------------
resource "aws_lambda_function" "confirm_inventory" {
function_name = "${var.project_name}-confirm-inventory"
role = aws_iam_role.lambda_role.arn
handler = "confirm_inventory.lambda_handler"
runtime = "python3.12"
filename= data.archive_file.confirm_inventory.output_path
source_code_hash = data.archive_file.confirm_inventory.output_base64sha256
depends_on = [aws_cloudwatch_log_group.lambda_confirm_inventory]
}
resource "aws_lambda_function" "process_payment" {
function_name = "${var.project_name}-process-payment"
role = aws_iam_role.lambda_role.arn
handler = "process_payment.lambda_handler"
runtime = "python3.12"
filename= data.archive_file.process_payment.output_path
source_code_hash = data.archive_file.process_payment.output_base64sha256
depends_on = [aws_cloudwatch_log_group.lambda_process_payment]
}
resource "aws_lambda_function" "arrange_shipping" {
function_name = "${var.project_name}-arrange-shipping"
role = aws_iam_role.lambda_role.arn
handler = "arrange_shipping.lambda_handler"
runtime = "python3.12"
filename= data.archive_file.arrange_shipping.output_path
source_code_hash = data.archive_file.arrange_shipping.output_base64sha256
depends_on = [aws_cloudwatch_log_group.lambda_arrange_shipping]
}
# -----------------------------------------------------------
# SNS: 注文完了通知
# -----------------------------------------------------------
resource "aws_sns_topic" "order_notification" {
name = "${var.project_name}-order-notification"
}
resource "aws_sns_topic_subscription" "email" {
topic_arn = aws_sns_topic.order_notification.arn
protocol = "email"
endpoint = var.notification_email
}
# -----------------------------------------------------------
# CloudWatch Logs: Step Functions 用ロググループ
# -----------------------------------------------------------
resource "aws_cloudwatch_log_group" "sf_standard" {
name = "/aws/states/${var.project_name}-standard"
retention_in_days = 7
}
resource "aws_cloudwatch_log_group" "sf_express" {
name = "/aws/states/${var.project_name}-express"
retention_in_days = 7
}
# -----------------------------------------------------------
# IAM: Step Functions 実行ロール
# -----------------------------------------------------------
resource "aws_iam_role" "sf_execution_role" {
name = "${var.project_name}-sf-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "states.amazonaws.com" }
}]
})
}
resource "aws_iam_role_policy" "sf_policy" {
name = "${var.project_name}-sf-policy"
role = aws_iam_role.sf_execution_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["lambda:InvokeFunction"]
Resource = [
aws_lambda_function.confirm_inventory.arn,
aws_lambda_function.process_payment.arn,
aws_lambda_function.arrange_shipping.arn,
]
},
{
Effect= "Allow"
Action= ["sns:Publish"]
Resource = [aws_sns_topic.order_notification.arn]
},
{
Effect = "Allow"
Action = [
"logs:CreateLogDelivery",
"logs:CreateLogGroup",
"logs:PutLogEvents",
"logs:GetLogDelivery",
"logs:UpdateLogDelivery",
"logs:DeleteLogDelivery",
"logs:ListLogDeliveries",
"logs:PutResourcePolicy",
"logs:DescribeResourcePolicies",
"logs:DescribeLogGroups",
]
Resource = ["*"]
},
]
})
}
# -----------------------------------------------------------
# Step Functions: Standard ワークフロー
# -----------------------------------------------------------
# コンソール版(Section 3)の Step D と ASL 構造が完全一致しています。
# Standard 版と Express 版は同一の templatefile を使用しており、
# type 属性だけが "STANDARD" / "EXPRESS" で異なります。
resource "aws_sfn_state_machine" "order_standard" {
name = "${var.project_name}-standard"
role_arn = aws_iam_role.sf_execution_role.arn
type = "STANDARD"
definition = templatefile("${path.module}/statemachine/definition.json.tpl", {
lambda_inventory_arn = aws_lambda_function.confirm_inventory.arn
lambda_payment_arn= aws_lambda_function.process_payment.arn
lambda_shipping_arn = aws_lambda_function.arrange_shipping.arn
sns_topic_arn = aws_sns_topic.order_notification.arn
})
logging_configuration {
log_destination = "${aws_cloudwatch_log_group.sf_standard.arn}:*"
include_execution_data = false
level= "ERROR"
}
depends_on = [aws_iam_role_policy.sf_policy]
}
# -----------------------------------------------------------
# Step Functions: Express ワークフロー
# -----------------------------------------------------------
# Standard 版と完全に同じ templatefile を使用(ASL は一字一句同じ)。
# Express ではログが唯一の実行履歴であるため、level = "ALL" を推奨。
resource "aws_sfn_state_machine" "order_express" {
name = "${var.project_name}-express"
role_arn = aws_iam_role.sf_execution_role.arn
type = "EXPRESS"
definition = templatefile("${path.module}/statemachine/definition.json.tpl", {
lambda_inventory_arn = aws_lambda_function.confirm_inventory.arn
lambda_payment_arn= aws_lambda_function.process_payment.arn
lambda_shipping_arn = aws_lambda_function.arrange_shipping.arn
sns_topic_arn = aws_sns_topic.order_notification.arn
})
logging_configuration {
log_destination = "${aws_cloudwatch_log_group.sf_express.arn}:*"
include_execution_data = true
level= "ALL"
}
depends_on = [aws_iam_role_policy.sf_policy]
}
コードのポイント:
| 項目 | Standard | Express |
|---|---|---|
type | "STANDARD" | "EXPRESS" |
definition | templatefile(...) — 共通 | templatefile(...) — 共通(完全同一) |
logging_configuration.level | "ERROR" | "ALL"(実行履歴がないため詳細ログ必須) |
include_execution_data | false | true |
4-7. outputs.tf
output "standard_state_machine_arn" {
description = "Standard ワークフローの ARN"
value = aws_sfn_state_machine.order_standard.arn
}
output "express_state_machine_arn" {
description = "Express ワークフローの ARN"
value = aws_sfn_state_machine.order_express.arn
}
output "lambda_inventory_arn" {
description = "在庫確認 Lambda の ARN"
value = aws_lambda_function.confirm_inventory.arn
}
output "lambda_payment_arn" {
description = "決済処理 Lambda の ARN"
value = aws_lambda_function.process_payment.arn
}
output "lambda_shipping_arn" {
description = "発送手配 Lambda の ARN"
value = aws_lambda_function.arrange_shipping.arn
}
output "sns_topic_arn" {
description = "注文完了通知 SNS トピックの ARN"
value = aws_sns_topic.order_notification.arn
}
4-8. デプロイ手順
# 1. Terraform 初期化(プロバイダーのダウンロード)
terraform init
# 2. 実行計画の確認
terraform plan -var="notification_email=your@email.com"
# 3. リソースの作成
terraform apply -var="notification_email=your@email.com"
terraform apply 完了後、SNS サブスクリプションの 確認メール が notification_email 宛に届きます。メール内の「Confirm subscription」リンクをクリックしてサブスクリプションを有効化してください。
4-9. 動作確認(Standard版)
Standard ワークフローは 非同期実行 です。start-execution コマンドで開始し、コンソールまたは CLI で実行履歴を確認できます。
# Standard 版: 非同期実行
aws stepfunctions start-execution \
--state-machine-arn $(terraform output -raw standard_state_machine_arn) \
--input '{"order_id": "TF-001", "customer_id": "CUST-001", "items": [{"product": "laptop", "qty": 1}]}' \
--region ap-northeast-1
# 実行一覧確認(コンソールで詳細履歴を確認可能)
aws stepfunctions list-executions \
--state-machine-arn $(terraform output -raw standard_state_machine_arn)
AWSコンソールでの確認手順:
- AWS コンソール > Step Functions > ステートマシン >
sf-order-standardを選択 - 「実行」タブを開き、実行 ID をクリック
- ビジュアルワークフロー・イベント履歴・入出力データを確認
Standard ワークフローの実行履歴は 90日間 保持されます。
4-10. 動作確認(Express版)
Express ワークフローは 同期実行(start-sync-execution) が利用可能です。コマンドのレスポンスに実行結果が直接含まれます。
# Express 版: 同期実行(結果がレスポンスに含まれる)
aws stepfunctions start-sync-execution \
--state-machine-arn $(terraform output -raw express_state_machine_arn) \
--input '{"order_id": "TF-SYNC-001", "customer_id": "CUST-001", "items": [{"product": "laptop", "qty": 1}]}' \
--region ap-northeast-1
レスポンス例:
{
"executionArn": "arn:aws:states:ap-northeast-1:...",
"stateMachineArn": "arn:aws:states:ap-northeast-1:...:stateMachine:sf-order-express",
"status": "SUCCEEDED",
"output": "{...最終出力データ...}"
}
Standard と異なり、Express の実行履歴はコンソールに表示されません。ログは CloudWatch Logs で確認します。
4-11. Standard vs Express の比較確認
両方のワークフローを実行し、以下の点を比較してみましょう。
実行履歴の確認方法の違い
Standard 版: AWSコンソール
- Step Functions コンソール >
sf-order-standard> 「実行」タブ - 各ステートの実行時間・入出力・イベント履歴を詳細に閲覧可能
- 90日間の履歴が自動保持される
Express 版: CloudWatch Logs
- CloudWatch コンソール > ロググループ >
/aws/states/sf-order-express - ログストリームを開き、各ステートのログエントリを確認
level = "ALL"設定により、すべてのステート遷移が記録されている
# Express 版のログを CLI で確認
aws logs filter-log-events \
--log-group-name /aws/states/sf-order-express \
--region ap-northeast-1 \
--limit 20
ASL の同一性確認
重要: 2つのステートマシンは type 以外まったく同じです。
# Standard 版の定義を取得
aws stepfunctions describe-state-machine \
--state-machine-arn $(terraform output -raw standard_state_machine_arn) \
--query 'definition' --output text | python3 -m json.tool > /tmp/standard_def.json
# Express 版の定義を取得
aws stepfunctions describe-state-machine \
--state-machine-arn $(terraform output -raw express_state_machine_arn) \
--query 'definition' --output text | python3 -m json.tool > /tmp/express_def.json
# 差分確認(ASL は完全一致のはず)
diff /tmp/standard_def.json /tmp/express_def.json
diff の出力が空(差分なし)であれば、Standard と Express で まったく同一の ASL が使われていることが確認できます。これが「Terraform の templatefile() で同一定義を共有する」アプローチの実証です。
クリーンアップ
動作確認が完了したらリソースを削除してコストを節約しましょう。
terraform destroy -var="notification_email=your@email.com"
まとめ(Section 4)
このセクションで構築したポイントを振り返ります。
| 項目 | 内容 |
|---|---|
| ASL の一元管理 | definition.json.tpl 1ファイルで Standard・Express 両方を生成 |
| コンソール版との一致 | Section 3 Step D の ASL と完全一致(Retry/Catch/ResultSelector/ResultPath すべて同一) |
| ログ設定の違い | Standard は ERROR のみ、Express は ALL(実行履歴がないため) |
| 実行方式の違い | Standard は非同期(start-execution)、Express は同期も可能(start-sync-execution) |
Terraform で IaC 化することで、Standard → Express への切り替えが type の1行変更だけ で済むことが実感できたはずです。次の Section 5 では、ユースケース別の選択基準とベストプラクティスをまとめます。
Section 5: 実践Tips(Express vs Standard設計ガイド)
5-1. Express移行の注意点: べき等性設計
At-least-once保証とは:
Expressは同一実行が複数回トリガーされる可能性があります。
べき等な処理(同じ処理を何度実行しても結果が同じ)でないと、重複処理による不整合が生じます。
べき等性を実現するパターン:
- DynamoDB条件付き書き込み:
{
"Type": "Task",
"Resource": "arn:aws:states:::dynamodb:putItem",
"Parameters": {
"TableName": "Orders",
"Item": {
"order_id": {"S.$": "$.order_id"},
"status": {"S": "processing"}
},
"ConditionExpression": "attribute_not_exists(order_id)"
}
}
→ 同じ order_id の重複処理を防止。すでに存在するキーへの書き込みは ConditionalCheckFailedException で弾かれます。
- 実行名(ExecutionName)に冪等キーを埋め込む:
aws stepfunctions start-execution \
--state-machine-arn <ARN> \
--name "order-$(echo -n ${ORDER_ID} | md5sum | cut -c1-8)" \
--input "{\"order_id\": \"${ORDER_ID}\"}"
→ 同じ ORDER_ID からは同名の実行が生成されます。Express Workflowsでは同名の実行を重複起動しようとするとエラーが返るため、重複処理を防止できます。
5-2. ログ設計: Express版でのデバッグ戦略
Express WorkflowsはStandardと異なり、コンソールから実行履歴を直接参照できません。CloudWatch Logsへの出力設定が必須です。
CloudWatch Logsの設定推奨値:
# Express State Machine(Terraform例)
logging_configuration {
level= "ALL" # 開発: ALL / 本番: ERROR
include_execution_data = true # 開発: true / 本番: false(コスト削減)
log_destination = "${aws_cloudwatch_log_group.sf_express.arn}:*"
}
ログコスト試算(月1万実行、平均3ステート、include_execution_data=true):
| 設定 | ログ量目安 | 月額CloudWatch Logs |
|---|---|---|
| ALL + execution_data=true | ~100MB | ~$0.08 |
| ERROR のみ | ~1MB | < $0.01 |
| ALLのみ(data=false) | ~10MB | ~$0.008 |
include_execution_data = false + level = "ERROR" が推奨です。デバッグ時だけ ALL に変更しましょう。実行ログの検索(AWS CLI):
# 特定実行のログを取得
aws logs filter-log-events \
--log-group-name /aws/states/order-express \
--filter-pattern '{ $.execution_arn = "*ORD-001*" }' \
--start-time $(date -u -d "1 hour ago" +%s000) \
--region ap-northeast-1
5-3. ハイブリッド構成: StandardからExpressを呼び出す
ユースケース: メイン処理はStandard(実行履歴・監査が必要)、高スループットのサブタスクはExpress(コスト削減・スケール)。
{
"Comment": "ハイブリッド: Standard親ワークフロー → Express子ワークフロー",
"StartAt": "ValidateOrder",
"States": {
"ValidateOrder": {
"Type": "Task",
"Resource": "arn:aws:states:::states:startExecution.sync:2",
"Parameters": {
"StateMachineArn": "<EXPRESS_STATE_MACHINE_ARN>",
"Input.$": "$"
},
"ResultPath": "$.validation_result",
"Next": "ProcessOrder"
},
"ProcessOrder": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "<LAMBDA_ARN>",
"Payload.$": "$"
},
"End": true
}
}
}
→ states:startExecution.sync:2 を使うことで、Express子実行の完了を親Standardが同期的に待機できます。子の実行結果は $.validation_result に格納されます。
ポイント:
– 親Standardは子Expressの実行をひとつのTaskとして扱うため、エラーハンドリング(Retry/Catch)も通常通り設定可能
– Express子実行のコストは「リクエスト数 × 実行時間」で課金されるため、短時間・高頻度処理に最適
5-4. Standard → Express 移行チェックリスト
移行前に以下を必ず確認してください:
| 確認項目 | 判定基準 | 対応方法 |
|---|---|---|
| 処理時間が5分以内か | YES必須 | 超える処理はStandard継続 |
| べき等性が保証されているか | YES必須 | DynamoDB条件付き書き込み等で担保 |
| 実行履歴の参照が不要か | 監査要件確認 | 監査要件がある場合はStandard |
| エラー調査フローがあるか | CloudWatch Logs設定確認 | Logs Insightsのクエリを事前設計 |
| コスト試算済みか | 月間実行数で試算 | 月間実行数×時間×メモリで計算 |
- Standard: $0.025 / 1,000 状態遷移(月4,000遷移まで無料)
- Express: $1.00 / 100万リクエスト + $0.00001667 / GB秒
- 月1万回実行でExpressが約$0.031 vs Standard約$0.75(約24倍の差)
Section 6: ハンズオン後の削除手順
6-1. コスト注意事項
| リソース | 月額目安 | 備考 |
|---|---|---|
| Lambda(3関数) | 無料枠内 | 100万リクエスト/月まで無料 |
| Step Functions | 無料枠内 | 4,000遷移/月まで無料(Standard) |
| SNS | 無料枠内 | 通知100万件/月まで無料 |
| CloudWatch Logs | ~$0.76/GB | Express ALLレベルでログが溜まる |
6-2. Terraformで構築した場合
すべてのリソースを一括削除:
terraform destroy -var="notification_email=your@email.com"
terraform destroy では削除されない場合があるリソース(手動削除が必要):# CloudWatch Logsロググループ
aws logs delete-log-group \
--log-group-name /aws/states/sf-order-standard \
--region ap-northeast-1
aws logs delete-log-group \
--log-group-name /aws/states/sf-order-express \
--region ap-northeast-1
aws logs delete-log-group \
--log-group-name /aws/lambda/sf-order-confirm-inventory \
--region ap-northeast-1
aws logs delete-log-group \
--log-group-name /aws/lambda/sf-order-process-payment \
--region ap-northeast-1
aws logs delete-log-group \
--log-group-name /aws/lambda/sf-order-arrange-shipping \
--region ap-northeast-1
削除後の確認:
# ステートマシン一覧(空になればOK)
aws stepfunctions list-state-machines --region ap-northeast-1
# Lambda一覧(sf-order-* がなければOK)
aws lambda list-functions --region ap-northeast-1 \
--query 'Functions[?starts_with(FunctionName, `sf-order`)].FunctionName'
6-3. コンソールで構築した場合の削除チェックリスト
以下の順番で削除することを推奨します(依存関係の逆順):
- [ ] Step Functions ステートマシン を削除
order-standard-step-aorder-express-step-border-standard-finalorder-express-final- [ ] Lambda 関数 3関数を削除
sf-order-confirm-inventorysf-order-process-paymentsf-order-arrange-shipping- [ ] SNS トピック(
order-notification)とすべてのサブスクリプションを削除 - [ ] IAMロール(
sf-order-execution-role)と付随するポリシーを削除 - [ ] CloudWatch Logs ロググループ を削除
/aws/states/order-standard-step-a/aws/states/order-express-step-b/aws/states/order-standard-final/aws/states/order-express-final/aws/lambda/sf-order-confirm-inventory/aws/lambda/sf-order-process-payment/aws/lambda/sf-order-arrange-shipping
Section 7: まとめと次のステップ
7-1. この記事で学んだこと
本記事では、Standard WorkflowsとExpress Workflowsの違いを実際のコードと共に体験しました。
- Standard vs Expressの本質的な違い: 実行保証(Exactly-once vs At-least-once)・履歴保存期間(90日 vs ~5分)・課金モデル(状態遷移数 vs リクエスト×時間)
- 同期Express(StartSyncExecution)と非同期Express(StartExecution)のユースケース: APIレスポンスが必要な場合は同期、大量バッチ処理は非同期
- 同一ASLをtype変更だけでStandard/Express両方に適用できること: コードの再利用性が高く、段階的な移行が可能
- コスト比較: 月1万回実行でExpressが約$0.031 vs Standard約$0.75(約24倍差)
- べき等性設計の重要性: At-least-once保証への対応(DynamoDB条件付き書き込み・実行名を冪等キーに)
- Express版でのログ設計: CloudWatch Logs ALL設定の重要性(コンソールから実行履歴を参照できないため必須)
- ハイブリッド構成: StandardからExpressを
startExecution.sync:2で呼び出すパターン
7-2. 次のステップ
第8弾予告: SDK Direct Integration(Lambda不要でAWSサービスを直接呼出し)
現在のアーキテクチャでは、Step FunctionsからAWSサービスを操作するためにLambda関数を仲介しています。次回は SDK Direct Integration(最適化済みの統合) を使って Lambda なしで DynamoDB・SNS・SQS などを直接呼び出す方法を解説します。
- Lambdaレスアーキテクチャへの進化でコスト・複雑さをさらに削減
- 対応サービス: DynamoDB、S3、SQS、SNS、ECS、Bedrock、その他200以上
7-3. シリーズリンク
本記事は「AWS ハンズオン TechBlog」Step Functions シリーズの第7回です。
シリーズ一覧:
– 第1回: AWS Step Functions 入門 — コンソールとTerraformで学ぶハンズオン
– 第2回: ECS × Step Functions 入門 — CSVバッチをFargateタスクでジョブ化するハンズオン
– 第3回: Step Functions エラーハンドリング完全ガイド — 注文処理パイプラインで学ぶRetry/Catch/Timeout
– 第4回: Step Functions 入出力データフロー制御完全ガイド — 5つのフィルタでペイロードを最適化するハンズオン
– 第5回: Step Functions Callbackパターン完全ガイド — .waitForTaskTokenで実現する経費承認ワークフロー
– 第6回: Step Functions Distributed Map完全ガイド — S3大規模並列処理をハンズオンで習得
– 第7回: 本記事(Express vs Standard完全ガイド)
次の記事を読む: 第8回 Step Functions SDK Direct Integration ▶
7-4. 参考リンク
- AWS Step Functions Developer Guide — Standard vs. Express Workflows
- Express Workflows の制限と料金
- StartSyncExecution API Reference
- CloudWatch Logs を使った Express Workflows のモニタリング