Step Functionsを使ってみる



社会人一年目のtkg-70です。
誕生日記念に初投稿します。（嘘です、たまたまです。）
今回、業務でAWSのStep Functionsを使う機会がありまして、せっかくなので自分の勉強のためにも記事を書いてみようと思いました。
Step Functonsを使うにあたり、押さえたほうが良いところと躓いたところをまとめていこうと思います。（また、自分が使うときのためにも）

##Step Functionsとは
うまく説明できる気がしないので、AWSの公式を貼っておきます。
https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/welcome.html
ざっくり、作成した複数のLambdaを、一連の流れとして動かせるものって思ってます。

##Step Functionsの作成
Step FunctionsはAmazon ステートメント言語を用い、JSONの形式でワークフローを書いていきます。
コードスニペットの生成のプルダウンにテンプレが一通り乗っており、モーダルで表示してくれるので、それをコピペして改変すれば書いていくことができます。
ワークフローを書き上げると、自動でビジュアルコンソールにフローチャートを作成してくれます。（自分で書いたワークフローが視覚的にわかるので、ありがたかったです。）

私が書いたコードはこんな感じ↓（LambdaのARNはテンプレのままなのでエラーでてます。）
<img width="858" alt="2020-03-12_00h12_16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599966/d894fdcb-c68d-be5c-adc3-d0f18a930c1b.png">



```
{
  "Comment": "A Hello World example of the Amazon States Language using Pass states",
  "StartAt": "LambdaA",
  "States": {
    "LambdaA": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
      "Parameters": {
        "S3Bucket": "xxxxxx",
        "filePath": null
      },
      "OutputPath": "$",
      "Next": "eventParamChoice"
    },
    "eventParamChoice": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.filePath",
          "StringGreaterThan": "0",
          "Next": "startShellProcess"
        },
        {
          "Not": {
            "Variable": "$.filePath",
            "StringGreaterThan": "0"
          },
          "Next": "FailedState"
        }
      ],
      "Default": "FailedState"
    },
    "startShellProcess": {
      "Type": "Parallel",
      "InputPath": "$",
      "OutputPath": "$",
      "Next": "SucessState",
      "Branches": [
        {
          "StartAt": "LambdaB",
          "States": {
            "LambdaB": {
              "Type": "Task",
              "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
              "InputPath": "$",
              "OutputPath": "$",
              "End": true
            }
          }
        },
        {
          "StartAt": "LambdaC",
          "States": {
            "LambdaC": {
              "Type": "Task",
              "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
              "InputPath": "$",
              "OutputPath": "$",
              "End": true
            }
          }
        },
        {
          "StartAt": "LambdaD",
          "States": {
            "LambdaD": {
              "Type": "Task",
              "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
              "InputPath": "$",
              "OutputPath": "$",
              "End": true
            }
          }
        }
      ]
    },
    "FailedState": {
      "Type": "Fail"
    },
    "SucessState": {
      "Type": "Pass",
      "End": true
    }
  }
}
```
##Step Functionsの書き方
Step Functionsの一つの処理の書き方の例は下記の通りです。

    "処理の名前": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
      "Parameters": {
        "S3Dir": "xxxxxxxxx",
        "filePath": null
      },
      "OutputPath": "$",
      "Next": "eventParamChoice",
      "Catch": [
        {
          "ErrorEquals": [
            "States.ALL"
          ],
          "Next": "FailedState"
        }
      ]
    },

このソースに書いてあることの意味について説明していこうと思います。

#### Type
Typeの状態によってその処理でできることが変わります。上記のTaskの状態は、Lambdaを実行することができます。状態は他にもいくつかあります。
使用できるTypeの状態：

- Choice（受け取った値から処理を分岐させることができる）
- Wait（待機できる）
- Succeed（実行を正常に停止する）
- Fail（実行を失敗として停止する）
- Parallel（複数のLambdaを並列実行できる、Parallelで書いた処理が一括りの処理として実行される）
- Map（同じ処理に複数の値を渡し、動的に実行できる）

#### Resource
Task状態で使えるフィールドです。LambdaのARNを指定できます。
#### Parameters
Task状態で使えるフィールドです。Lambdaに渡す値をJSON形式で記述できます。
Lambdaに渡す値を記述できるのはParametersだけではありません。
実行時に、渡す値を記述し、InputPathのフィールドを使うことでLambdaに値を渡すこともできます。
#### OutputPath
Task状態で使えるフィールドです。次の処理に渡す値を指定できます。
例えば、上記の記述でLambdaに渡した値がそのまま帰ってきた場合、$.S3Bucketと指定すれば、次の処理に渡されるのは{"S3Bucket": "xxxxxx"}のみになります。
#### Next
次に実行する処理の名前を指定します。
#### Catch
Lambdaで起きたエラーをcatchするときに使うフィールドです。ここではStates.ALL（States.Runtimeを除くすべてのエラー）をcatchした時、FailedStateの処理に移ります。（書かなくても大丈夫です）

基本のTask状態の説明をしましたが、Typeと受け渡しをする値に気をつけておけば、他のTypeもフィールドを調べながら書いていくことができると思います。
Type状態の公式ガイドのリンク貼っときます。（これみれば、フィールドはだいたいわかる）
https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/concepts-states.html

## Step Functonsで躓いたこと
Step Functionsを使うにあたって、Parallel状態のエラー処理の記述に躓きました。どのように書くと、どのように実行されるかがわからなかったため、複数書いて実行してみました。
#### Parllel でのエラー処理を実行してみる
Step Functionsを用いたエラー処理の方法はいくつかあったため、全て試してみることにしました。

- Lambdaでtry catchしない
最初に特に何もエラー処理を書かずにStep Functionsを実行してみました。
<img width="744" alt="2020-03-15_17h20_11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599966/d853639a-792c-e238-452f-f7be50ce9d8b.png">

  - エラーをなげるLambda

```js:LambdaB
exports.handler = async (event) => {
    throw new Error
    return event;
};
```
ワークフローでLambdaBをクリックすることで、例外の欄にエラーが出ていることがわかります。

- try catchした後、errorをthrowする
次に、エラーをcatchしてthrowしています。
<img width="748" alt="2020-03-15_17h17_38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599966/b1b9a941-fd37-5df4-a045-987990495cc2.png">


  - エラーをなげるLambda

```js:LambdaB
exports.handler = async (event) => {
    try{
        throw new Error
        return event;
    }catch(err){
        throw err
    }
};

```
この方法もワークフローでLambdaBをクリックすることで、例外の欄にエラーが出ていることがわかります。この方法ならば、エラーにメッセージを加えたりできるのでしょうか・・・（やり方がわかりませんでした。）

- try catchしてerrorをthrowしない
<img width="748" alt="2020-03-15_18h05_06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599966/442729c6-42f4-875e-259a-5996cf0d1569.png">


  - エラーをなげるLambda

```js:LambdaB
exports.handler = async (event) => {
    try{
        throw new Error
        return event;
    }catch(err){
    }
};
```
Lambdaはエラーをだしていますが、Step Functionsのワークフローではエラーが表示されていません。（個別のCloud Watchログではエラーが表示されています。）

- parallelでcatch（prallelにcatch追加し、Lambdaでエラーハンドリングは行わない）
<img width="746" alt="2020-03-12_00h33_10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599966/65ea655f-a5a8-ec1f-1a65-c777509b240f.png">
  - parallelに以下のコードを追加
 
```
      "Catch": [
        {
          "ErrorEquals": [
            "States.ALL"
          ],
          "Next": "FailedState"
        }
      ]

```
Parallelの出力とFailedStateの入力を見ることでerrorがわかります。この書き方では、Parallel内でエラーが発生した際のエラー処理をまとめて記述できます。


・
・
・

いろいろエラーの書き方を試してみましたが、どれがいいとかではなく、状況に応じて選択すべきだなと思いました。今回いろいろ実行してみて、注意した方がよいと思った点があるので、記載しておきます。

#### Parallelの注意ポイント

#####１．上記の「Parallelでcatch」は正常終了しているように見えますが・・・
<img width="748" alt="2020-03-15_17h29_10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599966/3796bd74-942c-edab-6b48-4f1bf9dd4fb8.png">

これを見ると、LambdaB、LambdaC、LambdaDが失敗しているように見えますが、LambdaC、LambdaDは正常終了しています。
→Parallelは記載した処理が一つのタスクとして失敗・成功が判断されます。Parallel内のタスクがエラーを出した場合、その時点で処理が終了しますが、呼び出されたLambdaは動いている点に注意が必要です。

#####２．parallelの出力は・・・
<img width="741" alt="2020-03-12_00h59_52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599966/d9ac8d78-e36b-e0a7-8bd7-bb24e196c1ec.png">

Parallelに出力するLambdaが複数あるとParallelの出力がこうなってしまいました。
こうなると、parameterを操作するのが難しかったです。
Parllelの出力は操作しないか、どれか一つが出力するようにすべきかなと思います。
### あとがき
初めてやりましたが、AWSはドキュメントがわかりやすくてよかったです。
Step　Functionsは見た目でもわかりやすくて助かりました。
この記事が誰かの参考になればいいなと思います。
間違い等あればコメントください。