品質要求が相反するシステムをどう分割するか — サービスベースアーキテクチャの実践

kosui
岩佐 幸翠 / kosui

テックリード @ 株式会社カケハシ

医療SaaSの共通基盤を開発。TypeScriptと関数型プログラミングで堅牢なシステム設計を実践。

はじめに

こんにちは、kosui(@kosui_me)(id:kosui_me)です。

本記事は SRE Kaigi 2026 での登壇「開発チームが信頼性向上のためにできること」の内容から、アーキテクチャ選定の部分を抜き出して再構成したものです。登壇ではRLSやドメインイベント、データ連携なども扱いましたが、この記事ではサービスベースアーキテクチャに絞り、登壇では時間の都合で話しきれなかった設計判断の背景や運用の教訓を紹介します。

普段は医療系のスタートアップで認証基盤・ライセンス基盤・組織階層基盤などのプラットフォームシステムを開発・運用するチームのテックリードをしています。

日本の医療に本気で向き合う。認証・権限管理基盤チームの決意 - KAKEHASHI Tech Blog

品質要求が相反する問題

私たちのチームは認証基盤、ID基盤、ライセンス基盤、端末・証明書基盤の4つのシステムを担当しています。社内の複数プロダクトが依存する共通基盤であり、障害が発生すると全プロダクトに影響が波及します。

プラットフォームの全体構成。プロダクト群がOIDCやAPIでプラットフォーム内の認証基盤・ライセンス基盤・端末/証明書基盤・ID基盤を参照し、ID基盤がデータ基盤へ反映する

課題は、これらのシステムがそれぞれ異なる品質要求を持っていることです。

認証基盤は「ログインできなければ医療現場が止まる」ため、可用性を最優先にする必要があります。一方、ID基盤は患者データの真正性を保証する必要があるため、整合性とトレーサビリティが最重要です。そして認証基盤はID基盤に依存しています。

認証基盤とID基盤の依存関係。認証基盤がID基盤を参照しており、この依存が品質要求の相反を引き起こす

この依存関係は、以下のような問題を引き起こします。

  • ID基盤で整合性維持のために停止してデータ移行を行いたいが、認証基盤は止められない
  • ID基盤でデータ不整合が発生すると、認証基盤も巻き込まれて停止せざるを得ない

品質要求が異なるシステムが密結合しており、一方を改善しようとすると他方に悪影響が出ます。この状況を解消する必要がありました。

加えて、チームは非常に小規模で、アプリケーション開発者の採用は進んだもののEmbedded SREを迎える余裕はありませんでした。SREの専門知識がない中でも信頼性を担保していくために、アーキテクチャの選定で運用負荷をできる限り抑える必要がありました。

何を選び、何を選ばなかったか

3つの要件が衝突していました。

  1. 基盤はまだ発展途上であり、安易に分割すると障害点が増える
  2. しかしデータの整合性は絶対に譲れない
  3. それでもシステムごとに独立デプロイしたい

この3つを同時に満たすアーキテクチャパターンを探して、4つの候補を検討しました。

マイクロサービス

マイクロサービスアーキテクチャ。サービスごとに独立したDB・デプロイ・API通信を持つ構成

最初に検討したのはマイクロサービスです。独立デプロイが可能で、スケーリングの自由度も高い構成です。しかし、実際に設計を詰めていくと「どこで分割するか」が定まりませんでした。4つのシステムは本来別の責務ですが、参照関係が絡み合っており、誤った境界で分割した場合の手戻りリスクが大きいと感じました。分散トランザクションの複雑さもあり、当時のチーム規模では現実的ではないと判断しました。

イベント駆動アーキテクチャ

イベント駆動アーキテクチャ。ProducerがEvent Brokerを介してConsumerに非同期でイベントを配信する構成

疎結合でスケーラビリティが高い構成です。しかし、結果整合性が前提となるため、私たちが求める強いデータ整合性とは相性がよくありません。Event Brokerが新たな障害点になることに加え、イベント消失時のリカバリなど運用負荷の増加も懸念されました。

なお、イベント駆動アーキテクチャを採用しない場合でも、監査証跡の確保は重要です。特に医療領域では、米国の医療情報に関する法律であるHIPAAが、情報システムに対して技術的セーフガードを要求しています。その一つである 45 C.F.R. § 164.312(b) は、システム上の活動記録を保持する仕組みの実装を求めるものです。日本の医療システムでも、この水準の監査証跡を確保しておくことは重要だと考えています。

私たちは、インフラレベルのイベントブローカーではなく、アプリケーション層でドメインイベントを永続化する設計を採用することで、この要件に対応しています。具体的な設計については「TypeScriptでドメインイベントを容易に記録できるコード設計を考える」で詳しく解説しています。

サービスベースアーキテクチャであっても、各サービスのユースケース層でイベントをDBに記録すれば、非同期メッセージングの複雑さを避けつつ監査証跡を確保できます。

モジュラモノリス

モジュラモノリスアーキテクチャ。単一デプロイユニット内でモジュール分離しつつ、単一データベースで強い整合性を確保する構成

単一DBで整合性を保ちつつモジュール分離が可能です。デプロイの独立性を求めなければ、モジュラモノリスはサービスベースアーキテクチャよりもシンプルに運用できます。特にデータベースマイグレーションの仕組みを単一に保てることは大きなメリットです。しかし、私たちのケースではデプロイが一体となる点が問題でした。認証基盤のみに脆弱性パッチを適用したい、といったケースに対応できません。

サービスベースアーキテクチャ

上の3つのどれも要件を完全に満たせない中で、カケハシのチーフアーキテクトである@kimutyamさんにサービスベースアーキテクチャの存在を教えていただきました。それをきっかけにMark RichardsとNeal Fordによる『ソフトウェアアーキテクチャの基礎』を改めて読み返し、この方法を試してみようと考えるようになりました。

サービスベースアーキテクチャとは

サービスベースアーキテクチャ。各サービスが独立デプロイ可能で、単一PostgreSQL内のスキーマを所有。サービス間のAPI通信は禁止し、他スキーマへはSELECTのみ許可する構成

概要は以下のとおりです。

  • 単一のDBを共有しつつ、サービスは独立してデプロイできる
  • サービス間のAPI通信は禁止
  • サービス間でDBの読み取りは許可
  • ただし、各テーブルは特定のサービスが所有し、他サービスは書き込めない

マイクロサービスの「独立デプロイ」と、モノリスの「単一DBによる強い整合性」を両立させる設計です。分散トランザクションの複雑さを回避しつつ、デプロイの独立性を確保できます。

DB共有というアンチパターンとの違い

「DB共有はアンチパターンでは?」という質問をよくいただきます。確かに、マイクロサービスの教科書には「各サービスは自分のDBを持つべき」と書かれています。

しかし、ここで言うDB共有アンチパターンとは「どのサービスもどのテーブルも自由に読み書きできる状態」を指しています。サービスベースアーキテクチャはこれとは異なります。

DB共有アンチパターンサービスベースアーキテクチャ
テーブルへのアクセスどのサービスもどのテーブルも自由に読み書きテーブルごとに所有権を持つ
書き込み権限制限なし所有サービスのみ書き込み可能
他サービスのデータ自由に変更可能読み取りのみ
変更の影響範囲不明所有権により追跡可能

重要なのは所有権です。各テーブル、またはスキーマ単位で明確な所有者が存在し、所有者のみが書き込みを行います。他のサービスは読み取りのみです。この点が「何でもありのDB共有」とは決定的に異なります。

なぜAPI通信ではなくDB直接参照なのか

// サービス間API呼び出し
const user = await userService.getUser(userId);

// DB直接参照
const user = await db.query(
  'SELECT * FROM directory.users WHERE id = $1',
  [userId]
);

以前、サービス間をAPI経由でデータ参照していた時期がありましたが、DNSキャッシュの問題でレスポンスが不安定になった経験があります。API呼び出しはネットワーク障害による連鎖障害のリスクを常に抱えています。

DB直接参照であれば、ネットワーク遅延の影響を受けず、トランザクション内で整合性も保証できます。私たちの目的は「独立デプロイ」であって「ネットワーク分離」ではないため、DB直接参照で十分でした。

PostgreSQLでの実装

スキーマ分割

PostgreSQL内のスキーマ分割。auth/directory/assetスキーマがそれぞれ認証/ID/ライセンスサービスに所有され、サービス間はSELECT参照のみ

単一のPostgreSQL内に、サービスごとのスキーマを作成します。

PostgreSQL
├── auth スキーマ        ← 認証サービスが所有
├── directory スキーマ   ← ディレクトリサービスが所有
└── asset スキーマ       ← 端末管理サービスが所有

物理的には1つのDBですが、論理的にスキーマで分離しています。

DBユーザーによる権限制御

「自分のスキーマのみ書き込み、他は読み取りのみ」を運用ルールではなくDBの権限として強制します。これが重要なポイントです。

-- 認証サービス用のロール
CREATE ROLE auth_service;
GRANT ALL ON ALL TABLES IN SCHEMA auth TO auth_service;
GRANT SELECT ON ALL TABLES IN SCHEMA directory TO auth_service;

-- ディレクトリサービス用のロール
CREATE ROLE directory_service;
GRANT ALL ON ALL TABLES IN SCHEMA directory TO directory_service;
GRANT SELECT ON ALL TABLES IN SCHEMA auth TO directory_service;
GRANT SELECT ON ALL TABLES IN SCHEMA asset TO directory_service;

auth_service はauthスキーマにフルアクセスできますが、directoryスキーマはSELECTのみです。運用ルールではなくDBレベルで制御しているため、開発者が誤ってINSERTを実行しようとしてもブロックされます。

発展可能性

サービスベースアーキテクチャを選んだ最大の理由は、発展可能性にあります。

ドメイン境界が明確になった段階で、特定のサービスをDBごと分離してマイクロサービス化できます。非同期処理が必要になった箇所にのみ、イベント駆動パターンを部分的に導入することも可能です。逆に「まだ分割しない」という選択を取ることもできます。

発展は分割方向だけではありません。運用を通じて「これらのサービスは実際には密結合で、別々にする必要がなかった」と判明した場合、モジュラモノリスへ統合し直すことも比較的容易です。DB共有のおかげでデータ移行が不要だからです。

一方で、一度マイクロサービスとして完全に分離してしまうと、再統合は大規模な作り直しになります。分離は不可逆に近い操作です。

早すぎる分離は誤った境界での分割を招きます。「現時点で判断できないことを認め、将来の選択肢を残す」ことが、このアーキテクチャの本質だと考えています。

課題

もちろん課題もあります。

テーブルの公開/非公開をどう明示するか

現状、私たちは同一チームで複数の基盤を運用しているため、「このテーブルは読み取ってよいのか」という混乱は発生していません。しかし、スキーマ内のすべてのテーブルが他サービスに公開されるべきとは限りません。たとえば、パスワードハッシュを管理するテーブルは認証基盤以外のサービスから参照されるべきではありません。

こうした「内部専用テーブル」と「公開テーブル」の区別を、仕組みとして明示する方法はいくつか考えられます。命名規則でプレフィックスを付与する方法や、DBユーザーの権限をテーブル単位で細かく制御する方法などです。現時点では同一チーム内の暗黙知で運用できていますが、チームの人数が増えたり、他チームがスキーマを参照するようになれば、「どのテーブルを読んでよいのか」という判断を個人の知識に頼るわけにはいきません。組織の拡大に先んじて、構造的な解決策を整備していく必要があると考えています。

スキーマ変更にはデプロイ順序の調整が必要

他サービスが参照しているテーブルのカラムを変更するには、影響範囲の調査とデプロイ順序の調整が必要です。カラム削除やリネームの場合は、参照元サービスの更新を先にデプロイしなければなりません。デプロイの柔軟性については、まだ改善の余地が残っています。

データベースが単一障害点になるリスク

サービスベースアーキテクチャの最大のトレードオフは、単一のデータベースが全サービスのSPOFになることです。マイクロサービスであればDB-Aが停止してもサービスBは影響を受けませんが、DB共有ではそうはいきません。

このリスクを軽減するために、いくつかの対策があります。

リードレプリカによる読み取り負荷の分散

PostgreSQLのストリーミングレプリケーションでリードレプリカを構成し、参照クエリをレプリカに振り分けることで、プライマリの負荷を軽減できます。サービスベースアーキテクチャでは他サービスのスキーマへのアクセスがSELECTのみであるため、クロススキーマ参照をリードレプリカに向けるのは自然な構成です。ただし、レプリケーション遅延は通常ミリ秒単位ですが、負荷が高い場合は秒単位に達する可能性があります。強い整合性が必要な読み取りはプライマリに向ける必要があります。

自動フェイルオーバーによるダウンタイムの最小化

Patroniやpg_auto_failoverなどのツールを使用すれば、プライマリ障害時に自動でスタンバイへ昇格できます。Amazon Aurora PostgreSQLであれば、フェイルオーバーは通常30秒以内、RDS Proxyを併用すれば10秒以内に完了します。いずれの場合もアプリケーション側のコネクションプーリングの再接続処理が必要です。

根本的な限界

これらの対策を講じても、論理的なSPOFが完全に解消されるわけではありません。共有DBに対する破壊的なスキーマ変更やデータ破損は、すべてのサービスに同時に影響します。これはサービスベースアーキテクチャの本質的なトレードオフであり、「独立デプロイ」と「強いデータ整合性」を両立するための代償です。将来的に特定のサービスの可用性要件が極めて高くなった場合は、そのサービスのDB分離を検討する必要があります。これは前述の発展可能性で触れた、マイクロサービス化への自然な移行パスです。

運用で学んだこと

  • テーブルの所有権を明文化してADRに記録する。新しいメンバーが「このテーブルは誰のものか」と迷わないようにする
  • SELECT権限の付与は慎重に行う。一度読み取り権限を付与すると依存関係が生まれるため、DB直接参照が本当に必要か、データ基盤経由で十分ではないか、都度検討する
  • テスト環境でも本番と同じロール設定を再現する。開発環境でスーパーユーザーを使用していると、本番で初めて権限エラーに気づくことになる

まとめ

サービスベースアーキテクチャは、データの強い整合性が必要であるが、デプロイは独立させたい、ドメイン境界もまだ不明確で、チームも小規模、という状況に適したアーキテクチャです。

「マイクロサービスかモノリスか」という二項対立に陥りがちなアーキテクチャ選定ですが、その中間にこのような選択肢があることを知っていただければ幸いです。

設計パターンは選定して終わりではなく、運用の中で課題を発見し、継続的に改善していくものだと実感しています。

他に書いたサーバサイドTypeScriptの記事

https://kosui.me/posts/2025/05/06/142842

https://kosui.me/posts/2025/12/10/210415

参考文献

Share

kosui
岩佐 幸翠 / kosui

テックリード @ 株式会社カケハシ

医療SaaSの共通基盤を開発。TypeScriptと関数型プログラミングで堅牢なシステム設計を実践。