[AWS] Step Functionsで遊んでみる（SAM + Lambda） Part.3（分岐編）

# Part.2のおさらい
[Part2](https://qiita.com/herohit-tool/items/fbd0b91dacce218819ae)では、主に以下のことを行いました。

- Lambdaに固定引数を渡してみる
- Lambdaに外部からの引数を渡してみる
- Lambdaにコンテキストオブジェクトを渡してみる
- Lambdaの戻り値を、別のTaskの引数に渡してみる

今回は、その続きからはじめてみます。
ここから始める方は、
https://github.com/hito-psv/sam-demo-005
のコードを`git clone`してもらっても大丈夫ですし、[Part2](https://qiita.com/herohit-tool/items/fbd0b91dacce218819ae)からやってみてもらっても構いません。

# 今回のターゲット

- Lambdaでランダムな結果を返す
- 結果に応じて、次に実行するタスクを変える
- リトライ時は、しばらく時間が経ってから再実行する

をターゲットにしてみたいと思います。
Lambda部分については、[Part2](https://qiita.com/herohit-tool/items/fbd0b91dacce218819ae)で作成した「HelloWorld」の関数を少しだけいじって、色々試してみたいと思います。

# 準備
## Lambda関数を修正する
現在のコードに、乱数でいくつかのパターンの文字列を返すようにしてみたいと思います。

```python:hello_world/app.py
import logging
import random

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    logger.info(event)

    message_array = [
        "hello world",
        "retry"
    ]
    message = random.choice(message_array)
    logger.info(message)

    return {
        "statusCode": 200,
        "body": {
            "message": message,
        }
    }
```

これで、結果のメッセージには

- hello_world
- retry

のいずれかの文字列が返されます。

## ビルド・デプロイする
この状態で、`sam build`、`sam deploy --guided`を実行し、Lambda関数をデプロイしましょう。

# Lambda関数の戻り値によって振る舞いを変える
## ステートマシン定義の修正
まずは、ステートマシンの定義を修正します。
修正内容は後ほど説明しますので、まずは、このように修正してみます。

```json:step_functions/state_machine.json
{
  "StartAt": "hello world 1",
  "States": {
    "hello world 1": {
      "Type": "Task",
      "Resource": "${HelloWorldFunction}",
      "Parameters": {
        "p1.$": $.p1,
        "p2.$": "$.p2",
        "p3": {
          "p3-1": 20,
          "p3-2": "xyz"
        },
        "all.$": "$"
      },
      "ResultPath": "$.hello_world_result",
      "OutputPath": "$",
      "Next": "check state"
    },
    "retry lambda": {
      "Type": "Task",
      "Resource": "${HelloWorldFunction}",
      "InputPath": "$",
      "ResultPath": "$.hello_world_result",
      "OutputPath": "$",
      "Next": "check state"
    },
    "check state": {
      "Type" : "Choice",
      "Choices": [
        {
          "Variable": "$.hello_world_result.body.message",
          "StringEquals": "hello world",
          "Next": "hello world 2"
        },
        {
          "Variable": "$.hello_world_result.body.message",
          "StringEquals": "retry",
          "Next": "wait state"
        }
      ],
      "Default": "fail state"
    },
    "wait state": {
      "Type": "Wait",
      "Seconds": 5,
      "Next": "retry lambda"
    },
    "hello world 2": {
      "Type": "Task",
      "Resource": "${HelloWorldFunction}",
      "InputPath": "$",
      "End": true
    },
    "fail state": {
      "Type": "Fail",
      "Cause": "No Matches!"
    }
  }
}
```

1. 「hello world 1」は次のステートに「check state」のステートを実行する
2. 「check state」は「$.hello_world_result.body.message」の値が「hello world」なら、次のステートに「hello world 2」を実行する
3. 「check state」は「$.hello_world_result.body.message」の値が「retry」なら、次のステートに「wait state」を実行する
4. 「check state」は「$.hello_world_result.body.message」の値が「hello world」「retry」以外なら、次のステートに「fail state」を実行する（今回は起こり得ない）
5. 「wait state」は、5秒経ったら「retry lambda」のステートを実行する
6. 「retry lambda」は、lambda関数をコールした後、「check state」のステートを実行する
7. 「hello world 2」はlambda関数をコールした後、ステート実行を終了する
8. 「fail state」は、異常終了としてステート実行を終了する

という記述になっています。
「Choices」の中で

- `Variable`：チェック対象
- `StringEquals`：文字列がこの値と同じなら
- `Next`：次に実行するステート

を指定します。
なお、`StringEquals`の箇所に指定できる演算は、[AWS Step Functions開発者ガイド](https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/amazon-states-language-choice-state.html)を参照ください。

## ビルド・デプロイする
この状態で、`sam build`、`sam deploy --guided`を実行し、デプロイしましょう。

## ステートマシンの確認
まずは、ステートマシンの定義更新によって、可視化されたステートマシンがどのようになっているかをみてみましょう。

![sf1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427998/0a2ebd20-e748-c84d-694b-48b5d3857f3e.png)

変更したように、「check state」から分岐し、「retry lambda」の後「check state」に戻っていることが確認できたと思います。

# ステートマシンを実行してみる
マネジメントコンソールから、ステートマシンを実行してみましょう。
Part.2からの修正のため、入力値のp1、p2を使用するため、入力蘭を以下のように指定してください。

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3432373939382f64303138383561632d323261362d623039372d646435622d3135383438333539616634372e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427998/9aa27f7d-00f7-60d0-5088-bbcf49e0ffea.png)

- p1に、9999数値を指定
- p2に「p2 strings.」という文字列を指定

## 結果をみてみる
まずは、ビジュアルフローを確認してみましょう。
見事に、全パスが通ってくれてます（ランダムなので、「hello world 1」→「check state」→「hello world 2」という流れもありえます）。

![sf2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427998/d02cc81c-5d30-3c09-7feb-ea917314c7a1.png)

では、`Choice`の処理が正しく動作しているのか、確認してみましょう。
まずは、「hello world 1」のステートから呼び出されたLambda関数の戻りがこちらです。

![sf3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427998/a577cc16-cd6b-bc49-46b0-87c08733d969.png)

bodyのmessage部が「retry」であることが確認できます。
その結果、次に「wait state」が実行され、5秒後（#10と#11の時間をチェック）「retry lambdaのステートが実行されています。

![sf4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427998/f41b7f9f-b6d7-71e7-ec46-3094d08e29bb.png)

実は、この後、更に2回連続して「retry」が返ってきますが、3回目に「hello world」が返されます。

![sf5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427998/80606d77-e7fe-3b13-6e3c-c500277f5b90.png)

その後は「hello world 2」のステートが実行されていることがわかります。

![sf6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427998/cd28fc2b-f960-78da-fc04-adc95599ad9b.png)

そして、そのままステートが終了します。

# 新しく追加された「Choice」ステート
2020/8/13に、アップデートされた内容があります。

## 値のテスト（型チェック）

- IsNull
- IsString
- IsNumeric
- IsBoolean
- IsTimestamp

によって、`Variable`の型を`Choice`で確認することができます。

```json:例
   "Variable": "$.foo",
   "IsNull|IsString|IsNumeric|IsBoolean|IsTimestamp": true|false
```

## 存在チェック

- IsPresent

によって、`Variable`そのものの存在チェックを行うことができます。

```json:例
  "Variable": "$.foo",
  "IsPresent": true|false
```

## ワイルドカードチェック

- StringMatches

によって、ワイルドカードを使用した判定チェックが行えます。

```json:例
  "Variable": "$.foo",
  "StringMatches": "log-*.txt"
```

## 別の入力フィールドとの比較

- StringEqualsPath

によって、入力フィールドを別の入力フィールドと比較できます。状態の変更などを確認するときに使用できます。

```jeson:例
  "Variable": "$.foo",
  "StringEqualsPath": "$.bar"
```

## 詳細
こちらから確認できます。

[AWS Step Functions adds updates to ‘choice’ state, global access to context object, dynamic timeouts, result selection, and intrinsic functions to Amazon States Language
](https://aws.amazon.com/jp/blogs/aws/aws-step-functions-adds-updates-to-choice-state-global-access-to-context-object-dynamic-timeouts-result-selection-and-intrinsic-functions-to-amazon-states-languages/)

# まとめ
今回は`Choice`による分岐を行なってみましたが、ここまで来ると、ようやくワークフローっぽくなってきた感じがしますね。
ここまでのPart.1〜Part.3の内容だけでも、ある程度のステートマシンが定義できるようになっているかと思います。
次回は、Lambda以外のサービスをタスク定義で呼び出してみたいと思います。

# サンプルコードリポジトリ
https://github.com/hito-psv/sam-demo-006