ブログ一覧

モノリスかマイクロサービスか:変更・データ・運用からサービス境界を決める

· 約8分で読めます

サービス数を増やしても、変更の待ち時間が減るとは限りません。注文・在庫・通知の例で、モジュラーモノリスとサービス分割を比較し、トランザクション、契約テスト、運用責任まで一緒に設計します。

最初に描くのはサービス図より、変更が待たされる場所

受注アプリの納期表示を変えるたびに、注文・在庫・通知の担当者が集まり、全員のリリースを待つとします。アプリを3つに分けても、同じ会議と一斉リリースが続くなら、調整の問題は残っています。DORAは、他チームを細かく待たずに変更・テスト・デプロイできることを、疎結合なチームと設計の重要な性質として扱っています。

  1. 最近の変更を追い、誰の確認・どのテスト環境・どのリリースを待ったかを記録する。
  2. 同時に変更されたコードとDBの表を調べる。単に同じフォルダーだからではなく、同じ業務ルールで変わるかを聞く。
  3. 遅延の原因が設計の結合か、承認手順か、担当者不足かを分ける。分割で解決する範囲を明確にする。

注文と在庫の境界は、守りたい制約から決める

「確保できた在庫の数だけ注文を確定する」という条件を、今は同じDBトランザクションで守っているとします。注文サービスと在庫サービスを分けて別DBにすると、そのトランザクションをそのまま使うことはできません。予約・期限切れ・失敗時の取り消しを含む状態遷移など、新しい整合性の設計が要ります。

業務のまとまり正本と更新責任境界を越える約束
注文注文状態は注文モジュールが更新確定・取消の意味、重複した要求の扱い
在庫引当可能数と予約状態は在庫モジュールが更新予約の成否、失効の検知、取消と再試行の扱い
通知送信状態は通知モジュールが更新イベントの重複、再送、送信不能の確認

共有DBでも、他モジュールの表を自由に更新しないという責任の境界は作れます。注文と在庫を同一トランザクションで扱う必要があるなら、公開インターフェースを通して同じトランザクションに参加させる設計も候補です。論理的なモジュール境界と、別プロセス・別DBにする境界を区別します。

モジュラーモノリスとサービス分割を同じ条件で比較する

モジュラーモノリスは、公開する入口と依存方向を決めたモジュールを、1つのアプリとして配置する構成です。Martin FowlerのMonolith Firstは、サービス運用の追加負担と初期の境界の不確実さを論じています。これは必ずモノリスから始めるという法則ではなく、チームの経験や既知の境界も含めて判断する材料です。

観点モジュラーモノリスサービス分割
変更と公開内部を分けても、配置する単位は共通互換性を保てれば個別に配置できる
データ整合性同じDBのトランザクションを使える場合がある別DB間の失敗・再送・補償を設計する
容量アプリ全体を増やすのが基本。重い処理の分離も検討負荷の違う部分を個別に増やしやすい
障害の影響プロセスや資源を共有する影響がある分離の機会があるが、同期依存で障害が伝播しうる
運用配置・監視・調査する単位が少ない各サービスに監視、配布、認証、復旧の仕組みが要る

今回なら、まず注文・在庫・通知をモジュールとして分ける案が考えられます。通知の負荷や外部サービス障害が注文受付に影響しているなら、通知を非同期で分離する価値を検証します。注文と在庫は、整合性と運用の代償に見合う理由が確認できるまで、同じ配置単位に置く選択もできます。

境界を、コードの検査とデータの責任で守る

ディレクトリを分けただけでは、注文から通知の内部クラスを呼び、通知の表を書き換える依存が増えます。公開インターフェースだけを利用するルールと、禁止する依存を決めます。ArchUnitは、Javaコードのパッケージ依存や循環などを検査するための仕組みを提供しています。

モジュールの依存ルールの例。実装言語に合う検査へ落とし込む
orders -> inventory.public
orders -> notifications.public
禁止: orders -> inventory.internal
禁止: orders -> notifications.internal
禁止: notifications -> orders.internal
更新責任: 各モジュールの表はそのモジュールの処理から更新
検証: 依存検査 + 業務ルールのテスト + 重要経路の結合テスト

静的な依存検査だけでは、生のSQLによる別モジュールの表の更新や、動的な呼び出しまで保証できません。DBアクセスの実装、必要に応じた権限、レビュー、結合テストも合わせます。既存の違反を一度に直せない場合は件数と箇所を把握し、新しい違反が増えたら検査で止めます。

通知を取り出すなら、通信と復旧の約束を先に決める

通知を非同期で取り出す一例。保存と送信の間の失敗を設計に含める

注文と送信予定を同じDBトランザクションで保存 → 配送処理が送信予定を読み、イベントを送る → 通知サービスがイベントIDで重複を扱う → 外部への送信結果を記録し、不明な結果を照会・確認

注文保存後にHTTPで通知するだけだと、その間の停止で通知が抜けます。送信予定を注文と同じトランザクションで保存するOutboxなどを検討します。配送を再試行すれば重複しうるため、通知側でも重複を扱います。外部へのメール送信の直後に落ちた場合など、ローカルの処理済み記録だけでは二重送信を防げない境界もあります。

分割の成功は、サービス数より変更と運用の結果で測る

分割前後で、変更が出るまでの待ち時間、同時リリースが必要な頻度、障害の影響範囲、復旧に要した手間を比べます。サービスが増えても毎回まとめてテスト・公開しているなら、契約やデータの結合を再確認します。モジュラーモノリスを続ける場合も、共有する配置と資源がどこまで許容できるかを定期的に見直します。

設計レビューでは「通知の滞留を注文受付から分離したい。まず更新責任と非同期の契約を固め、変更待ちと障害時の影響で効果を確かめる」と説明できます。構成の名前に加えて、分割する目的と支払う代償が伝わります。

境界の設計と検査に使う一次資料

この記事で深掘りしたTips