AWS Step Functions makes it easy to coordinate the components of distributed applications and microservices using visual workflows.
In this lab, let's build a state machine to orchestrate a tax microservice.
Access the AWS Step Functions console at https://console.aws.amazon.com/states
If you're in the initial Splash Screen
page, click on Get Started
, if not just ignore this step
Click on Create a state machine
Under Step 1, for Name your state machine
, enter ProductOrderPipeline-[YOUR_ORG_ID]
Under Step 2, for Select a blueprint
, choose Hello World
You'll be prompted with a dialog box titled IAM role for your...
OK
Let's now take this for a quick spin
New execution
{
"Who": "Big Daddy!"
}
Start Execution
We have now successfully created and executed our very first (albeit, very simple) State Machine using AWS Step Functions and Lambda.
Let's take this a bit further and explore more complex state machines and how we'd go about building them. Before we go ahead though, we'll need a few Lambda functions to execute at various stages in the State machine. So let's create a few additional Lambda functions (these will serve as our rudimentary mico-services).
Once we're done creating our Lambda functions, we will create a state machine using Step Functions to orchestrate them.
You can use the previously created SimpleTaxCalculator Lambda function here, no need to create a new one. But I'm adding the code here anyway.
Ensure that you pick the one that's yours: SimpleTaxCalculator-[YOUR_ORG_ID]
'use strict';
console.log('Loading tax calculator function...');
exports.handler = (event, context, callback) => {
console.log('Received event: ', JSON.stringify(event, null, 2));
try {
console.log(`Product price: $${event.productPrice}`);
console.log(`Tax rate: ${event.taxRate}%`);
console.log(`Surcharge rate: ${event.surchargeRate}%`);
let tax = event.productPrice * (event.taxRate / 100.00);
let surcharge = event.productPrice * (event.surchargeRate / 100.00);
let finalPrice = event.productPrice + tax + surcharge;
console.log(`Final price with tax: $${finalPrice}`);
let returnValue = {
price: finalPrice
}
// On success, invoke the callback like so (2 arguments)
// first one being null.
callback(null, returnValue); // Return calculated tax
}
catch(e) {
console.log(e);
// On failure, invoke the callback like so (a single argument)
// with a helpful error message.
callback('ERROR: Something went wrong!');
}
console.log("Done calculating tax.");
};
Follow the same steps as before to create a brand new Lambda function. Let's name this CaliforniaTaxCalculator-[YOUR_ORG_ID]. Just ensure that you add the following values to each of the fields in the Create Lambda
screen.
Name: CaliforniaTaxCalculator
Runtime*: Node.js 6.10
Role*: Choose an existing role
Existing Role*: lambda_execution_role_YOUR_ORG_ID
Once created, edit the code inline and add this:
'use strict';
console.log('Loading California tax calculator function...');
const CA_STATE_TAX_RATE = 20;
exports.handler = (event, context, callback) => {
console.log('Received event: ', JSON.stringify(event, null, 2));
try {
console.log(`Product price: $${event.price}`);
console.log(`Calculating California state taxes at ${CA_STATE_TAX_RATE} rate`)
let stateTax = event.price * (CA_STATE_TAX_RATE / 100.00);
let finalPrice = event.price + stateTax;
console.log(`Final price with tax: $${finalPrice}`);
// On success, invoke the callback like so (2 arguments)
// first one being null.
callback(null, finalPrice); // Return calculated tax
}
catch(e) {
console.log(e);
// On failure, invoke the callback like so (a single argument)
// with a helpful error message.
callback('ERROR: Something went wrong!');
}
console.log("Done calculating California tax.");
};
Testand configuring a test event like so:
{
"price": 20
}
Follow the same steps and add a WisconsinTaxCalculator-[YOUR_ORG_ID].
Use the same execution role as before. All of our Lambda functions will use the same execution role when running.
Edit the code inline and update it to the below:
'use strict';
console.log('Loading Wisconsin tax calculator function...');
const WI_STATE_TAX_RATE = 10; // cheaper than California
exports.handler = (event, context, callback) => {
console.log('Received event: ', JSON.stringify(event, null, 2));
try {
console.log(`Product price: $${event.price}`);
console.log(`Calculating Wisconsin state taxes at ${WI_STATE_TAX_RATE} rate`)
let stateTax = event.price * (WI_STATE_TAX_RATE / 100.00);
let finalPrice = event.price + stateTax;
console.log(`Final price with tax: $${finalPrice}`);
// On success, invoke the callback like so (2 arguments)
// first one being null.
callback(null, finalPrice); // Return calculated tax
}
catch(e) {
console.log(e);
// On failure, invoke the callback like so (a single argument)
// with a helpful error message.
callback('ERROR: Something went wrong!');
}
console.log("Done calculating Wisconsin tax.");
};
{
"price": 20
}
Let's modify the new Step Function that we just created in Step 1 to use the Lambda functions that we just created.
State machine detailstab
Codeand
Visual Workflow
Codehere is JSON and describes the flow of the state machine
Visual Workflowis a visual representation of this state machine
Edit state machine
Codeto below:
{
"Comment": "A more complicated tax calculator",
"StartAt": "SimpleTaxCalculator",
"States": {
"SimpleTaxCalculator": {
"Type": "Task",
"Resource": "[MOVE CURSOR HERE TO SEE OPTIONS]",
"Next": "ChooseState"
},
"ChooseState": {
"Type" : "Choice",
"Choices": [
{
"Variable": "$.state",
"StringEquals": "CA",
"Next": "CaliforniaTaxCalculator"
},
{
"Variable": "$.state",
"StringEquals": "WI",
"Next": "WisconsinTaxCalculator"
}
],
"Default": "InvalidState"
},
"CaliforniaTaxCalculator": {
"Type" : "Task",
"Resource": "[MOVE CURSOR HERE TO SEE OPTIONS]",
"End": true
},
"WisconsinTaxCalculator": {
"Type" : "Task",
"Resource": "[MOVE CURSOR HERE TO SEE OPTIONS]",
"End": true
},
"InvalidState": {
"Type": "Fail",
"Error": "Cannot calculate state tax!",
"Cause": "Invalid State!"
}
}
}
Visual Workflowis automatically updated to reflect the changes in the state machine
Resourcekey
Updateagain
Start Execution
{
"productPrice": 20,
"taxRate": 10,
"surchargeRate": 1
}
Start Executionto execute.
You will notice that the Step Function just failed. Can you figure out why?
Expand the result of each state and dig in...
Now that we've identified the issue, let's fix this by updating the original code for SimpleTaxCalculator.
state: "CA"
let returnValue = {
price: finalPrice,
state: "CA"
}
Save
Testand test that the lambda function still works using the test event that we've already configured and used previously.
New Execution
{
"productPrice": 20,
"taxRate": 10,
"surchargeRate": 1
}
Start Executionto execute
This time, notice that the state machine branched off to the correct states and finished executing successfully.
For this, let's deliberately update the State Machine Code to something invalid and see what kind of errors we get.
To do this, go to the Step Functions console to edit the State Machine. Refer to the instructions in Step 3 for the full monty.
Update the Stat Machine Code to the below:
{
"Comment": "A more complicated tax calculator",
"StartAt": "SimpleTaxCalculator",
"States": {
"SimpleTaxCalculator": {
"Type": "Task",
"Resource": "[MOVE CURSOR HERE TO SEE OPTIONS]",
"Next": "FirstState"
},
"ChooseState": {
"Type" : "Choice",
"Choices": [
{
"Variable": "$.state",
"StringEquals": "CA",
"Next": "CaliforniaTaxCalculator"
},
{
"Variable": "$.state",
"StringEquals": "WI",
"Next": "WisconsinTaxCalculator"
}
],
"Default": "InvalidState"
},
"CaliforniaTaxCalculator": {
"Type" : "Task",
"Resource": "[MOVE CURSOR HERE TO SEE OPTIONS]",
"End": "true"
},
"WisconsinTaxCalculator": {
"Type" : "Task",
"Resource": "[MOVE CURSOR HERE TO SEE OPTIONS]",
"End": true
},
"InvalidState": {
"Type": "Fail",
"Error": "Cannot calculate state tax!",
"Cause": "Invalid State!"
}
}
}
Updateand notice the errors it spits out.
Can you figure out why?
See here for more information on how to define StepFunctions using Amazon States Language