Loop inside a Step Function - amazon-web-services

I am trying to call a couple of steps in my step function in a loop but I am unable to get my head around how I need to do this. Here's what I have till now: I need to add another lambda function(GetReviews) which will then call CreateReview, SendNotification in a loop. How would I go about doing this?
I am referring to the "Iterating a Loop Using Lambda" document, which shows it is possible.
Step function Defination:
{
"Comment": "Scheduling Engine",
"StartAt": "CreateReview",
"States": {
"CreateReview": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-west-2:529627678433:function:CreateReview",
"Next": "CreateNotification",
"InputPath": "$",
"ResultPath": "$.CreateReviewResult",
"OutputPath": "$"
},
"CreateNotification": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-west-2:529627678433:function:CreateNotification",
"InputPath": "$",
"ResultPath": "$.CreateNotificationResult",
"OutputPath": "$",
"End": true
}
}
}

I am contributing to this answer because I am using a little different approach to be able to loop inside of a step-function without having to rely on a lambda to increment. If someone in the future needs a generic solution, this can be a good reference.
Here is the example with code:
{
"Comment": "A description of my state machine",
"StartAt": "InitVariables",
"States": {
"InitVariables": {
"Type": "Pass",
"Parameters": {
"index": 0,
"incrementor": 1,
"ArrayLength.$": "States.ArrayLength($.inputArray)"
},
"ResultPath": "$.iterator",
"Next": "LoopChoice"
},
"LoopChoice": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.iterator.ArrayLength",
"NumericGreaterThanPath": "$.iterator.index",
"Next": "IncrementVariable"
}
],
"Default": "End"
},
"IncrementVariable": {
"Type": "Pass",
"Parameters": {
"index.$": "States.MathAdd($.iterator.index, $.iterator.incrementor)",
"incrementor": 1,
"ArrayLength.$": "$.iterator.ArrayLength"
},
"ResultPath": "$.iterator",
"Next": "LoopChoice"
},
"End": {
"Type": "Pass",
"End": true
}
} }
This is the base for the loop, I use the States.MathAdd($.iterator.index, $.iterator.incrementor) intrinsic function to add two values, in this case, increment the index with a increment amount defined in the initVariables state. And also get the length of the array that I want to loop. You get the array length by also using a intrinsic function, States.ArrayLength("$.path.to.array"). The array is passed in the input.
To get the value of the array we can use the intrinsic function, States.ArrayGetItem($.inputArray, $.iterator.index).
All the custom logic should be put between the loopChoice state and the IncrementVariable State.
Hope this helps someone in the future.

Sorry for the late reply. You've probably solved it in between, but here you are
So, when looping in Step Functions, I quite simply add a Choice State (see Choice State Rules).
One of your State would need to output wether or not you have finished looping, or the number of items iterated on and the total number of items.
In the first case, it would be something like
{
"Comment": "Scheduling Engine",
"StartAt": "CreateReview",
"States": {
"GetReviews": {
whatever
"Next": "LoopChoiceState"
},
"LoopChoiceState": {
"Type" : "Choice",
"Choices": [
{
"Variable": "$.loopCompleted",
"BooleanEquals": false,
"Next": "GetReviews"
}
],
"Default": "YourEndState"
},
"CreateReview": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-westz2:529627678433:function:CreateReview",
"Next": "CreateNotification",
"InputPath": "$",
"ResultPath": "$.CreateReviewResult",
"OutputPath": "$"
},
"CreateNotification": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-west-2:529627678433:function:CreateNotification",
"InputPath": "$",
"ResultPath": "$.CreateNotificationResult",
"OutputPath": "$",
"End": true
}
}
}
Second case:
{
"Comment": "Scheduling Engine",
"StartAt": "CreateReview",
"States": {
"GetReviews": {
whatever
"Next": "LoopChoiceState"
},
"LoopChoiceState": {
"Type" : "Choice",
"Choices": [
{
"Variable": "$.iteratedItemsCount",
"NumericEquals": "$.totalItemsCount",
"Next": "CreateNotification"
}
],
"Default": "CreateReview"
},
"CreateReview": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-west-2:529627678433:function:CreateReview",
"Next": "CreateNotification",
"InputPath": "$",
"ResultPath": "$.CreateReviewResult",
"OutputPath": "$"
},
"CreateNotification": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-west-2:529627678433:function:CreateNotification",
"InputPath": "$",
"ResultPath": "$.CreateNotificationResult",
"OutputPath": "$",
"End": true
}
}
}
You could also use indexes (current index and last index) instead of the the number of items iterated over; it would help you keep track of where you in create reviews.

Three options are presented below. Here is a visual summary:
#1 Map: Repeat a set of steps for each element of an array (without a loop, optionally concurrently)
Map State is an alternative to looping when you want to run a set of steps for each element of an input array. Each element runs in parallel by default. Set MaxConcurrency: 1 to mimic the serial execution of #NunoGamaFreire's loop-based solution.
{
"StartAt": "MockArray",
"States": {
"MockArray": {
"Type": "Pass",
"Result": [ { "name": "Zaphod" }, { "name": "Arthur" }, { "name": "Trillian" } ],
"ResultPath": "$.Items",
"Next": "MapState"
},
"MapState": {
"Type": "Map",
"ResultPath": "$.MapResult",
"End": true,
"Iterator": {
"StartAt": "MockWork",
"States": {
"MockWork": {
"Type": "Pass",
"Parameters": {
"output.$": "States.Format('Hello, {}!', $.name)"
},
"OutputPath": "$.output",
"End": true
}
}
},
"ItemsPath": "$.Items"
}
}
}
One output is produced for each element in the MockArray, processed concurrently:
"MapResult": [ "Hello, Zaphod!", "Hello, Arthur!", "Hello, Trillian!" ]
#2 Repeat a set of steps X times (loop without lambda, serially)
This option involves proper looping! Repeat a set of tasks serially X number of times until a Choice State determines that an incrementing counter variable has reached X. Use the new States.MathAdd intrinsic function to increment without a Lambda. This option is suited for custom retry logic or other cases when you may want to break the loop early.
{
"StartAt": "InitializeCounter",
"States": {
"InitializeCounter": {
"Type": "Pass",
"Comment": "Initialize the counter at 0. Move all inputs to Payload.Input",
"Parameters": {
"Counter": 0
},
"Next": "IncrementCounter"
},
"IncrementCounter": {
"Type": "Pass",
"Comment": "Increment the Counter by 1",
"Parameters": {
"Counter.$": "States.MathAdd($.Counter, 1)"
},
"Next": "MockWork"
},
"MockWork": {
"Type": "Pass",
"Comment": "Simulate some work. Optionally break early from the loop with ExitNow: true",
"Result": false,
"ResultPath": "$.ExitNow",
"Next": "Loop?"
},
"Loop?": {
"Type": "Choice",
"Choices": [{ "Or": [
{ "Variable": "$.Counter", "NumericGreaterThanEqualsPath": "$$.Execution.Input.workCount" },
{ "Variable": "$.ExitNow", "BooleanEquals": true } ],
"Next": "Success"
}
],
"Default": "IncrementCounter"
},
"Success": {
"Type": "Succeed"
}
},
"TimeoutSeconds": 3
}
Counter iterates by one for each loop. The loop breaks if a task returns ExitNow: true.
{ "Counter": 4, "ExitNow": false }
#3 Repeat a set of steps X times (without a loop, *concurrently*)
This option is a hybrid of the first two. Like #2, we start with a desired number of iterations from the $.workCount input . Like #1, we map over an array concurrently. This time, though, the state machine creates the array with another intrinsic function, States.ArrayRange(1, $.workCount, 1).
{
"StartAt": "Iterations",
"States": {
"Iterations": {
"Type": "Pass",
"Parameters": {
"Iterations.$": "States.ArrayRange(1, $.workCount, 1)"
},
"Next": "MapState"
},
"MapState": {
"Type": "Map",
"ResultPath": "$.MapResult",
"End": true,
"Iterator": {
"StartAt": "MockWork",
"States": {
"MockWork": {
"Type": "Pass",
"Parameters": {
"output.$": "States.Format('Hello from iteration #{}', States.JsonToString($))"
},
"OutputPath": "$.output",
"End": true
}
}
},
"ItemsPath": "$.Iterations"
}
}
}
Tasks run concurrently, once for each item in Iterations.
"Iterations": [ 1, 2, 3, 4 ],
"MapResult": [ "Hello from iteration #1", "Hello from iteration #2", "Hello from iteration #3", "Hello from iteration #4" ]

Related

Workflow has no terminal state

I am creating a workflow with AWS Step Function where I am first checking if a record exists in database, then based on the records there are two branches and each of them end at either Succeed or Failed state, but I am still getting Workflow has no end state error.
Following is the JSON for workflow
{
"Comment": "A demo state machine",
"StartAt": "FindCategory",
"States": {
"FindCategory": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:xxxxxxx:function:xxxxxx",
"Next": "Exists?"
},
"Exists?": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.exists",
"BooleanEquals": true,
"Next": "Yes"
},
{
"Variable": "$.exists",
"BooleanEquals": false,
"Next": "No"
}
]
},
"Yes": {
"Type": "Pass",
"Next": "GetQuestions"
},
"GetQuestions": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:xxxxxxxxxxxxxx",
"Next": "ReplyWithPolls"
},
"ReplyWithPolls": {
"Type": "Map",
"MaxConcurrency": 2,
"Iterator": {
"StartAt": "SendPoll",
"States": {
"SendPoll": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:xxxxxxx",
"Next": "SendPoll"
}
}
},
"Next": "Succeed"
},
"No": {
"Type": "Pass",
"Next": "FailState"
},
"Succeed": {
"Type": "Succeed"
},
"FailState": {
"Type": "Fail",
"Error": "404",
"Cause": "Category not found"
}
}
}
I believe the problem is that your SendPoll state results in an infinite loop. It references itself as next. Instead, the state in the iterator should be a terminal state.
Replace the "Next" field in "SendPoll" state with an "End" field.
"SendPoll": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:xxxxxxx",
"End": true
}

Can AWS Step Function describe this kind of dataflow?

It can not be described with Parallel State in AWS Step Function.
B and C should be in parallel.
C sends messages to both D and E.
D and E should be in parallel.
{
"StartAt": "A",
"States": {
"A": {
"Type": "Pass",
"Next": "Parallel State 1"
},
"Parallel State 1": {
"Type": "Parallel",
"Branches": [{
"StartAt": "B",
"States": {
"B": {
"Type": "Pass",
"End": true
}
}
},
{
"StartAt": "C",
"States": {
"C": {
"Type": "Pass",
"End": true
}
}
}
],
"Next": "Parallel State 2"
},
"Parallel State 2": {
"Type": "Parallel",
"Branches": [{
"StartAt": "D",
"States": {
"D": {
"Type": "Pass",
"End": true
}
}
},
{
"StartAt": "E",
"States": {
"E": {
"Type": "Pass",
"End": true
}
}
}
],
"Next": "F"
},
"F": {
"Type": "Pass",
"End": true
}
}
}
Answer is No , inside step function no state can set multiple states (invokes both successors)to its Next task. As per AWS step function cannot start State Machine as StartAt by providing multiple State names.
You can tweak your logic and use The Parallel state and achive same ,If you share your usecase may be help to solve problems.
How to specify multiple result path values in AWS Step Functions
A Parallel state provides each branch with a copy of its own input
data (subject to modification by the InputPath field). It generates
output that is an array with one element for each branch, containing
the output from that branch.
https://aws.amazon.com/blogs/aws/new-step-functions-support-for-dynamic-parallelism/
Example of state function
{
"Comment": "An example of the Amazon States Language using a choice state.",
"StartAt": "FirstState",
"States": {
"FirstState": {
"Type": "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
"Next": "ChoiceState"
},
"ChoiceState": {
"Type" : "Choice",
"Choices": [
{
"Variable": "$.foo",
"NumericEquals": 1,
"Next": "FirstMatchState"
},
{
"Variable": "$.foo",
"NumericEquals": 2,
"Next": "SecondMatchState"
}
],
"Default": "DefaultState"
},
"FirstMatchState": {
"Type" : "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:OnFirstMatch",
"Next": "NextState"
},
"SecondMatchState": {
"Type" : "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:OnSecondMatch",
"Next": "NextState"
},
"DefaultState": {
"Type": "Fail",
"Error": "DefaultStateError",
"Cause": "No Matches!"
},
"NextState": {
"Type": "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
"End": true
}
}
}
https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-resource.html#connect-wait-example
https://sachabarbs.wordpress.com/2018/10/30/aws-step-functions/
As I answered in How to simplify complex parallel branch interdependencies for Step Functions, what you asked is better to be modeled as DAG but not state machine.
Depends on your use case, you might be able to workaround it (just as #horatiu-jeflea 's answer), but it's a workaround (not the straightforward way) anyway.

How to capture output of a parallel state machine in AWS Step function

I need to run an AWS Step function that runs a parallel state machine running, say two state machines. My requirement is to check the final execution status of the parallel machine and if there is any failure, invoke an SNS service to send out an email. Pretty standard stuff but for the life of me, i can't figure out how to capture the combined error of a parallel step machine. This sample parallel machine runs
A "passtask" that is just a simple lambda pass function,
and
Runs a failtask that has a sleep timer for 5 seconds and is suppposed to fail after 5 seconds.
If I execute this machine, this machine correctly shows passtask as succeeded, failtask as cancelled, Overall Parallel Task as succeeded (?????), Notify Failure task as cancelled and the overall execution of state machine as "failed" as well.
I'd like to see passtask as succeeded, fail task as failed, overall Parallel Task as Failed, Notify Failure task as succeeded.
{
"Comment": "Parallel Example",
"StartAt": "Parallel Task",
"TimeoutSeconds": 120,
"States": {
"Parallel Task": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "passtask",
"States": {
"passtask": {
"Type": "Task",
"Resource":"arn:xxxxxxxxxxxxxxx:function:passfunction",
"End": true
}
}
},
{
"StartAt": "failtask",
"States": {
"failtask": {
"Type": "Task",
"Resource":"arn: xxxxxxxxxxxxxxx:function:failfunction",
"End": true
}
}
}
],
"ResultPath": "$.status",
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "Notify Failure"
}
],
"Next": "Notify Success"
},
"Notify Failure": {
"Type": "Pass",
"InputPath": "$.input.Cause",
"End": true
},
"Notify Success": {
"Type": "Pass",
"Result": "This is a fallback from a task success",
"End": true
}
}
}
From your requirment "My requirement is to check the final execution status of the parallel machine and if there is any failure, invoke an SNS service to send out an email.", I understand that the "failtask" is just for debugging purposes and in the future it won't neccesarily fail. So the problem is, the moment Step Functions detect a failure in a branch all other branches are terminated and their outputs discarded, only the failed branch's output is used. So if you want to preserve the output of each Branch and check if a failure has occured, you will need to handle the errors in each branch and not report the whole branch as failed. Additionally you will need to add an output field to each branch which says if there was a failure or not (Choice State will give an error if a field does not exist). And also remember that the output of a ParralelState is an array with the output of each Branch, for example this State Machine should let each branch finish execution and handle the errors correctly:
{
"Comment": "Parallel Example",
"StartAt": "Parallel Task",
"TimeoutSeconds": 120,
"States": {
"Parallel Task": {
"Type": "Parallel",
"Branches": [{
"StartAt": "passtask",
"States": {
"passtask": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:XXXXXXXXXXXXXXXXX",
"Next": "SuccessBranch1",
"Catch": [{
"ErrorEquals": ["States.ALL"],
"Next": "FailBranch1"
}]
},
"SuccessBranch1": {
"Type": "Pass",
"Result": {
"Error": false
},
"ResultPath": "$.Status",
"End": true
},
"FailBranch1": {
"Type": "Pass",
"Result": {
"Error": true
},
"ResultPath": "$.Status",
"End": true
}
}
},
{
"StartAt": "failtask",
"States": {
"failtask": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:XXXXXXXXXXXXXXXXX",
"Next": "SuccessBranch2",
"Catch": [{
"ErrorEquals": ["States.ALL"],
"Next": "FailBranch2"
}]
},
"SuccessBranch2": {
"Type": "Pass",
"Result": {
"Error": false
},
"ResultPath": "$.Status",
"End": true
},
"FailBranch2": {
"Type": "Pass",
"Result": {
"Error": true
},
"ResultPath": "$.Status",
"End": true
}
}
}
],
"ResultPath": "$.ParralelOutput",
"Catch": [{
"Comment": "This catch should never catch any errors, as the error handling is done in the individual Branches",
"ErrorEquals": ["States.ALL"],
"ResultPath": "$.ParralelOutput",
"Next": "ChoiceStateX"
}],
"Next": "ChoiceStateX"
},
"ChoiceStateX": {
"Type": "Choice",
"Choices": [{
"Or": [{
"Variable": "$.ParralelOutput[0].Status.Error",
"BooleanEquals": true
},
{
"Variable": "$.ParralelOutput[1].Status.Error",
"BooleanEquals": true
}
],
"Next": "Notify Failure"
}],
"Default": "Notify Success"
},
"Notify Failure": {
"Type": "Pass",
"End": true
},
"Notify Success": {
"Type": "Pass",
"Result": "This is a fallback from a task success",
"End": true
}
}
}
For a more general case (although more complex) of the above as asked by Nisman in the comments. Instead of hardcoding the Choice State to check for every branch we can add a pass state with some JSONPath tricks to check for conditions not currently possible with a choice state alone.
Inside this Pass State we use Parameters to restructure our data in such a way that when we apply a JSONPath filter expression to this data using the OutputPath we are left with an array of either 2 (if no branches failed) or 3 (if some branches failed) elements, where the first element always contains the original input data and the second/third contains at least 1 key with the same name to be used by the choice state. Here's the State Machine JSON:
{
"Comment": "Parallel Example",
"StartAt": "Parallel Task",
"States": {
"Parallel Task": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "passtask",
"States": {
"passtask": {
"Type": "Task",
"Resource": "<TASK RESOURCE>",
"End": true,
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"ResultPath": "$.error-info",
"Next": "FailBranch1"
}
]
},
"FailBranch1": {
"Type": "Pass",
"Parameters": {
"BranchOutput.$": "$",
"BranchError": true
},
"End": true
}
}
},
{
"StartAt": "failtask",
"States": {
"failtask": {
"Type": "Task",
"Resource": "<TASK RESOURCE>",
"End": true,
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"ResultPath": "$.error-info",
"Next": "FailBranch2"
}
]
},
"FailBranch2": {
"Type": "Pass",
"Parameters": {
"BranchOutput.$": "$",
"BranchError": true
},
"End": true
}
}
}
],
"ResultPath": "$.ParralelOutput",
"Next": "Pre-Process"
},
"Pre-Process": {
"Type": "Pass",
"Parameters": {
"OrderedArray": [
{
"OriginalData": {
"Input.$": "$",
"ShouldFilterData": false
}
},
{
"ValuesToCheck": {
"ListBranchErrors.$": "$.ParralelOutput[?(#.BranchError==true)].BranchError",
"BranchFailures": true
}
},
{
"DefaultAlwaysFalse": {
"ShouldFilterData": false,
"BranchFailures": false
}
}
]
},
"OutputPath": "$..[?(#.ShouldFilterData == false || #.ListBranchErrors[0] == true)]",
"Next": "ChoiceStateX"
},
"ChoiceStateX": {
"Type": "Choice",
"OutputPath": "$.[0].Input",
"Choices": [
{
"Variable": "$[1].BranchFailures",
"BooleanEquals": true,
"Next": "NotifyFailure"
},
{
"Variable": "$[1].BranchFailures",
"BooleanEquals": false,
"Next": "NotifySuccess"
}
],
"Default": "NotifyFailure"
},
"NotifyFailure": {
"Type": "Pass",
"End": true
},
"NotifySuccess": {
"Type": "Pass",
"Result": "This is a fallback from a task success",
"End": true
}
}
}

Parallel States Merge the output in Step Function

Is it possible to have following kind of Step Function graph, i.e. from 2 parallel state output, one combined state:
If yes, what would json for this looks like? If not, why?
A parallel task always outputs an array (containing one entry per branch).
You can tell AWS step functions to append the output into new (or existing) property in the original input with "ResultPath": "$.ParallelOut" in your parallel state definition, but this is not what you seem to be trying to achieve.
To merge the output of parallel task, you can leverage the "Type": "Pass" state to define transformations to apply to the JSON document.
For example, in the state machine below, I'm transforming a JSON array...
[
{
"One": 1,
"Two": 2
},
{
"Foo": "Bar",
"Hello": "World"
}
]
...into a few properties
{
"Hello": "World",
"One": 1,
"Foo": "Bar",
"Two": 2
}
{
"Comment": "How to convert an array into properties",
"StartAt": "warm-up",
"States": {
"warm-up": {
"Type": "Parallel",
"Next": "array-to-properties",
"Branches": [
{
"StartAt": "numbers",
"States": {
"numbers": {
"Type": "Pass",
"Result": {
"One": 1,
"Two" : 2
},
"End": true
}
}
},
{
"StartAt": "words",
"States": {
"words": {
"Type": "Pass",
"Result": {
"Foo": "Bar",
"Hello": "World"
},
"End": true
}
}
}
]
},
"array-to-properties": {
"Type": "Pass",
"Parameters": {
"One.$": "$[0].One",
"Two.$": "$[0].Two",
"Foo.$": "$[1].Foo",
"Hello.$": "$[1].Hello"
},
"End": true
}
}
}
It is possible as opposed diagram below
The parallel state should look like this
"MyParallelState": {
"Type": "Parallel",
"InputPath": "$",
"OutputPath": "$",
"ResultPath": "$.ParallelResultPath",
"Next": "SetCartCompleteStatusState",
"Branches": [
{
"StartAt": "UpdateMonthlyUsageState",
"States": {
"UpdateMonthlyUsageState": {
"Type": "Task",
"InputPath": "$",
"OutputPath": "$",
"ResultPath": "$.UpdateMonthlyUsageResultPath",
"Resource": "LambdaARN",
"End": true
}
}
},
{
"StartAt": "QueueTaxInvoiceState",
"States": {
"QueueTaxInvoiceState": {
"Type": "Task",
"InputPath": "$",
"OutputPath": "$",
"ResultPath": "$.QueueTaxInvoiceResultPath",
"Resource": "LambdaARN",
"End": true
}
}
}
The output of MyParallelState will be populated as in array, from each state in the Parallel state. They are populated within ParallelResultPath object and will be passed into the Next state
{
"ParallelResultPath": [
{
"UpdateMonthlyUsageResultPath": Some Output
},
{
"QueueTaxInvoiceResultPath": Some Output
}
]
}
We can use ResultSelector and Result Path to combine the result into one object
We have a parallel state like:
{
"StartAt": "ParallelBranch",
"States": {
"ParallelBranch": {
"Type": "Parallel",
"ResultPath": "$",
"InputPath": "$",
"OutputPath": "$",
"ResultSelector": {
"UsersResult.$": "$[1].UsersUpload",
"CustomersResult.$": "$[0].customersDataUpload"
},
"Branches": [
{
"StartAt": "customersDataUpload",
"States": {
"customersDataUpload": {
"Type": "Pass",
"ResultPath": "$.customersDataUpload.Output",
"Result": {
"CompletionStatus": "success",
"CompletionDetails": null
},
"Next": "Wait2"
},
"Wait2": {
"Comment": "A Wait state delays the state machine from continuing for a specified time.",
"Type": "Wait",
"Seconds": 2,
"End": true
}
}
},
{
"StartAt": "UsersUpload",
"States": {
"UsersUpload": {
"Type": "Pass",
"Result": {
"CompletionStatus": "success",
"CompletionDetails": null
},
"ResultPath": "$.UsersUpload.Output",
"Next": "Wait1"
},
"Wait1": {
"Comment": "A Wait state delays the state machine from continuing for a specified time.",
"Type": "Wait",
"Seconds": 1,
"End": true
}
}
}
],
"End": true
}
},
"TimeoutSeconds": 129600,
"Version": "1.0"
}
enter image description here
And the output will be like:
{
"UsersResult": {
"Output": {
"CompletionStatus": "success",
"CompletionDetails": null
}
},
"CustomersResult": {
"Output": {
"CompletionStatus": "success",
"CompletionDetails": null
}
}
}
Your diagram is technically wrong because no state can set multiple states to its Next task. You cannot start State Machine as StartAt by providing multiple State names. Also, even if it was possible I don't see any point why would you want to run two parallel states as opposed to one parallel state with all the sub states that you would split into two.
This worked for me
"Transform And Freeze": {
"Type": "Parallel",
"InputPath": "$",
"Branches": [
{
"StartAt": "Transform Status",
"States": {
"Transform Status": {
"Type": "Map",
"ItemsPath": "$",
"MaxConcurrency": 25,
"Iterator": {
"StartAt": "Transform",
"States": {
"Transform": {
"Type": "Task",
"Resource": "${TransformFunction}",
"End": true
}
}
},
"End": true
}
}
},
{
"StartAt": "Freeze Status",
"States": {
"Freeze Status": {
"Type": "Map",
"MaxConcurrency": 25,
"Iterator": {
"StartAt": "Freeze",
"States": {
"Freeze Transactions": {
"Type": "Task",
"Resource": "${FreezeFunction}",
"End": true
}
}
},
"End": true
}
}
}
],
"ResultPath" : "$.parts",
"Next": "SetParallelOutput",
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"ResultPath": "$.exception",
"Next": "Error Handler"
}
]
},
"SetParallelOutput": {
"Type": "Pass",
"Parameters": {
"foo.$": "$.foo",
"bar.$": "$.bar",
"parts.$": "$.parts[0]"
},
"Next": "Target Type"
},

Iterating over results from previous state in AWS Step Functions

I need to develop a 'State Machine' using 'AWS Step Functions' that accomplishes the following:
Call a Lambda function that will connect to DynamoDb & retrieve a list of rows. (I know how to do this.)
For each row from previous step, I need to call another Lambda function until all rows are read.
How do I do step #2 above in AWS Step Functions? In other words, how do I iterate over the results from the previous step.
It's not pretty, but this can be accomplished out of the box / without an iterator lambda by using JSONPath's slice operator and a Sentinel value.
Here's an example state machine:
{
"Comment": "Example of how to iterate over an arrray of items in Step Functions",
"StartAt": "PrepareSentinel",
"States": {
"PrepareSentinel": {
"Comment": "First, prepare a temporary array-of-arrays, where the last value has a special SENTINEL value.",
"Type": "Pass",
"Result": [
[
],
[
"SENTINEL"
]
],
"ResultPath": "$.workList",
"Next": "GetRealWork"
},
"GetRealWork": {
"Comment": "Next, we'll populate the first array in the temporary array-of-arrays with our actual work. Change this from a Pass state to a Task/Activity that returns your real work.",
"Type": "Pass",
"Result": [
"this",
"stage",
"should",
"return",
"your",
"actual",
"work",
"array"
],
"ResultPath": "$.workList[0]",
"Next": "FlattenArrayOfArrays"
},
"FlattenArrayOfArrays": {
"Comment": "Now, flatten the temporary array-of-arrays into our real work list. The SENTINEL value will be at the end.",
"Type": "Pass",
"InputPath": "$.workList[*][*]",
"ResultPath": "$.workList",
"Next": "GetNextWorkItem"
},
"GetNextWorkItem": {
"Comment": "Extract the first work item from the workList into currentWorkItem.",
"Type": "Pass",
"InputPath": "$.workList[0]",
"ResultPath": "$.currentWorkItem",
"Next": "HasSentinelBeenReached"
},
"HasSentinelBeenReached": {
"Comment": "Check if the currentWorkItem is the SENTINEL. If so, we're done. Otherwise, do something.",
"Type": "Choice",
"Choices": [
{
"Variable": "$.currentWorkItem",
"StringEquals": "SENTINEL",
"Next": "Done"
}
],
"Default": "DoWork"
},
"DoWork": {
"Comment": "Do real work using the currentWorkItem. Change this to be an activity/task.",
"Type": "Pass",
"Next": "RemoveFirstWorkItem"
},
"RemoveFirstWorkItem": {
"Comment": "Use the slice operator to remove the first item from the list.",
"Type": "Pass",
"InputPath": "$.workList[1:]",
"ResultPath": "$.workList",
"Next": "GetNextWorkItem"
},
"Done": {
"Type": "Succeed"
}
}
}