Slide 1

Slide 1 text

Goで実装した UPSIDERの決済金額リミット機能 株式会社 UPSIDER Miki Masumoto 1 Gopherは「Go」のマスコットキャラクター、原作者は Renee French さんです。以降のページも同様。

Slide 2

Slide 2 text

自己紹介 ● Masumoto Miki ● 2022/1からUPSIDERにJOIN ○ Sler→フリーランス→UPSIDER ● Gopher歴は1年ほど ○ JavaとJavaScript/TypeScriptをよく書いてました ● 常にワーケーション中 2

Slide 3

Slide 3 text

UPSIDERについて ● 成長企業向けの法人カード、支払いプラットフォームを提供 ● ビジネスの「お金」を呼吸感覚まで自在に 3

Slide 4

Slide 4 text

Today’s goal 決済というクリティカルかつリアルタイム性が求め られるシステムで Goをどう活用しているのかを知ってもらうこと 4

Slide 5

Slide 5 text

目次 ● 決済システムの概要 ● 決済金額リミットを扱う機能の紹介 ● 実装のポイントとGoのコードサンプル ● Goで書いてよかったところ 5

Slide 6

Slide 6 text

    カード決済の流れ 6 カード決済 システム 加盟店 オーソリ(承認要求) クリアリング(売上確定) ◯◯円で決済します OK/NG ◯◯円請求します カード利用者

Slide 7

Slide 7 text

決済金額のリミット機能とは? 決済が飛んできた時にチェックされる金額上限 1.企業ごとの決済金額リミット 2.ユーザーごと1取引の決済金額リミット 3.ユーザーごとの月間決済金額リミット 毎月1日0時にリセット 4.ユーザーごとの日次決済金額リミット 毎日0時にリセット ユーザごとのリミットはユーザが任意で設定可能 7

Slide 8

Slide 8 text

決済金額リミット関連の処理たち ● 決済金額のリミットを設定/解除する ○ WEB画面からの操作によって呼ばれる ● DBからのトータル決済額の読み込み・書き込み ○ オーソリ・クリアリングなどが飛んできた時に呼ばれる ● トータル金額のリセット ○ システム内部のバッチで呼ばれる 8

Slide 9

Slide 9 text

決済金額リミット関連のデータを扱うstruct 9

Slide 10

Slide 10 text

実装のポイント① 柔軟な金額リミット機能 10

Slide 11

Slide 11 text

実装のポイント① 柔軟な金額リミット機能 11 今後、もっと柔軟なリミット機能が欲しくなるかも ● 週ごと/期ごとにリミットを持たせたい ... etc ● 毎月20日/15日など間隔は同じで特定日や時間にリセットしたい

Slide 12

Slide 12 text

実装のポイント① 柔軟な金額リミット機能 12 💡新しいリミットのタイプを実装したい 👉次回のリセットのタイミングを計算するロジックだけ作れば実装できる

Slide 13

Slide 13 text

実装のポイント① 柔軟な金額リミット機能 13 👉次回リセット日時(NextResetAt)を変えることで実現できる 💡既存のリミット間隔で特定の日付・時間にリセットしたい ● 20日にリセットされる月間リミット 7/1 8/1 9/1 リミットの設定をする NextResetAt = 7/20 リセット処理が走る NextResetAt = 8/20 LastResetAt = 7/20 7/17 7/20 8/20

Slide 14

Slide 14 text

実装のポイント② 安全なトータル金額のリセット 14

Slide 15

Slide 15 text

実装のポイント② 安全なトータル金額のリセット 15 トータル金額リセット時の懸念 ● リセット時刻にリセットする処理が遅延/失敗したら? ● オーソリなどのリアルタイムで飛んでくる決済が同時に飛んできたら?

Slide 16

Slide 16 text

実装のポイント② 安全なトータル金額のリセット 16 💥 ケース1:リセット処理がまだなのにオーソリが飛んできてしまった 単純な、トータル金額を0円にするリセット処理ではなぜダメなのか 00:00:00 00:00:01 23:59:59 トータル金額 0時にリセットされる1日あたりの決済金額リミットの例 10000円 13000円 0円 オーソリ 3000円 リセット処理の実行 本当の トータル金額 10000円 3000円 3000円 合わない

Slide 17

Slide 17 text

実装のポイント② 安全なトータル金額のリセット 17 💥 ケース2:日付が変わる直前にオーソリが飛んできて処理中にリセットが走った 単純な、トータル金額を0円にするリセット処理ではなぜダメなのか 00:00:00 00:00:01 23:59:59 トータル金額 0時にリセットされる1日あたりの決済金額リミットの例 10000円 0円 4000円 オーソリ 4000円(処理に時間がかかった) リセット処理の実行 本当の トータル金額 10000円 0円 0円 合わない

Slide 18

Slide 18 text

実装のポイント② 安全なトータル金額のリセット 18 リセット前後のトータル金額も保持し、取得時刻から使われるべきトータル金額を判断する

Slide 19

Slide 19 text

実装のポイント② 安全なトータル金額のリセット 19 トータル金額リセット処理は0にするのではなく、次の断面にスライドさせる

Slide 20

Slide 20 text

Goのよかったところ① 20 ● 言語仕様がシンプルで既存コードのキャッチアップしやすい 処理の流れと分岐が追いやすい

Slide 21

Slide 21 text

Goのよかったところ② 21 ● 抽象化の機能が限られているので、個別ケースの扱いに困ったり過 度な抽象化による難読コードを書くリスクを避けられる 抽象化していないことで コードを追いやすい 似た振る舞いのstruct

Slide 22

Slide 22 text

Goのよかったところ③ 22 ● エラーハンドルが必ず入るのでバグに気付きやすい いろんなところにif err If errを書きながら不具合に気づく Try catchのネストもない

Slide 23

Slide 23 text

Goのよかったところ④ 23 ● ほぼ標準ライブラリで開発が可能(特にテストで嬉しい) テストカバレッジも自動で出してくれる 🎉 標準ライブラリで テーブル駆動テストが簡単に書ける

Slide 24

Slide 24 text

まとめ 24 📍 決済というクリティカルかつリアルタイム性が求められるシステムで Goをどう活用しているのか? 💪 考えることが多い要件や仕様でもGoのシンプルな言語仕様を生かし レビューしやすく見通しの良いコードを書くことによって 堅牢なシステム作りをしています

Slide 25

Slide 25 text

Thank you for listening. 25 We are hiring! @masumomo @m_miki0108

Slide 26

Slide 26 text

Goのつらかったところ 26 ● ポインタ関連(うっかり ○ た ● 金額を扱うためのDecimalの扱いが面倒 ○ DBは文字列で保持している ○ 変換処理のエラーハンドルやコスト ● コードが冗長になりがち

Slide 27

Slide 27 text

- 意味不明なところがないか - 間違ったこと言ってるところはないか - 用語の使い方や一貫性 - 自分はスライド作る中でゲシュタルト崩壊しており、正常な判断ができない - 余談:ledger.Calcの部分も色々作り込んだけど話してみたら時間オーバーで全部消した 😇 - なのでリミットに関連するusage serviceメイン - 余談2:ちょっとスライド寂しいところには Gopherを足そうと思う 気になるところ、フィードバックが欲しいところ 27

Slide 28

Slide 28 text

フィードバックメモするところ 28

Slide 29

Slide 29 text

UPSIDERの内部構成 29 Auth handler Clearing handler OrgAccount Reader/Writer 各メッセージの Handler Services 各種ドメインの 読み書きServices UserAccount Reader/Writer Ledger Reader/Writer Usage Reader/Writer Shared DB VisaNet ・・・ ・・・ GatewayやRouter ・・・ Message Router

Slide 30

Slide 30 text

実装のポイント① 柔軟な金額リミット機能 30 👉NextResetAtを次のリセットしたい日時に設定することで実現できる 💡既存のリミット間隔で特定の日付・時間にリセットしたい ● 通常の月間リミット(1日にリセットされる) 7/1 8/1 9/1 リミットの設定をする NextResetAt = 8/1 リセット処理が走る NextResetAt = 9/1 LastResetAt = 8/1 7/17