AWS Step Functions 入門してみた

# 初めに

Step Functions に入門してみました。操作方法などを簡単に書いておきます。

# ステートマシンの作成・実行

ステートマシンのコードには ASL（Amazon States Laguage）という独自の言語が使われます。

簡単に作成してみます。以下のステートマシンは、一つのステートを実行するステートマシンです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/9451b96e-6f2d-fa46-d19f-e25017da978a.png)

```json
{
  "StartAt": "State",
  "States": {
    "State": {
      "Type": "Pass",
      "Result": "State Result",
      "End": true
    }
  }
}
```

これを実行してみます。実行前に以下の画面が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/89955929-5ff0-c96f-0fc8-c04469d0ec0c.png)

「実行の開始」を押すと以下の画面が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/e71dd1c6-524b-fabb-a9f1-ed761d48c345.png)

これでこのステートマシンの実行が完了しました。上図の左側の「State」をクリックした後、右側の「ステップ入力」をクリックすると、この「State」が入力に何を受け取ったかが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/5d005a63-eec8-7a5d-c945-1de465b4a678.png)

続けて出力も見てみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/a24dbf4e-6816-b6ad-c60b-93a64654d789.png)

このステートマシンは、実行が開始されると、

```json
{
  "Comment": "Insert your JSON here"
}
```

を受け取り、

```
"State Result"
```

を返すことがわかりました。

# 入力・出力の変更

入力の値を変更するには、ステートマシンを実行する前の画面において編集します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/89955929-5ff0-c96f-0fc8-c04469d0ec0c.png)

こちらを以下のように編集します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/eb59492b-b21b-5653-47a9-6f66d61851ea.png)

次に出力の値を変更します。出力の値を変更するには、以下のようにステートマシンの `Result` フィールドを変更します。

```json
{
  "StartAt": "State",
  "States": {
    "State": {
      "Type": "Pass",
      "Result": "Blue",
      "End": true
    }
  }
}
```

これでこのステートマシンは、

```json
{
  "Color": "Red"
}
```

を受け取り、

```
"Blue"
```

を返すステートマシンになりました。

# 入力に受け取った値を出力する

受け取った値を出力に反映させるには、`InputPath` フィールドを追加し、そこに入力のキーを指定します。

```json
{
  "StartAt": "State",
  "States": {
    "State": {
      "Type": "Pass",
      "InputPath": "$.Color",
      "End": true
    }
  }
}
```

入力は前回と同じですが、出力が入力の値を反映したものになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/cbcb9928-2e44-ebd1-e9c3-5309792bcf6c.png)

次に JSON 形式で受け取った値を返す方法ですが、これには `ResultPath` フィールドを追加します。

```json
{
  "StartAt": "State",
  "States": {
    "State": {
      "Type": "Pass",
      "InputPath": "$.Color",
      "ResultPath": "$.Color",
      "End": true
    }
  }
}
```

この出力結果は以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/ec795fbd-9ddb-aff2-35d2-33dec578b41d.png)

入力と出力が一致しています。実はこのように入力をそのまま出力に渡すようにするには、`InputPath` と `ResultPath` は必要ありません。つまり以下のコードで十分です。

```json
{
  "StartAt": "State",
  "States": {
    "State": {
      "Type": "Pass",
      "End": true
    }
  }
}
```

# 出力を制限する

以下のような入力を考えます。

```json
{
  "ID": "A10",
  "ProductName": "Jacket",
  "Color": "Red"
}
```

これを受けて、ID と赤色のジャケットの在庫の個数を返すステートを作成します。そのためには以下のように、`OutputPath` で出力するキーを指定します。


```json
{
  "StartAt": "State",
  "States": {
    "State": {
      "Type": "Pass",
      "Result": 3,
      "ResultPath": "$.Stock",
      "OutputPath": "$['ID', 'Stock']",
      "End": true
    }
  }
}
```

実行結果は以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/2fdf4d8f-9cd7-93eb-5d24-4452437db894.png)


`"ResultPath": "$.Stock"` により、出力に `Stock` というキーを追加しています。`OutputPath` フィールドで、出力するキーを制御できます。ここでは、入力の `ID` キー と、追加した `Stock` キーを出力し、それ以外のキーは出力しないように制御しています。

# Task ステート

ステートマシンから他の AWS リソースを実行するには、タスクステートを使用します。ステートにはいくつか種類があり、ステートの `Type` フィールドで指定します。例えば、

- `"Type": "Pass"`：パスステート
- `"Type": "Task"`：タスクステート

があります。

詳細はこちらです。

https://docs.aws.amazon.com/step-functions/latest/dg/concepts-states.html

まずは 注文を受けて、在庫数を返す Lambda 関数を作成します。

```python:lambda_function.py
import json

def lambda_handler(event, context):
    product_name = event['ProductName']
    color = event['Color']
    product_table = {
        'Jacket': {
            'Red': 3,
            'Blue': 5
        }
    }
    return product_table[product_name][color]
```

その後 Lambda 関数のリソースベースのポリシーに、ステートマシンのロールの ARN を指定し、`lambda:InvokeFunction` を許可します。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/ef417f05-8764-cfcb-b5ec-3c6e2dd8c3ac.png)

これで呼び出す Lambda 関数の作成は完了です。

続いて、ステートマシンを作成します。パスステートと違う点は、以下の 4 点です。

- `Type` フィールドを `Task` に変更
- `Resource` フィールドで呼び出す Lambda 関数の ARN を指定
- `Parameters` フィールドで Lambda 関数に渡すパラメータを追加
- `Result` フィールドを削除

```json
{
  "StartAt": "State",
  "States": {
    "State": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:GetStoreFunction",
      "Parameters": {
        "ProductName.$": "$.ProductName",
        "Color.$": "$.Color"
      },
      "ResultPath": "$.Stock",
      "OutputPath": "$['ID', 'Stock']",
      "End": true
    }
  }
}
```

この実行結果は以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/12a24bbb-6312-7ad2-8dda-7c094e654e63.png)

# Choice ステート

最後にステートの出力結果によって次の処理を分岐させる Choice ステートについて書きます。前回の Lambda 関数は、商品の在庫数を返します。その在庫数によって出力にコメントを含めるように新しいステートを作成してみます。

まずは在庫数が 5 以上であるかどうかを判定する Choice ステートを新たに追加します。

```json
    "State2": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.Stock",
          "NumericGreaterThanEquals": 5,
          "Next": "Sufficient"
        }
      ],
      "Default": "Shortage"
    }
```

`Variable` フィールドは、 Choice ステートが受け取る入力中のどのキーの値を評価するかについて指定します。ここでは `$.Stock` と指定していますので、商品の在庫数を評価することになります。

`"NumericGreaterThanEquals": 5` については、受け取った商品の在庫数が 5 個以上であれば "Next" フィールドに記載されているステートに進みます。そうでなければ次の条件に進みます。`Default` フィールドはどの条件にも合致しない場合に進むステートを定義します。プログラムではいわゆる else 文に相当します。

次に `Sufficient` ステートと `Shortage` ステートを記述します。それぞれ新たに出力するキーを追加しています。`End` フィールドはこのステートでステートマシンが終了する場合に追加します。

```json
    "Sufficient": {
      "Type": "Pass",
      "Result": "Sufficient stock",
      "ResultPath": "$.comment",
      "End": true
    }
```

```json
    "Shortage": {
      "Type": "Pass",
      "Result": "Shortage stock",
      "ResultPath": "$.comment",
      "End": true
    }
```

ステートマシン全体のコードは以下のようになります。最初の Lambda 関数を呼び出す State1 は、State2 に処理が続くため、`Next` フィールドを追加しています。

```json
{
  "StartAt": "State1",
  "States": {
    "State1": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:GetStoreFunction",
      "Parameters": {
        "ProductName.$": "$.ProductName",
        "Color.$": "$.Color"
      },
      "ResultPath": "$.Stock",
      "OutputPath": "$['ID', 'Stock']",
      "Next": "State2"
    },
    "State2": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.Stock",
          "NumericGreaterThanEquals": 5,
          "Next": "Sufficient"
        }
      ],
      "Default": "Shortage"
    },
    "Sufficient": {
      "Type": "Pass",
      "Result": "Sufficient stock",
      "ResultPath": "$.comment",
      "End": true
    },
    "Shortage": {
      "Type": "Pass",
      "Result": "Shortage stock",
      "ResultPath": "$.comment",
      "End": true
    }
  }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/f3e90212-60c3-b638-8174-8f0fdfeaf4f6.png)

それぞれの分岐を通るように入力を与えてみます。

以下は在庫数が 5 の商品の入力になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/db24ef48-5356-9f60-564c-aa9625a338c1.png)

上記の入力に対する実行結果は以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/e6ca8411-db47-f810-8f17-9c37fd9357f6.png)

以下は在庫数が 3 の商品の入力になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/af4caa4a-a21e-7c43-f7ae-aea21ce9634b.png)

上記の入力に対する実行結果は以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621414/7edce67b-bcb0-d26d-1044-f84e4ff9e9fa.png)

# 参考記事

https://docs.aws.amazon.com/step-functions/latest/dg/concepts-states.html

https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-pass-state.html

https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-task-state.html

https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-choice-state.html

https://docs.aws.amazon.com/step-functions/latest/dg/input-output-inputpath-params.html

https://d1.awsstatic.com/webinars/jp/pdf/services/20190522_AWS-Blackbelt_StepFunctions.pdf

https://www.youtube.com/watch?v=PGyasNJ1QTQ