AWS Step Functions - Simple Order Flow
Tags: aws, lambda, node
Published: 2021-09-13 08:34:04

## AWS Step Functions - Simple Order Flow Example (Step by Step Guide)


## What is [AWS Step Functions?](https://aws.amazon.com/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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mjp9av6ec8lig90z9voe.png)
 
2. Click on **Create Role** 
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x6f7ccp4q49coyhvqu7d.png)

3. On the next screen, keep the default **AWS Service** option selected and under the list of Services choose **Step Functions** 
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/or19ibmny0vusu7ja9kr.png)

4. Leave the rest as default and click Next on next 3 screens, Give a **Role Name** and click **Create Role**
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iq6khkcf40x5x0sk4i8g.png)

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
1. In the AWS Console, search **Step Functions**, click on **State Machines** in the left hand pane
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fjczoeae5px6rkzlvai7.png)

2. Click on 'Create State Machine' Button
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lspermgvmg4qx7934bvy.png)

3. Choose Authoring method as **Design you workflow visually** and select Type as **Standard**, Click Next
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ek5vzqmk81ilp66wvnqo.png)

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
    }
  }
}
```
5. Replace the **arn:aws:states:::lambda:invoke** with lambda that you specifically created in Step 2.

6. Click **Next**, review generated code
7. 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/na04tnrhpevzn9egcux0.png)

### 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"
} 
```
Result is:
** Note it goes to inventory Available flow **
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f2o9t261rvcm3o0pe7k7.png)
 
5. Now lets try another input without inventory
```
{
  "orderId": "124",
  "bookId": "343122"
} 
```
Result is:
** Note it goes to the book printing flow**
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rxlewboul549vnzqah11.png)
 

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