AWS Step Functions - Simple Order Flow Example (Step by Step Guide)
What is AWS Step Functions?
- AWS Step Functions is a fully managed service that makes it easy to coordinate the components of distributed applications and microservices using visual workflows.
- Building applications from individual components that each perform a discrete function lets you scale easily and change applications quickly.
- Step Functions is a reliable way to coordinate components and step through the functions of your application. Step Functions provides a graphical console to arrange and visualize the components of your application as a series of steps.
- This makes it simple to build and run multi-step applications. Step Functions automatically triggers and tracks each step, and retries when there are errors, so your application executes in order and as expected.
- Step Functions logs the state of each step, so when things do go wrong, you can diagnose and debug problems quickly.
- You can change and add steps without even writing code, so you can easily evolve your application and innovate faster.
Order Flow - Design Details
For this example, I will demo how step functions can help to manage a order flow once user has submitted the order. Here, for this example, I am taking an online bookstore which will ship books based on the order submission. It should perform the below steps as part of Order Handling
- On Order Submit, system will check the available inventory of the book.
- If inventory available, then proceed further. If not available, then trigger a printOrder and wait for the book to be printed.
- Once the book is printed, the system will prepare order for delivery and trigger the send shipment flow.
- In parallel, System will
- Update Loyalty Points for the Customer
- Check future discount eligibility and send discount code for future order
- Update Recommendation Engine
- Send a free eBook for Tips to Better Life
Order Flow - Steps
For creating a Step Function, below are the steps required which I will show in details
- Create an IAM Role for the AWS Step function to be able to execute the AWS Services ( eg:- Lambda in this case)
- Create Lambda Executor Functions
- Create AWS Step Functions with the flow as highlighted above
Order Flow - Step 1 - IAM Roles
1 In the AWS Console, go to Identity and Access Management (IAM), click on Roles in the left hand pane
On the next screen, keep the default AWS Service option selected and under the list of Services choose Step Functions
Leave the rest as default and click Next on next 3 screens, Give a Role Name and click Create Role
Great! Step 1 has been completed and we are now ready for Step 2 on creation of the required Lambda functions
Order Flow - Step 2 - Create Lambda
Next step is to create below Lambda functions as per the requirement of our code.
- In the IAM console, search Lambda and click on Create Function
- Select Author from scratch
- Give Function name as per below function names
- Select Runtime as Node.js 14.x
- Under Permissions, select Use and Existing Role and select the role created at Step 1
- Copy-Paste the below code for checkInventory (1 below)
- Click Deploy
Now repeat this step for (2-8 lambda code below)
1 - checkInventory
console.log('Loading function checkInventory');
exports.handler = async (event, context) => {
var x = {ItemStock: 0};
if (event.bookId == 343222)
x = {ItemStock: 20};
return x;
};
- 2 - OrderToPrint
console.log('Loading function orderToPrint');
exports.handler = async (event, context) => {
console.log('Printing the Order Book');
var retJson = { TotalValue: 500 };
return retJson;
};
- 3 - checkFurtherDiscountEligibility
console.log('Loading function check Further Discount');
exports.handler = async (event, context) => {
var TotalDiscount = { Discount: 10 };
if (event.TotalValue > 400){
TotalDiscount = { Discount: 20 };
}
return TotalDiscount;
};
- 4 - generateDiscountCode
console.log('Loading function generate Discount Code');
exports.handler = async (event, context) => {
//console.log('Received event:', JSON.stringify(event, null, 2));
var Disc = { DiscountCode: "Hello10" };
if (event.Discount >20 )
Disc = { DiscountCode: "Hello20" };
return Disc;
};
- 5 - updateLoyaltyPoints
console.log('Loading function update Loyalty Points');
exports.handler = async (event, context) => {
var LoyaltyPoints = { LoyaltyPoints: event.TotalValue };
return LoyaltyPoints;
};
- 6 - prepareOrder
console.log('Loading function prepare Order');
exports.handler = async (event, context) => {
var shipmsg = { Shipmsg: "Order Prepared - Ready for Shipment"};
console.log(' Order Prepared - Ready for Shipment');
return shipmsg;
};
- 7 - sendToShipment
console.log('Loading function send to shipment');
exports.handler = async (event, context) => {
//console.log('Received event:', JSON.stringify(event, null, 2));
var shipment = { ShipmentSent : "True" };
return shipment;
};
- 8 - updateRecoEngine
console.log('Loading function update Reco Engine');
exports.handler = async (event, context) => {
var Reco = { RecoengineUpdated : "True"};
return Reco;
};
Order Flow - Step 3 - Create Step Functions
In the AWS Console, search Step Functions, click on State Machines in the left hand pane
Choose Authoring method as Design you workflow visually and select Type as Standard, Click Next
On the Next Screen, you can choose to design the workflow by using the Lambda Actions and decision making Flow as per our example statement or you can use the code below
{
"Comment": "An Order Flow example of the Amazon States Language using Lambda",
"StartAt": "Order Handling",
"States": {
"Order Handling": {
"Type": "Pass",
"Next": "CheckInventory"
},
"CheckInventory": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"OutputPath": "$.Payload",
"Parameters": {
"Payload.$": "$"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
],
"Next": "Choice"
},
"Choice": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.ItemStock",
"NumericGreaterThan": 0,
"Next": "Pass"
}
],
"Default": "OrderPrint"
},
"Pass": {
"Type": "Pass",
"Next": "Parallel"
},
"OrderPrint": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"OutputPath": "$.Payload",
"Parameters": {
"Payload.$": "$"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
],
"Next": "Parallel"
},
"Parallel": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "CheckFurtherDiscountEligibility",
"States": {
"CheckFurtherDiscountEligibility": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"OutputPath": "$.Payload",
"Parameters": {
"Payload.$": "$"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
],
"Next": "GenerateDiscountCode"
},
"GenerateDiscountCode": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"OutputPath": "$.Payload",
"Parameters": {
"Payload.$": "$"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
],
"End": true
}
}
},
{
"StartAt": "UpdateLoyaltyPoints",
"States": {
"UpdateLoyaltyPoints": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"OutputPath": "$.Payload",
"Parameters": {
"Payload.$": "$"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
],
"End": true
}
}
},
{
"StartAt": "PrepareOrder",
"States": {
"PrepareOrder": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"OutputPath": "$.Payload",
"Parameters": {
"Payload.$": "$"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
],
"Next": "SendToShipment"
},
"SendToShipment": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"OutputPath": "$.Payload",
"Parameters": {
"Payload.$": "$"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
],
"End": true
}
}
},
{
"StartAt": "UpdateRecoEngine",
"States": {
"UpdateRecoEngine": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"OutputPath": "$.Payload",
"Parameters": {
"Payload.$": "$"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
],
"End": true
}
}
}
],
"Next": "Order Handled"
},
"Order Handled": {
"Type": "Pass",
"End": true
}
}
}
Replace the arn:aws:states:::lambda:invoke with lambda that you specifically created in Step 2.
Click Next, review generated code
Click Next and Specify state name, under permissions, select the existing role that you had created earlier, keep the rest setting as default and click create state machine
Order Flow - Final Testing.
So now you are ready with a working state machine and it's time to test.
- Go to State Machine and click on View Details
- Click on Start Execution
- For the test, i have created two types of inputs, book id = 343222 which has inventory and any other number which will not have inventory, lets try it out now.
- Enter the below input: (With Inventory)
{
"orderId": "123",
"bookId": "343222"
}
Result is:
** Note it goes to inventory Available flow **
- Now lets try another input without inventory
{
"orderId": "124",
"bookId": "343122"
}
Result is:
** Note it goes to the book printing flow**
Top comments (2)
Hi Hardik, thnaks for the fantastic post. As I was testing the setup, I have ran into the error:
{
"error": "States.Runtime",
"cause": "An error occurred while executing the state 'CheckInventory' (entered at the event id #4). Invalid path '$.Payload' : No results for path: $['Payload']"
}
Error is fixed with updated CheckInventory Task, by removing the "OutputPath" and "Parameters", as:
"CheckInventory": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Retry": [
{ ....
Thanks!
Hi @nikolabravo , Thanks a lot for this.
I will check and correct it.
Apologies for late reply as didnt get any alert on the comment :-).