課金システムをマイクロサービス化したお話

はじめに

こんにちは、Baseconnectのエンジニアインターンの東野です!

20xx年某日、僕はあるプロジェクトにアサインされました。その名も「課金システムマイクロサービス化」プロジェクトです。

このプロジェクトにはもちろん社員メンバーの方と一緒に携わらせてもらったのですが、今回代表して、Musubuの課金システムをマイクロサービス化した話を執筆させていただくことになりました!

まずMusubuって?という方にはぜひこちらを見ていただけたらと思います。 (ざっくり一言で説明するなら「営業支援ツール」になります。)

もともとMusubuはモノリシックなシステムだったのですが、課金システムをマイクロサービス化しようという流れになり、プロジェクトが開始しました。

課金システムの主な機能

  • 定期課金情報の管理
  • 支払情報の管理
  • カードへの請求や請求書払いなどの支払いに関する処理です

システム構成図

まずは大まかな課金実装のシステム構成は以下の通りです。

モノリシックな状態 マイクロサービス化後の状態

端的に言うとMusubuでカード決済代行サービスの処理を実行していたり、データベースの中に定期課金や支払いに関する処理を保持していたものを切り離しました。

課金システムのER図

次にどのようなデータがあるかについてです。以下の他にもモデルはありますが、概ねの流れを理解するのに必要な部分を抜き出したいと思います。

クライアントデータ

クライアントデータは、課金システムからみた外部システム、つまり今回でいうとMusubuになります。Musubu以外のサービスでも利用できるようにクライアントデータという名前で抽象的な概念として情報を持っています。

顧客データ

顧客データはユーザー情報のうち請求処理に必要な情報のみを保持しています。

課金データ

ユーザーの定期課金の情報を保持しています。次の支払い時期や月額単価などの情報を保持しています。

購入データ

毎月発生する定期課金の支払いや個別での支払いの情報を保持しています。

課金登録時の処理

  1. Musubuのユーザーが定期課金(有料プラン)登録
  2. Musubu内でアイテム(企業情報を取得することができる件数等)付与
  3. 課金に関するデータを作成
  4. カード決済代行サービスへカード情報作成と支払い処理をリクエス

この2~4の処理はすべて同一トランザクションにしています。 マイクロサービス化されているので、外部への処理の委託をトランザクションの最後に実行して、失敗した場合(レスポンスのステータスが2xx以外のとき)ロールバックするという風にすると、アトミックな処理に対して一貫性を保つことができます。

しかしリクエストの結果からさらにデータを更新する必要がある場合、トランザクションという方法で完全に一貫性を保つことはできませんので、リトライ等で対策する必要があります。

契約更新時の請求処理

  1. スケジューラが支払い処理を開始させる
  2. 更新日に達した課金データに対して支払い取得し支払い処理を実行します
  3. 支払い処理の成否をMusubuに通知して、Musubu側でのユーザーにアイテム付与や利用停止などの処理を行います

わかったこと

マイクロサービス化で難しい点

  1. データの整合性の担保
  2. 複雑な条件での検索(データが分散しているため)
  3. 各システムの責務の切り分け方(これが実装のしやすさや安全性に大きく関わってきます)
  4. 外部システムの知識を汎用的に表現すること(責務の切り分け方が悪い場合もありますが、どうしても外部システムの知識が必要になることがあります)

データの整合性の担保

課金登録時の処理の説明のところでも出てきていますが、どうしてもアトミックな処理が複数のシステムの処理をまたがってしまい、ぶつ切りになってしまいます。そんなときはアトミックな処理をできるだけユーザーにとってストレスのない単位で切り分けて、すぐに検知して正しいデータに戻せるようにする必要があります。

複雑な条件での検索

データが複数のDBに分散しているため、それぞれのDBにしかないプロパティを組み合わせた条件などで検索するには少し工夫する必要があります。一例として片方のデータベースにメタ情報として外部のシステムのデータを保持する方法があります。ただメタ情報をもたせすぎるとマイクロサービスではなくサブシステムっぽくなってしまうので注意が必要です。

各システムの責務の切り分け方

マイクロサービスは外部システムの知識を減らすことで、他のシステムでも利用できるようになったり、コードがシンプルになるというメリットがあります。なので責務を切り分けたタイミングで、外部システムの知識が混ざるような構造になっていると、サブシステムのようになってしまい他のシステムで利用できないようになったり、実装が複雑になったりしてしまい、あまり意味がなくなってしまうこともあります。

外部システムの知識を汎用的に表現すること

どこまでしっかり責務を切り分けたとしても、やむを得ない事情で外部システムの知識を保持する必要がでてくることがあります。そんなときはできるだけ汎用的にメタ情報(外部システムの情報)を表現することが大切ですが、その結果万能モデルみたいになってしまいコードやデータが複雑になりすぎてしまうことがあるので、できるだけ利用しないように運用を工夫するなどの対策をしなければならないです。

終わりに

今回は課金サービスの実装した経験を書かせていただきました。自分でもこんなにも重要なプロジェクトに参加させていただけるとは思っておらず、大変貴重な経験となりました。皆様にとって少しでも役に立てるような記事になっていたらとても嬉しく思います。ありがとうございました!

Baseconnectではエンジニアメンバーを絶賛募集しております! もしご興味をお持ちいただけた方がいらっしゃいましたら、下記のリンクよりぜひカジュアルにお話しさせていただけましたら嬉しく思います!

herp.careers