DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for AWS Step Functions - Simple Order Flow

AWS Step Functions - Simple Order Flow

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

  1. On Order Submit, system will check the available inventory of the book.
  2. If inventory available, then proceed further. If not available, then trigger a printOrder and wait for the book to be printed.
  3. Once the book is printed, the system will prepare order for delivery and trigger the send shipment flow.
  4. 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

  1. Create an IAM Role for the AWS Step function to be able to execute the AWS Services ( eg:- Lambda in this case)
  2. Create Lambda Executor Functions
  3. 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
image

  1. Click on Create Role
    image

  2. On the next screen, keep the default AWS Service option selected and under the list of Services choose Step Functions
    image

  3. Leave the rest as default and click Next on next 3 screens, Give a Role Name and click Create Role
    image

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;  
};
Enter fullscreen mode Exit fullscreen mode
  • 2 - OrderToPrint
console.log('Loading function orderToPrint');

exports.handler = async (event, context) => {
    console.log('Printing the Order Book');

    var retJson = { TotalValue: 500 };

    return retJson;  
};
Enter fullscreen mode Exit fullscreen mode
  • 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; 
};
Enter fullscreen mode Exit fullscreen mode
  • 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; 
};
Enter fullscreen mode Exit fullscreen mode
  • 5 - updateLoyaltyPoints
console.log('Loading function update Loyalty Points');

exports.handler = async (event, context) => {
    var LoyaltyPoints = { LoyaltyPoints: event.TotalValue };

    return LoyaltyPoints;  
};

Enter fullscreen mode Exit fullscreen mode
  • 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;  
};

Enter fullscreen mode Exit fullscreen mode
  • 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; 
};

Enter fullscreen mode Exit fullscreen mode
  • 8 - updateRecoEngine
console.log('Loading function update Reco Engine');

exports.handler = async (event, context) => {
    var Reco = { RecoengineUpdated : "True"};
    return Reco;
};

Enter fullscreen mode Exit fullscreen mode

Order Flow - Step 3 - Create Step Functions

  1. In the AWS Console, search Step Functions, click on State Machines in the left hand pane
    image

  2. Click on 'Create State Machine' Button
    image

  3. Choose Authoring method as Design you workflow visually and select Type as Standard, Click Next
    image

  4. 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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Replace the arn:aws:states:::lambda:invoke with lambda that you specifically created in Step 2.

  2. Click Next, review generated code

  3. 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
    image

Order Flow - Final Testing.

So now you are ready with a working state machine and it's time to test.

  1. Go to State Machine and click on View Details
  2. Click on Start Execution
  3. 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.
  4. Enter the below input: (With Inventory)
{
  "orderId": "123",
  "bookId": "343222"
} 
Enter fullscreen mode Exit fullscreen mode

Result is:
** Note it goes to inventory Available flow **
image

  1. Now lets try another input without inventory
{
  "orderId": "124",
  "bookId": "343122"
} 
Enter fullscreen mode Exit fullscreen mode

Result is:
** Note it goes to the book printing flow**
image

Thanks a lot. Hope this helps you with more learning on Step functions. Would love to hear your comments.

Top comments (2)

Collapse
nikolabravo profile image
Nikola Bravo

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!

Collapse
_hardikjoshi profile image
πŸ…·πŸ…°πŸ†πŸ…³πŸ…ΈπŸ…Ί πŸ…ΉπŸ…ΎπŸ†‚πŸ…·πŸ…Έ Author

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 :-).

🌚 Life is too short to browse without dark mode