StepFunctionsでたくさんメールを送信してみる話


ZOZOテクノロジーズ #4 Advent Calendar 2020 18日目の記事です。
昨日は Vimの使い手 @aratana_tamutomo さんDefxとDeniteを合わせて検索を快適に という記事でした!

本日は、技術系初心者新卒2年目が一生懸命書きますので温かい目で見て頂けると嬉しいです。
「こうしたらもっといいのにー」とかあったらばんばん教えてやってください

そもそも StepFunctions って?

一言で言うと

「オートマトン」ってやつです。
Lambdaなどから起動すると、最初に定義した通りのループや条件分岐に従って
勝手に処理を進めてくれる優れものです。
AWS 開発者ガイドは図もあり説明がわかりやすいので一読を

「ワークフローが定まっている / 同じ処理を何度も繰り返す」
こんな処理に利用すると、実行後は途中処理をいちいち気にする必要がなくなるのでとっても便利です!

「お腹空いた」 → YES → 「お菓子を食べる」 →
「お腹空いた」 → YES → 「お菓子を食べる」 →
「お腹空いた」 → YES → ・・・・・・・・・ → 
「お腹空いた」 → NO  → 「ごちそうさま!」

しかも定義はserverless.yml(今回YML利用)に書くだけなのでめっちゃ簡単。
これは使うしかないです。

もうちょっとだけ詳しく

▶︎ データのやりとり

Lambdaの起動時だけでなく、ステートマシン相互にデータのやり取りが可能

  • 最初の起動時は通常通り、Lambdaのeventなどからデータを取得できる
  • 定義しておくことで、あるステートマシン(1フロー)の実行結果の戻り値データを取得できる
  • 定義しておくことで、上記戻り値データを別ステートマシンに引き渡すことができる
▶︎ Typeの設定
  • 対象ステートマシンがどういった処理をするのか定義しておく必要がある
  • 全部で6つの状態がある
    • Task:Lambdaを呼び出す など
    • Choice:次にどのステートを呼び出すかの条件分岐(選択肢記述) など
    • Pass:あるステートマシンの出力を別ステートマシンの入力に渡す など
    • Wait:指定された時間待機する など
    • Parallel:並行起動する など
    • Map:リストで渡すと繰り返してくれる?(いまいちわかってない・・・)など
    • Fail/Succeed:失敗/成功で処理終了
  • 上記は個人的理解のため、正確にはAWS 開発者ガイド > 状態を確認してくださいね!

実現したかったこと

今回はタイトルに書いてる通り「同じメールを一度の起動でたくさん送信する」ために頑張りました。

要件定義はざっくりこんな感じ。

  • 送信対象のリストアップは済んでいる(送信対象は100件以上)
  • 画面から【送信】を1度だけぽちっとしたら、対象全部へ勝手に送信してくれる
  • 各対象が送信されているかされていないかをリアルタイムで更新したい

今日は画面の話はしないので、
「1度のポチッで100件以上送信でき、送信可否の途中経過が知れる機能開発」
ってことで実装頑張りました

やったこと

フロー図はこんな感じ。
※ちなみにこれStepFunctionsで勝手に作ってくれるフロー図です。便利!!!

  1. [Start] APIからLambdaが起動し、Lambda内部でStepFunctionsを起動
  2. [ReadyForNextSending:Task] 送信対象リストから次の送信ターゲットを決める
  3. [isCompleteAll:Choice] ターゲットがあれば4を起動、なければ7を起動
  4. [SendToCustomer:Task] ターゲットにメール送信、送信成功の場合ステータス更新
  5. [isSuccessToSendToCustomer:Choice] メール送信に成功していれば2を起動、失敗していれば6を起動
  6. [WaitForCustomer:Wait] 3秒待機後、4を再起動
  7. [CompleteSending:Task] 送信完了処理の実行
  8. [Finish:Succeed] 処理終了

※4でステータス更新をすることで、1件ずつ処理される度に送信可否がわかるようにできました。
※AWS SESの送信リクエスト過多などに引っかかって遅れない場合があったため、6で待機時間を挟みました。


YML定義は以下みたいな感じです。
ちょっと省略しましたが、たったこれだけであのループと分岐がかけちゃいますすごい。


stepFunctions:
  stateMachines:
    StepFunctionSendMail:
      name: test-state-machine-send-mail
      role:
        Fn::GetAtt: [StateMachineSendMailRole, Arn]
      definition:
        StartAt: ReadyForNextSending
        States:
          ReadyForNextSending:
            Type: Task
            Resource: {Lambda名}
            Next: "isCompleteAll"
          isCompleteAll:
            Type: Choice
            Choices:
              - Variable: "$.status"
                StringEquals: "this_target"
                Next: "SendToCustomer"
              - Variable: "$.status"
                StringEquals: "complete"
                Next: "CompeleteSending"
          SendToCustomer:
            Type: Task
            Resource: {Lambda名}
            Next: "isSuccessToSendToCustomer"
          isSuccessToSendToCustomer:
            Type: Choice
            Choices:
              - Variable: "$.needs_wait"
                BooleanEquals: true
                Next: "WaitForCustomer"
              - Variable: "$.needs_wait"
                BooleanEquals: false
                Next: "ReadyForNextSending"
          WaitForCustomer:
            Type: Wait
            Seconds: 3
            Next: "SendToCustomer"
          CompeleteSending:
            Type: Task
            Resource: {Lambda名}
            Next: "Finish"
          Finish:
            Type: Succeed

感想

1つのLambda内部でこれを処理しようとすると、
ループや分岐の処理が複雑になりすぎて辛くなったり、タイムアウトが起きちゃったり・・・
とてつもなくしんどいです。。。

それがStepFunctionsの力でこんなに簡単に!!!

一つだけ気をつけることは、フロー図は定義後にしか出てこないので。
最初の設計段階では、自分の手できちんと書いてイメージをしっかり持ってからやることです。
(複雑になってくると途中 ??? になります)

ではでは、皆さんもハッピー Step Function LIFE を〜
ここまで読んでいただいてありがとうございました!