1. プロジェクトの背景および技術選定
アンケートサービスは、患者が直接報告する結果(PRO、Patient-Reported Outcome)を扱う医療アンケートプラットフォームの中核サービスであり、アンケートフォームの定義から配信、回答収集、締め切りに至るまで、アンケートの全ライフサイクルを担います。採点・統計・レポート加工は別のメトリクスサービスが、通知・チャット・SMS/メールの実際の送信は別のコミュニケーションサービスが担当するよう責任を明確に分離し、本サービスは「アンケートデータの定義と収集」という単一責任に集中するよう設計しました。
多数の内部マイクロサービスおよび外部のレガシー医療情報システム(EMR)と連携する環境で、データ整合性と拡張性を確保するため、以下の技術スタックを戦略的に選定しました。
• Java 21 & Spring Boot 3.5.13 : 最新のLTSランタイムと安定したSpringエコシステムを基盤として、サービスの信頼性を確保しました。
• Spring Cloud 2025.0.0 (OpenFeign) : マイクロサービス間の同期通信を宣言的(declarative)な方式で処理し、可読性と保守性を向上させました。
• PostgreSQL & JPA/Hibernate & QueryDSL 5.0 : 複雑なアンケートドメインモデルをオブジェクト指向で管理し、型安全な動的検索を実装しました。JSONカラムはhypersistence utilsで処理し、可変的なアンケート属性を柔軟に保存します。
• Apache Kafka : サービス間の非同期イベント通信により結合度を下げ、結果整合性を保証します。
• OAuth2 & Keycloak : 分散環境における統合認証(SSO)・認可のため、業界標準であるOAuth2プロトコルを適用しました。
• Redis · ShedLock · MapStruct · Jasypt : キャッシュ、分散スケジューラーロック、オブジェクトマッピング、設定の暗号化など、運用の安定性を高める補助技術も併せて適用しました。
2. 主要技術の適用およびアーキテクチャ設計
2.1 ドメイン中心のマルチモジュールおよびヘキサゴナルアーキテクチャ
ビジネスロジックを技術的な依存関係から保護するため、ドメインを中心としたマルチモジュール構成を採用しました。これはヘキサゴナル(ポート・アダプター)アーキテクチャの思想を反映したもので、コアドメインロジックはdomainモジュールに集約し、データベース永続化(store-jpa)や外部サービス連携(proxy)は交換可能なアダプターとして分離しました。
サービスは責任に応じて9つのモジュールで構成されています:domain(ドメインモデル・ポート)、store-jpa(永続化アダプター)、proxy(外部連携・イベント発行アダプター)、feature(ユースケース)、facade(REST・イベント受信)、client/event(他サービスが利用する契約・イベントアーティファクト)、scheduler(バッチ)、boot(組み立て・エントリーポイント)。ユースケース層はドメインインターフェースのみに依存し、実際の実装はbootモジュールが注入(DIP)するため、連携対象が変更されてもコアロジックを修正せずに維持できます。
2.2 CQRSに基づくコマンド(Command)/クエリ(Query)の責任分離
書き込みと読み取りの要件が異なるアンケートドメインの特性に合わせ、CQRSパターンを適用しました。データ変更は正規化されたコマンドモデル(cm_*テーブル)で実行し、検索は画面に最適化された非正規化済みのクエリモデル(qm_*ビュー)で処理します。両モデルはイベントを通じて同期され、結果整合性を維持します。
これにより、複雑な一覧・検索処理がコマンドトランザクションに負荷をかけることなく、クエリモデルを画面の要件に合わせて自由に非正規化できます。コマンドの対象選定は常に最新値を保証するコマンドモデルを基準に行い、整合性を確保しました。
2.3 マルチテナンシーとソフト削除によるデータの分離・保護
複数の医療機関・診療単位が1つのプラットフォームを共有する環境に対応するため、共通ベースエンティティがテナント識別子(機関/診療部門/段階)をすべてのデータに自動的に注入するよう設計しました。これにより、アプリケーションコードの介入なしに、テナント単位でデータを分離できます。
また、すべてのエンティティに対して物理削除ではなく有効性フラグによるソフト削除を適用し、削除されたデータも監査(audit)追跡できるようにするとともに、医療データの履歴保存要件を満たしました。
3. 主要機能の実装および問題解決の経験
3.1 アンケートフォームの安全なバージョン管理(Master/Snapshotパターン)
アンケートフォームは運用中にも頻繁に修正されますが、すでに配信され患者が回答したアンケートについては、配信時点のフォームとの整合性を維持しなければならないという難しい要件がありました。これを「Master/Snapshot」パターンで解決しました。
• 編集(Master): 作成者はMasterフォームを自由に編集します。Masterは現在有効なスナップショットを指すポインターを保持します。
• 公開(Publish): 公開時点でMasterの内容を複製した不変(immutable)のSnapshotを生成します。その後に配信されるアンケートは、このスナップショットに固定されます。
• 回答の整合性保証: 患者の回答は常に配信時点のスナップショットを参照するため、フォームが後から変更されても過去の回答の意味が損なわれません。
公開前検証のためのプレビュー発行や、編集内容をMasterにマージするフローなども併せて実装し、運用中の無停止フォーム改訂とデータ完全性を同時に実現しました。
3.2 アンケートの配信・割り当ておよび回答収集のライフサイクル
アンケートの実施は「割り当て → 作業 → 状態」という段階でモデル化しました。割り当て(誰が誰に、どのようなスケジュールで送るか)から個別の実施単位である作業が生成され、作業の進行状態(未開始/一時保存/提出/完了など)は別の状態コンテナで管理されます。
回答は質問タイプに応じて、自由記述式は値として、選択式は選択項目の集合として分けて保存し、後続の採点・統計処理を一貫して行えるよう正規化しました。また、アンケートの完了・強制完了・期限切れの処理をユースケースとして明確に分離し、スケジューラーが締め切り・期限切れ・通信終了を自動的に実行することで、運用負担を軽減しました。
3.3 外部レガシー医療情報システムとの連携
既存の医療機関のレガシーEMRと連携しつつ、マイクロサービス間には物理的な外部キー(FK)を設けず、IDベースの論理参照のみを使用する原則に従いました。外部システムとの連携は、別のアダプターサービスをREST(Feign)経由で利用するようにし、外部システムの変更がコアドメインに直接侵入しないよう、腐敗防止層(ACL)を形成しました。
レガシーシステムが採番する識別番号を回答保存時点でマッピングし、両システム間のデータを整合性を保って接続しました。また、連携実装をStrategyパターンで抽象化し、将来的に別の機関・システムを追加する際にも、コアロジックを修正せずに拡張できるようにしました。
4. インフラおよび運用効率化
4.1 Apache Kafkaによるサービス間の結合度の解消
ドメイン上で意味のある事象が発生すると(フォーム公開、アンケート送信、回答完了、ケース完了など)、これをドメインイベントとして発行します。採点・統計サービスと通知・チャットサービスは必要なイベントのみを購読してそれぞれの責務を果たすため、サービス間の直接呼び出しによる依存が解消され、一方の障害が他方へ波及することもありません。
一方、ユーザー・組織情報の変更イベントは本サービスが購読して参照モデルを更新することで、マスターデータとの結果整合性を維持します。標準医療情報交換形式(FHIR)への変換用イベントも併せて発行し、外部連携の拡張性を確保しました。
4.2 運用安定性インフラ(Redis · ShedLock · Outbox)
• Redisキャッシュ:頻繁に参照される共通データをキャッシュ(TTLを適用)することで、参照性能を向上させ、データベースの負荷を軽減しました。
• ShedLock:複数インスタンス環境で締め切り処理や送信処理などのスケジューラーが重複実行されないよう、分散ロックによって処理の一意性を保証しました。
• Outboxパターン:共通ライブラリのOutboxパターンを活用し、データ変更とイベント発行を同一トランザクションにまとめることで、イベントを失うことのない信頼性の高い発行を実現しました。
4.3 セキュリティおよび認証
外部からの入口はOAuth2 Resource Serverで保護し、検証済みトークン(JWT)のみを許可するとともに、ロールベースのアクセス制御によって患者・医療従事者・運用担当者・管理者の権限を分離しました。サービス間の内部呼び出しでは、client_credentials方式のサービストークンをFeignインターセプターが自動的に注入し、ビジネスロジックを認証コードから分離する設計としました。データベース接続情報などの設定に含まれる機密値は暗号化(Jasypt)して保管し、認証情報や内部エンドポイントは運用環境変数からのみ注入することで、ソースに露出しないよう管理しています。
4.4 契約ベースのモジュール配布戦略
他のサービスが本サービスを呼び出すための通信契約(Feignインターフェース)と、購読するイベントスキーマをそれぞれclient/eventモジュールに分離し、社内アーティファクトリポジトリに配布します。利用側のサービスはこれらのアーティファクトを依存関係として取得して使用するため、APIの変更がコンパイル時に明らかになり、連携の安定性が向上します。
5. 結論および成果
アンケートサービスは、アンケートの定義から収集までのライフサイクル全体を単一の責務として凝集させながら、採点・通知などの後続処理をイベントとして分離し、拡張性と安定性を同時に確保した事例です。
• データ完全性:Master/Snapshotパターンにより、フォームが変更されても過去の回答の整合性を保持しました。
• 結合度の解消:Kafkaイベントベースの非同期通信によってサービス間の結合度を低減し、障害の分離を実現しました。
• 拡張性の確保:ヘキサゴナルなマルチモジュールとマルチテナンシー設計により、新たな連携対象や導入機関が増えても、コアロジックを修正することなく柔軟に対応できる基盤を整えました。
本プロジェクトを通じて培ったドメイン中心設計、CQRS、イベントベースアーキテクチャの経験は、今後複雑なドメインを扱うさまざまなシステムの設計・構築における重要な資産となるでしょう。
conley