Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JP_Stripes: 一貫性に寄与する設計

8843d0fb02a3688513a890bd23a5fa3c?s=47 acomagu
February 28, 2020

JP_Stripes: 一貫性に寄与する設計

200228 JP_Stripes in 会津 Vol.2
The Designium Inc. 伊藤勇希

前回: https://speakerdeck.com/acomagu/stripedehazimetefalsejue-ji

8843d0fb02a3688513a890bd23a5fa3c?s=128

acomagu

February 28, 2020
Tweet

Transcript

  1. 一貫性に寄与する設計 200228 JP_Stripes in 会津 Vol.2 The Designium Inc. 伊藤勇希

  2. 自己紹介 - 伊藤勇希 @acomagu (あこまぐ) - 会津大学 学部4年 - 最近の趣味:

    音ゲー / VTuber - 最近好きな技術: Go / TypeScript / Linux / Semantic Web / Solid
  3. Agenda - 前回のおさらいと起きた問題 - 要件の設定 - 解決策の検討 - Event Sourcing

    とは? - Stripe で Event Sourcing - まとめ
  4. 前回のおさらいと起きた問題

  5. 前回のおさらい - バスチケットの購入/利用ができる PWA - TypeScript + AWS Lambda +

    Auth0 + Stripe - Stripe の購入履歴と自前のデータベースとの一貫性が壊れ るリスクを避けるため、データベースを用意しない設計を選 択
  6. None
  7. その後どうなったか...? - 1/24 無事リリース - 大きな問題なく稼働中 https://about.samurai.ms

  8. しかし課題が浮かび上がる - Stripe からのレスポンスが想像 以上に遅い - API コール 1 回だいたい

    1 〜 3 秒くらいの幅がある - 「チケットの表示に時間がかかる と降りる直前に焦る」という意見 ↓ チケット取得 API のレイテンシ
  9. しかし課題が浮かび上がる さらに - 現状フロントとの実装の兼ね合いもあり「全ての購入履歴を取得」している - Auth0 ユーザーとの紐付けは email フィールド(Stripe Checkout

    クライアント 専用組み込みを利用しているため、metadata が利用できない) - そうすると購入履歴が膨れるに従って、線形的に API コールの時間が長くな る - 1度の API コールで 100 件までしか履歴を取得できないため
  10. ユーザーのリクエストごとに Stripe の API を叩くのは限界になってきている じゃあどうするか? -> 自分で何らかのデータを持つしかない しかし課題が浮かび上がる

  11. 要件の設定

  12. 要件の設定 - とりあえず「ユーザーの購入済みチケットをリストする」ユースケースに絞って考える - レスポンスまで: 目標1秒以内 - 購入したらすぐに購入済みチケットに反映されるようにする(現実的な時間内) - Stripe

    の購入履歴と独自データベースの整合性ができるだけ破綻しにくいようにす る
  13. 解決策の検討

  14. 案1: 決済時にフロントからデータを送ってもらう 1. 決済が成功したときに、Stripe から決済 ID を送ってもらう 2. 決済 ID

    を Stripe に問い合わせ、独自データベースを更新する 問題点: 例えば決済は成功したが、決済 ID の送信に失敗したらどうする? → 不整合
  15. 案2: Stripe API をキャッシュする - 何らかのデータ -> キャッシュ?(キャッシュを持てばいい?) - キャッシュの更新に今までと同じ時間がかかってしまうため、「即時反映」を満たせ

    ない
  16. 案2: Stripe API をキャッシュする - 何らかのデータ -> キャッシュ?(キャッシュを持てばいい?) - キャッシュの更新に今までと同じ時間がかかってしまうため、「即時反映」を満たせ

    ない - いつ更新するの? - Stripe Webhook?
  17. 寄り道: Stripe Webhook とは? - Stripe のアカウントに「何らかの出来事」が起こったときにそれを Webhook で知ら せてくれる仕組み

    - 設定次第で Stripe 上で履歴が残るような出来事の全てをイベントとして受け取るこ とができる - 今回の場合、支払いが成功したときにその旨を HTTP で受け取れる
  18. 案3: Stripe Webhook で受け取ったイベントを基に データベースを更新する (例えば)ユーザーが Stripe で決済を完了したとき、Stripe から決済情報を含むイベント をアプリケーションが受け取り、それに応じてユーザーが持つチケットを増やす

  19. 案3: Stripe Webhook で受け取ったイベントを基に データベースを更新する (例えば)ユーザーが Stripe で決済を完了したとき、Stripe から決済情報を含むイベント をアプリケーションが受け取り、それに応じてユーザーが持つチケットを増やす

    レスポンス速度: 購入済みチケットの確認には Stripe を叩く必要がないので速い 反映までの早さ: ??? 破綻しにくさ: ???
  20. None
  21. 案3: Stripe Webhook で受け取ったイベントを基に データベースを更新する (例えば)ユーザーが Stripe で決済を完了したとき、Stripe から決済情報を含むイベント をアプリケーションが受け取り、それに応じてユーザーが持つチケットを増やす

    レスポンス速度: 購入済みチケットの確認には Stripe を叩く必要がないので速い 反映までの早さ: ??? 破綻しにくさ: ユーザーデータ(独自データベース)の更新に失敗すると不整合が生まれ る...
  22. 案3: Stripe Webhook で受け取ったイベントを基に データベースを更新する (例えば)ユーザーが Stripe で決済を完了したとき、Stripe から決済情報を含むイベント をアプリケーションが受け取り、それに応じてユーザーが持つチケットを増やす

    レスポンス速度: 購入済みチケットの確認には Stripe を叩く必要がないので速い 反映までの早さ: ??? 破綻しにくさ: ユーザーデータ(独自データベース)の更新に失敗すると不整合が生まれ る... でもイベントから定期的に再計算することはできるのでは?
  23. None
  24. Replayable

  25. Event Sourcing とは?

  26. Event Sourcing とは? 「アプリケーションの状態を一連のイベントの列として表す設計手法」 例えばアクセスカウンターは DB の count というデータをアップデートするのではなく、 「〜時にアクセスがあった」というデータを追加しておく。

    パフォーマンスのために同時に別の count データをアップデートしてもいいが、その データはいつでも破棄することができる。
  27. Event Sourcing の重要な3特性 - Complete Rebuild: アプリケーションの状態をイベントだけで再現することができる - Temporal Query:

    アプリケーションのある時点の状態(例えば1ヶ月前など)を再現 することができる - Event Replay: もしあるイベントが間違っていた場合、イベントを修正してもう一度 計算することでアプリケーションを正しい状態に修正することができる
  28. 破綻しにくくなるのか? 破綻しにくくなるわけではな いが、修正は楽になる 追加のコードを書かなくて も、定期的にイベントをリプ レイすることで結果整合性を 維持することは可能になる

  29. その他の Event Sourcing の特徴 - Reversing Events: イベントを「逆適用」することで、いわゆる “Undo” が可能にな

    る - Paralell Models: ある地点でイベントを変化させた場合と変化させない場合の2パ ターンをリプレイすることで、2パターンの状態を観測できる(git のような)
  30. Stripe で Event Sourcing

  31. そもそも Stripe で Event Sourcing は可能なのか? Stripe Webhook で来るデータは Stripe

    Event と呼ばれる Stripe で取れる全てのデータは Stripe Event だけでも再現可能 間違いなくできる(とてもやりやすい)
  32. 実装上で考えなければならないこと - 「メールを送信する」などの外部に干渉するロジックがリプレイされると困るので確 認する(External Queries) - Gateway や Proxy と呼ばれるパターンで対応する

    - 「どのロジックがこのイベントをハンドルするか?」(Processing Selection Logic)をど こで考えるか。Event に持たせるのが OOP らしい - Stripe のイベントと Domain イベントの変換をどこでやるか - Stripe コンテキストと Stripe に依存しない主要ドメインを持ち、2つが通信する ようにした
  33. 「現在の状態を持つ DB」って本当に必要? 実は当初この DB は用意しない方針だった: 現在の状態をメモリ上にのみ持ち、アプリ ケーションが立ち上がるときに再計算することで実現できる →しかしアプリケーションのスケールとリクエストのロードバランシングが AWS Lambda

    依存になっている関係でそれが不可能ということがわかった(AWS だと全ての Lambda インスタンスにイベントをブロードキャストする方法がない) AWS「Lambda 関数はステートレスでなければなりません」
  34. 実装してみてどうだったか? - 実装し直しになってしまいしんどい... - ほぼ書き直しになりそう - 「一部だけ Event Sourcing を採用し、パフォーマンスが重要でないところは今まで

    どおり Stripe を叩く」という方向性
  35. まとめ

  36. まとめ - Event Sourcing とは「アプリケーションの状態を一連のイベントの列として表す設 計手法」 - 今回はその特性のうち Event Replay

    が必要だったので採用した - 「Stripe 上のデータと独自データベースの不整合」を防ぐのは難しいが、Event Sourcing を利用することで不整合の修正が安全に、追加のコード無しでできるよう になる - Stripe と Event Sourcing はとても相性が良い!
  37. Thanks!