この記事は Tech KAYAC Advent Calendar 2021の6日目の記事です。
ごきげんよう、Tonamelサーバサイドエンジニアの谷脇です。GUILTY GEAR -STRIVE-ではラムレザルを使っています。ランクマで出会ったらよろしくおねがいします。
さてこの記事では、最近リリースしたTonamelのスポンサー機能に関する設計の進め方と実装の振り返りをつらつら記述していきます。
Tonamelとは
Tonamelとはeスポーツ大会などをエントリーからトーナメント表構築および進行をサポートするWebサービスです。以下の機能を主に提供しています。
スポンサー機能とは
スポンサー機能は2021年9月に一部の大会主催者向けにリリースした機能です。以下の画像のように、大会に対してお金を使ってスポンサーが出来る機能です。
この記事の趣旨
- スポンサー機能を作るにあたって、実際にどういう設計にしたかを説明する
- これは実装時にやってよかったなあと思った事があったので共有する
誰に読んでほしいか
- マイクロサービスの分割方法にお悩みの方
- 実際の例を紹介します
- 2つのマイクロサービス間の連携方法について興味がある方
スポンサー機能をどのように作ったか
前提: モノリスからマイクロサービスへの移行期
Tonamelのサーバサイドのアーキテクチャは現在、Perlで開発しているモノリスのWebアプリケーションからGo製のマイクロサービス群へ機能を切り出している最中です。昨年トーナメント表構築と進行を切り出したのが9月ぐらいですから、もう1年経っています。ただ全面的に移行が終わるのは、もう少しかかるんじゃないかなと思っています。
現在の切り出し方の方針としては以下のとおりです。
- 以下のケースはマイクロサービスへの切り出しを行う
- まったくの新機能を実装する場合は新しいマイクロサービスを作る
- 既存機能であっても責務を拡張したり、挙動を大きく変える場合はマイクロサービスを作る
- 以下のケースは既存のモノリスを修正する
- 責務を拡張しない修正
- バグフィックス
- マイクロサービスを構築する際に必要な情報を受け渡しする機能
- 認証プロキシ
- 内部API
- ドメインイベント発行
そして、スポンサー機能を実装した当時は、昨年の記事で記述したトーナメント表管理サービスのみを切り出している状態でした。
いくつか切り出し方の候補
スポンサー機能は前述した方針からすると「全くの新機能」とも言えますし、「既存機能の責務を拡張する」とも言えます。
前者は分かりますが、後者の解釈はどうしてでしょうか?
スポンサー機能は見方によっては大会管理機能の一部と捉えることも出来ます。「スポンサー」は大会に紐づくものです。なのでエントリー管理や大会形式などの設定と同じ大会管理の一部として実装できます。
なので実装のオプションとしては今のところ、
- スポンサー機能単体をマイクロサービスとして分離して新規実装する
- 大会管理機能自体をマイクロサービスとして分離する
- モノリスの大会管理機能にスポンサー機能をくっつけて実装する
というのが考えられます。ただ、3つ目は「責務を拡張しない修正」には当てはまらないと判断したためはじめに脱落、あとは大会管理機能自体をマイクロサービスとして分離するか、スポンサー単体を分離するかどうかですです。これらもそれぞれ以下のメリット・デメリットが存在します。
1. スポンサー機能単体をマイクロサービスとして実装する場合
メリット: 新機能のみを新しく実装するため、実装の期間が短くなる
デメリット: 表示したいは大会に密結合していたり権限管理に主催団体情報を用いるので、うまく分離ができるか技術的に不安
2. 大会管理機能自体をマイクロサービスとして切り出す場合
メリット: 大会機能に紐づく情報をスポンサー機能自体にも流用しやすくなる
デメリット: 既存のモノリスに実装されている大会機能を再実装するのは時間がかかる。あとここまで拡張して本当にそれは「マイクロ」なのか?
このような話をチームメンバーにしたところ、「マイクロサービスをちゃんとやるなら切り出せる単位で細かく切り出せたほうが良い」と言われたので、それはそうだなと思いました。なので前者の「スポンサー機能単体をマイクロサービスとして実装する」を選択しました。
本当にこの切り出し方で終わりか?
「マイクロサービスをちゃんとやるなら切り出せる単位で細かく切り出せたほうが良い」と言う話を受けて、スポンサー機能自体を更に分けることは出来ないかと考えます。
また、マイクロサービスを行う際の分割として課金は単体で分離したほうが良いという話が散見されます。というのも、課金機能自体は他のサービスから呼ばれる汎用的なコンポーネントであり、これが課金以外の機能とくっついていると流用がしにくくなったり、いびつなサービス設計になったりします。
というわけでスポンサー機能は、
- スポンサー管理機能
- 課金機能
の2階建てで行うことにしました。
スポンサー管理サービスと課金サービスの責務分割
この2つは、既存のマイクロサービス同様GraphQLをしゃべるAPIサーバとして実装します。また、課金サービスはプラットフォーム課金の機能であるStripe Connectの存在や開発者的な使いやすさから決済ゲートウェイとしてStripeを採用しています。
機能の住み分けは、以下のようにしました。
スポンサー管理サービス
- 大会に紐づくスポンサーメニューの生成と管理
- スポンサー額の設定
- 大会に紐づくスポンサーの管理
- 申請, 却下, 承認, 解除などのサイクル
課金サービス
- Stripe Connect連結アカウントとTonamel上のアカウントの紐付け
- 任意のStripe Connect連結アカウントへの支払いセッションの生成
- 支払いの状態管理
- 仮売上, 本売上, 返金など
- Stripe Connect連結アカウントへの銀行口座振込
2つのサービスの連携方法
既存のマイクロサービスの連携にあった方法をそのままスポンサー管理サービスと課金サービスに適用しています。
こういったマイクロサービス間でサービス間で行う連携方法は、一般的には同期的な処理の移譲と非同期的なメッセージの送受信に分けられます。Tonamelのサービス内部でもそれぞれの用途にあった2つの方法を使用しています。
- サービス間連携用内部向けHTTP API
- 同期的に処理を行ったり、APIレスポンスに含まれている情報をすぐに利用するケースで使用しています
- ドメインイベントの送受信
- Tonamelは内部の非同期処理の設計手法としてドメインイベントを採用しています
- 実装の細部としては、Amazon SQSに対してメッセージにエンキューするためのミドルウェアが立っていて、それに対してHTTP APIでドメインイベントのエンキューを行います。SQSからAWS Lambdaが起動し、設定されたsubscriberエンドポイントに対してHTTPでドメインイベントを送信します。subscriberエンドポイントは各マイクロサービスがそれぞれ用意しています
連携の例: スポンサー申請時
スポンサー申請時、申請者が大会のスポンサーメニューを選択してフォームを入力すると、Stripeの決済画面に遷移します。決済の結果、主催者に申請が届き、主催者が申請を承認すると大会トップに申請した画像やユーザ名などが載ります。
実際に実装時に書いた図を貼ります。
SQSを挟んでいるevent-publisher
と書かれているのが、ドメインイベントを配送するためのミドルウェアです。GQL Proxy
にくっついているversus
はPerl製モノリスアプリケーションを指していて、セッション管理や認証を行うため、クライアントと各マイクロサービスのAPIとの間に挟まるAPI Gatewayのような役割も担っています。
図の見方ですが、各APIや状態は①,②などのように振られた番号の順番で進んでいきます。実線は同期的なリクエストとレスポンス、点線は非同期の通信です。
このようにしてみると、複雑にマイクロサービスや外部サービスであるStripeが絡み合っているのですが、最初にこの図を書くことで、どのようにすれば2つのサービスで1つの機能を実現するかの理想の状態が明文化されていたので、だいぶ助かったと思います。
この図は他にもスポンサー解除に伴う返金時や、振込などユースケースごとに記述して実装を進めました。
まとめ
- Tonamelで最近実装したスポンサー機能を2つのマイクロサービスに分割してそれぞれ実装した
- 連携方法をユースケースごとに記述しておくと迷いが少なく、また穴も実装する前に見つかって良い
他にもStripeの使い方とかで困ったりしたことがあったりしたんですが、そのへんの実装の詳細に関してはまたの機会にしておきます。
マイクロサービス設計に関して興味があるという方の募集もお待ちしております。
あとこの記事とはちょっと関係ないかもですが、Tonamelチームはフロントエンドエンジニアも募集しております。