Startup Clix: First Steps with AWS Step Functions
Tags: pushercontest, javascript, aws
Published: 2018-05-09 17:18:00

---
title: Startup Clix: First Steps with AWS Step Functions
published: true
description: 
tags: pushercontest, javascript, aws
series: [pusher contest]
---

Yesterday I finally got presence channel working with [Pusher](https://pusher.com/), which I need to check who's in what game and to distribute game updates to them.

Today was about getting myself educated in [the art of state-machines](https://en.wikipedia.org/wiki/Finite-state_machine) with [AWS Step Functions](https://aws.amazon.com/de/step-functions/).

## Step Functions

AWS Step Functions is a serverless (autoscale, pay per use, etc.) service, that lets you define and execute state-machines in the AWS cloud.

It can control Lambda functions and most importantly it can be controlled __by__ Lambda functions.

I created a test implementation to see how this could work.

First I created a state-machine definition and integrated it into my CloudFormation [template.yaml](https://github.com/kay-is/startup-clix/blob/master/back-end/template.yaml). I also created a IAM role that now will be used by Lambda and Step Functions to do their thing.

The definition of a state-machine is generally straight forward, but kinda ugly to integrate into CloudFormation, because it needs to be done as string and the definition is written in JSON.

Anyway, this is the definition inside the template:

```javascript
  GameStateMachine:
    Type: "AWS::StepFunctions::StateMachine"
    Properties:
      RoleArn: !GetAtt [ ExecutionRole, Arn ]
      DefinitionString:
        Fn::Sub:
          - |-
            {
              "StartAt": "WaitingForPlayers",
              "States": {
                "WaitingForPlayers": {
                  "Type": "Task",
                  "Resource": "${joinGameActivityArn}",
                  "TimeoutSeconds": 20,
                  "End": true
                }
              }
            }
          -
            joinGameActivityArn: !Ref JoinGameActivity

  JoinGameActivity:
    Type: "AWS::StepFunctions::Activity"
    Properties:
      Name: JoinGame
```

As one can see, some string substitution is taking place to get the ARN the `JoinGameActivity` into the definition. The activity is also defined in the template.

It goes like this:

1) `startGame` Lambda function is called via API-Gateway
```javascript
module.exports = async (event, context) => {
  const executionParams = {
    // The GameStateMachine ARN is available via env-var
    // it's passed here by CloudFormation
    stateMachineArn: process.env.GAME_STATE_MACHINE_ARN,

    // some input data that is used as start input for the state-machine
    input: JSON.stringify({ gameId: GAME_ID })
  };

  // starting a new execution via the AWS-SDK
  await stepFunctions
    .startExecution(executionParams)
    .promise();
};
```

2) `GameStateMachine` execution goes into the `WaitingForPlayers` state until some kind of worker sends a success via the AWS-SDK or if the timeout hits.

3) `joinGame` Lambda function is called via API-Gateway
```javascript
module.exports = async (event, context) => {
  let task;
  {
    const getTaskParams = {
      // The JoinGame activity ARN is available via env-var
      // it's passed here by CloudFormation
      activityArn: process.env.JOIN_GAME_ACTIVITY_ARN
    };

    // If a task for this activity is available it will be polled here
    task = await stepFunctions.getActivityTask(getTaskParams).promise();
  }

  // some game logic happening, haha
  const input = JSON.parse(task.input);
  {
    // this token is need to send a success or fail state later
    const { taskToken } = task;

    const taskSuccessParams = {
      taskToken,
      output: JSON.stringify(input)
    };
    
    // the success is send to the activity
    // so the GameStateMachine can transition to the next state
    await stepFunctions.sendTaskSuccess(taskSuccessParams).promise();
  }
};
```

4) The `GameStateMachine` transitions to its end state and succeeds execution.

## Next

The problem is, there are multiple executions, one for every game, but is still only one `JoinGame` activity that can be polled. Now, when players for 2 games poll this activity they should only get the activity tasks for their game, which is not possible at the moment.

Well, maybe tomorrow :)