hacomono TECH BLOG

フィットネスクラブやスクールなどの顧客管理・予約・決済を行う、業界特化型SaaS「hacomono」を提供する会社のテックブログです!

非同期処理によるサービス間連携のしくみ - FitFits × hacomono の場合 -

こんにちは。FitFits開発部でエンジニアをしている和田です。
去る 2026/3/5 に、1年ほどかけて開発したサービス FitFits (フィットフィッツ)がローンチしました。
FitFits を一言で言うと「月額制のフィットネス施設検索・予約サービス」です。
会員の方には登録していただいた月額プランに応じたポイント(コインと呼称しています)が付与され、そのコインを使用して掲載された施設を予約・利用していただくという流れになっています。

FitFits のしくみ

FitFits は予約基盤に hacomono を利用したサービスとなっており、会員の方は FitFits アプリを通じて予約を行い、裏側では hacomono へ処理を流すかたちになっています。
掲載にあたっても、まずは hacomono へ店舗や予約枠を登録し FitFits へ掲載する流れになります。

おおまかなユースケースは上記のとおりですが、特にサービス間通信のアーキテクチャは開発後期までチューニングや見直しが入るなど苦労したポイントでした。
今回はそこにフォーカスしたアーキテクチャの紹介をしていきたいと思います。

非同期処理のモチベーション

一般論として他のサービスの API 呼び出しのような処理を非同期化することは以下のようなメリットがあるかと思います。

  • 他サービス呼び出し中に待たせないことによるUX向上
  • 疎結合となることによる耐障害性の向上
  • コネクション占有防止や流量制御によるリソース効率化

デメリットとしては以下のようなものがあります。

  • システム構成、UXの複雑化
  • 他サービス呼び出し失敗時の制御

FitFits のアーキテクチャを考えていくうえではポイントとしては以下のようなものがありました。

  • 1万以上の店舗さまに導入いただいている hacomono との連携に耐えられるスケーラビリティ
  • 反対に hacomono へ影響を及ぼさないための考慮
  • 掲載情報や予約といったデータのサービス間での整合性
  • コインの正確な管理

これらを考慮し、FitFits ⇔ hacomono 間の連携をどのように実装したのかこれから説明していきます。

アーキテクチャ詳細

予約処理 (FitFits → hacomono)

予約を実現するために必要なタスクは以下の様になっています。

これらを実装に落とし込んで考えると以下のようなアーキテクチャとなっています。

  • 予約データ作成(受付)、コイン減算
    • サービス内に閉じた処理 & 整合性のため同期的に処理
    • ここまでの成否を、一旦アプリにレスポンスする
      • アプリは予約受付の成功後、予約状況をポーリングして予約確定まで一定時間待機するようになっており、UXとしては同期的に予約が行われるようになっています。
  • hacomono への予約処理(API呼び出し)
    • ここで hacomono への負荷の考慮(流量調整)、hacomono の不調を想定し非同期処理としています
  • 成功後の処理
    • hacomono への予約と同一の処理の中で、予約データ更新(確定)を実施
  • メール送信
    • メールの送信はここ単体では非同期化するメリットは薄いのですが、他の同期的な処理(月額プラン契約など)においてメール送信を非同期化したい要件があり、そちらでのアーキテクチャに乗っかる形で非同期処理になっています。
  • 空き枠数の更新
    • 在庫情報を hacomono で管理している都合上、hacomono が担っており、別のフローで hacomono から非同期で連携
  • 失敗時の処理
    • hacomono への予約と同一の処理の中で、予約データ更新(失敗)およびコイン返却を実施
    • hacomono への予約の Lambda が予期せぬエラーで失敗した場合は DLQ にメッセージを送信し、その中で補償トランザクションとして予約データ更新およびコイン返却を実施(図では割愛)

掲載設定の更新 (hacomono → FitFits)

前項が長くなってしまったためこちらは簡単な紹介としますが、hacomono からのデータ連携は以下のようなフローとなっています。

  • hacomono への掲載設定の更新などをトリガに非同期でイベントバス※へ連携
  • イベントバスからFitFits へは API でデータを連携
  • API で DB に書き込まれたデータは、更に検索のために用意している OpenSearch へ非同期で反映

(※ hacomono で発生したイベントを非同期で連携してくれる別の基盤アーキテクチャ。図はかなりデフォルメしています。詳細はこちらを参照ください。

補足

ここではアーキテクチャに関する補足をいくつか説明します。

Transactional Outbox パターン

鋭い方は予約における非同期処理に SQS(+ Lambda) と Transactional Outbox のアーキテクチャが混在していることに疑問を持たれるかと思います。
開発初期は体制・コスト(簡単さや利便性)のため SQS を採用してきたのですが、「DBは更新されたがメッセージが飛ばない」もしくは「メッセージは飛んだがDB更新がロールバックされた」という原子性の問題を抱えているのが実際です。
開発が進み、Transactional Outbox のアーキテクチャが実装された現在は、特に重要な処理は Outbox アーキテクチャを利用し、そうでない場合はあるデメリットを受け入れつつ SQS を使い続けることを考えています。
現在はメール送信部分で試運転しているといった状況です。

冪等性の担保

SQS を利用する場合、処理が重複して実行される可能性があるため冪等性を担保する必要があります。
SQS を利用していない場合でもアーキテクチャの変更に耐えられるよう、分散処理を考える場合は常に冪等性は考慮した設計にすることが望ましいと考えられます。

冪等キーの利用

今回のアーキテクチャ内では利用していませんが、決済などのクリティカルな領域では冪等キーを用いて重複実行自体を許容しないようにすることがもっとも確実な方法です。
非同期とは話がそれますが、FitFits でも決済代行サービスを利用しており、そこでは冪等キーを利用して多重決済のないようにしています。

状態遷移の管理

例えばですが、予約処理が2回重複した場合に1回目で予約成功、2回目は予約済みであれば2回目は何もしないというという設計です。
状態遷移を整理し、遷移パターンごとの振る舞いを決定できれば冪等性を担保できます。
他にはリクエストにバージョン情報をもたせ、楽観ロックまたは処理をスキップさせるのも手段の一つになります。
例えば hacomono → FitFits への掲載設定のデータ連携は、以下のような設計になっています。

  • 連携データに hacomono 上の最終更新日時を含める
  • FitFits 側にも最終更新日時を永続化しておく
  • 連携で受け取った最終更新日時が、FitFits に保存済みの最終更新日時より古い場合は処理をスキップする
ロジックの工夫

UPSERT する、インクリメント・デクリメントせずに毎回集計し直すといった工夫で、冪等性が担保できることがあります。

トレードオフ

非同期処理を実装することで、UX向上やスケーラビリティを確保しつつ、サービスを疎結合にすることができました。
反面、前述した通り非機能面での考慮事項は多く、システムの複雑性は高まります。
一方で同期処理で実装したものを後から非同期化する場合、更に難易度が上がるものと考えられるため難しい判断が求められる部分でもあります。

機能(ビジネス)面でのデメリット

非同期というよりは掲載データの整合性モデルに結果整合性(一定時間後に同期が取られる)を採用したことによるデメリットといえますが、機能面でも複雑性が上がったり、妥協が求められる場合があります。
1例として以下のような問題があったため、参考までに紹介しておきます。

問題

あるレッスンの予約に必要となるコイン数を hacomono に設定した場合、「hacomono」「FitFits」および「アプリに表示されている内容」の3箇所にデータが存在します。それぞれで保持されている内容が異なる可能性があり、どの数字を正とすべきかが曖昧になります。

方針

FitFits が保持しているデータを正とします。アプリからは予約に使用するコイン数をリクエストに含めてもらい、サーバー側で突合することで FitFits とアプリ間の整合性を保っています。

妥協点

hacomono で設定された内容が FitFits に反映されるまでには僅かなラグがあります。その間に予約が入り、hacomono 側の設定と表示・突合結果に齟齬が出る可能性があるケースについては、店舗側に妥協してもらう設計としています。

おわりに

FitFits は機能もアーキテクチャも発展途上であり、本稿で紹介した考え方も利用状況や外部サービス側の変化にあわせて、これからも試行錯誤が続くものだと思っています。
運用していく中での課題やアップデート、知見がたまった際にはまたご紹介できればと思います。



📖関連記事