アーキテクチャレベルで依存性を逆転させたら最高だった話
はじめに
LITALICO の @katzumi です。
2020 年に LITALICO へ Join して以来、ずっとレセプト業務の開発に携わってきました。
レセプト業務は複雑なドメインゆえミスが許されず、さらに3年に一度の大きな報酬改定があり、ロジックが大幅に変わります。
その改定作業は情報公開から実装完了までの期間が約 3 ヶ月と短いです。また、年々複雑化するシステムに対応する必要があります。
その複雑な業務に立ち向かった内容を過去にも以下の内容で開発業務の記事を書いていました。
今年 2024 年は法改正の年になっており、取り組みの結果、その後どうなったのか?を振り返っていきます。
今回も壮絶だった法改正
私自身の大規模法改正の経験が今回で 2 回目となります。
チーム構成としては私ともう一名を除いて前回の 2021 年度法改正を経験したメンバーがいませんでした。前回は 3 種類のサービスのみの対応だったのが、ビジネスの拡大に伴い 30 種類[1]のサービスまで拡大する必要がありました。
さらに、今回の法改正では新たな挑戦もありました。法改正対応を見据えて新規開発した「Rezept as a Service」が、各プロダクトとの連携を開始して間もない段階での法改正対応となりました。
さらに、今年は外部要因による困難も重なりました。補正予算の影響で 4 月から 2 ヶ月間限定の加算が発生し、システム構築に必要な情報の公開も例年より遅れました。
法改正の情報は前年 12 月から段階的に公開されるため、数日の遅延でも開発スケジュールへの影響は大きく、2 月からの本格的な設計開始で 4 月末のリリースという厳しいスケジュールを強いられることになりました。
加えて、報酬制度自体も複雑化が進んでいました。一例を挙げると、あるサービスの請求条件を表すサービスコードの数が前回の 3 倍に増加しました。
これは請求パターンの増加を意味し、テストの複雑度も大幅に上昇しました。 [2]
システム進化の歴史
最初から Rezept as a Service を作ろうと思って取り組みを行っていたわけではなく、2回の法改正を経て2度のリアーキテクチャを行っています。
以下にコンポーネント図 [3] をまとめます。
- 2021 年以前のシステムレセプトコンポーネントはモノリシックな構成で、お互いの API を呼び合い合うどちらかというと密結合な構成でした。
- 2021 年のリアーキテクチャ(1回目の法改正)時系列で関心事を分離して、振り分けの層をアプリケーションとして実現。請求業務アプリからレセプト業務が独立した状態。
- 2024 年の新アーキテクチャ(2回目の法改正)Rezept as a Service を確立した。プロダクトとの責務が明確になり、依存関係が一方通行になった。
Rezept as a Service はマルチテナントなシステムとして設計・実装している。ただし、データ管理およびシステム構成の都合から、シングルユースの構成で各プロダクトの VPC 上に構築している。
コアドメインの蒸留
上述のシステム進化が行われていますが、この構成になるにはコアドメインとなるレセプト業務の本質的な責務の見極めが必要となります。
コアドメインを見極めるため、まずレセプト業務の本質的な責務とそうでない部分の切り分けを行いました。
具体例を挙げると、以前のシステムでは利用者への負担額請求もレセプトシステムの一部として実装していました。しかし、これは保険者への利用料請求はレセプト業務の本質的な責務ではないと判断し、別システムとして切り離しました。このような見直しを複数の機能について実施していきました。
この取り組みにより、レセプトシステムは本来の責務に集中でき、各プロダクト特有の機能要件を排除できました。結果として、システム設計がシンプルになり、保守性も向上しました。
依存性を逆転させる
システムの依存関係を考えると、レセプト業務は事業所体制の情報や実績データを必要とします。
本来これらのデータを持つ各コンポーネントの"下流"に位置することとなります。
2021 年法改正時のシステムではこのデータ依存が残っており、上流のデータ仕様が確定するまでレセプトシステムの設計が進められないという問題がありました。今回、この依存部分を API インターフェースとして定義し、各プロダクトから依存させる形にしました。
これにより、データの流れは従来通り各プロダクトからレセプトシステムへと向かいますが、依存関係は逆転していることになります。
この依存性の逆転により、レセプトシステムは上位モジュールとして振る舞うことができ、各プロダクトの実装の詳細から完全に切り離されることになりました。
例えば、あるプロダクトが実績データの保存方法を変更したとしても、定められたインターフェースを通じてデータを提供する限り、レセプトシステムには一切の影響がありません。
このアプローチは、SOLID の依存関係逆転の原則をシステム全体に適用したものです。通常この原則は個々のクラスやモジュールレベルで適用されますが、今回はシステムアーキテクチャ全体に適用した点が特徴的です。
これにより、法改正対応という時間的制約の中で、レセプトシステムと各プロダクトが並行して開発を進めることが可能となり、プロジェクト全体の開発効率を大きく向上させることができました。
コアドメインとして依存をなくすことのメリット
今回行ったコアドメインの見定めと依存性の逆転は、ドメイン駆動設計(エヴァンス本)で説明されているドメインの蒸留そのものだと感じました。
この取り組みにより、これまで混在していたコンポーネントを明確に分離し、レセプトシステムを最も安定したサービスとして確立できました。
システムが安定することで、スキーマ駆動開発を通じて Rezept as a Service と各プロダクトの並行開発が可能になりました。これは法改正対応において重要な意味を持ちます。冒頭で触れた通り、法改正は非常にタイトなスケジュールで進める必要があり、手戻りが許されないためです。
実績データの蓄積方法については、各プロダクトが使いやすいシステムを目指して慎重に作り込みを行っています。これ自体は重要な取り組みですが、その結果として実績データの仕様確定に時間がかかりがちになります。
このような不安定な要素への依存は、システム全体の安定性を損なう原因となります。特に法改正対応では手戻りを避け、迅速に開発を進める必要があります。そのため、レセプト業務を他の要素に依存しない、最も安定したシステムとして設計する必要があると考えました。
コミュニケーション設計の重要性
Rezept as a Service は多くのプロダクトから依存される中心的なシステムとなったため、複数のチームとの連携の重要度が上がりました。そのため各チームからの問い合わせ対応などでコミュニケーションコストの増大が懸念されました。いかにして効率的なコミュニケーションフローを構築するかが、重要になってくると考えていました。
明確なサービス定義と仕様合意
報酬改定への対応には、告示内容、留意事項、その他資料から正しい法解釈を導き出し、それをシステムとして実装することが求められます。この法解釈を算定要件としてまとめ、システムインターフェースへと落とし込む作業を、各プロダクトチームメンバーと協業して行っていきました。
この作業は高度なドメイン知識が必要なうえ、抽象的な法規定を具体的な要件に変換する必要があり、非常に難しいものです。特に法的根拠を明確にし、具体的な論点を整理していかないと、仕様についての議論が堂々巡りとなりチーム間での合意形成ができません。
しかし、LITALICO は実際に支援事業を運営しているため、社内にドメインのエキスパートが多数在籍しています。さらに、SaaS として多くの事業所にサービスを提供していることで、豊富な現場のノウハウも蓄積されています。このような環境があることとは大変ありがたく、抽象的な法規定を具体的な要件へと落とし込むことが出来ていると感じています。
一方で、多くの関係者が議論に参加することで、空中戦になりがちでした。様々な立場の専門家の意見を円滑にまとめ上げ、全員が理解できる形で整理することは予想以上に難しいものでした。議論を効率的に進めるため、当初は独自のフォーマットを定めて進めようとしましたが、実際の運用では期待通りの効果が得られませんでした。
この反省から、法改正に関わる設計判断を記録する手法について検討中です。通常のシステム設計では Architecture Decision Record(ADR)を使用しますが、レセプト業務という特殊なドメイン領域では、そのままでは十分に機能しないと感じています。そこで現在は、報酬算定の構造を明確に記録できる「ドメイン特化型の算定構造決定レコード」のフォーマットが出来ないか?という所に挑戦中です。
バージョニングされた高品質なドキュメントと整備されたリリースフロー
コミュニケーションコストを最小にした場合の理想的な状態としては、連携する際に勝手にドキュメントを見て実装をし、勝手にデプロイして検証・リリースまで持っていってくれることです。
イメージとしては、プラットフォームエンジニアリングで言うところのセルフサービス型のアプリケーションです。各プロダクトチームが必要なタイミングで、ドキュメントを参照しながら自身で実装、デプロイ、検証、そしてリリースまでを完結できる状態が望ましいと考えています。
ここで、以下の 3 点が重要になります。
- ドキュメントとアプリケーションの一貫性(乖離がないこと)
- ドキュメントとコードの紐付けが明確
- 常に最新の状態を維持
- バージョン管理とリリースノートの整備
- 各バージョンの仕様書への容易なアクセス
- 変更履歴の明確な記録
- 柔軟なリリースフロー
- バージョンを指定したデプロイが可能
- セルフサービスでのデプロイを実現
上記のポイントが適切に運用されて実現するには、自動化が非常に大事になっています。
これらの要件を満たすため、開発プロセス全体を通して自動化の仕組みを組み込みました。
詳細を説明すると、今回の記事では収まらないので、今年の3月の CI/CD Test Night #7 で発表したスライドを紹介しておきます。
良かったら見てください。
コアドメインを一番安定させる為に
コアドメインとなる以上、品質の重要度が増します。品質が十分でないと各チームからの問い合わせが増え、結果としてコミュニケーションコストが膨らんでしまうためです。
採用したスキーマ駆動開発において、スキーマの品質がプロジェクト全体の成否を左右すると考えていました。そのため、スキーマ駆動開発を確実に実施できるよう、様々な取り組みを行いました。
先に触れたリリースフローの取り組みにも触れていますが、その他にも以下の取り組みを紹介しています。
- スキーマ自体の品質を向上させる為の取り組み
- API のシナリオテストの導入
スキーマ自体の品質を向上させる為の取り組みについては 「実装と乖離させないスキーマ駆動開発フロー / OpenAPI Laravel編」 にまとめていますので、ぜひご覧ください。
宣伝となりますが、こちらの取り組みの一部を今年の9月に OSS としてライブラリ化を行いました。こちらも良かったらお願いします!
API のシナリオテストについても、重要な取り組みの 1 つでした。Rezept as a Service は全ての機能を API として提供するため、効果的なテスト戦略の確立がプロジェクトの成功を左右する要素となりました。
API シナリオテストについて色々調べていた所、 runn に出会いました。すぐに惚れ込んでプロジェクトに導入しました。
導入後はプロジェクトに不可欠なツールとして定着しています。以下の記事で紹介していますので参考にしてください。
また、昨年の一人アドベントカレンダーの記事をベースにしたチュートリアル本も公開していますので、併せてご参照ください。
なにが最高なのか?
ここまで、コアドメインを見定めし、ドメインの蒸溜についてお話しました。また、よりコアドメインに集中する際の勘所についても触れさせて頂きました。
この一連の取り組みを通じて、コアドメインの独立性を確保し、安定した仕様でスキーマ駆動開発を円滑に行うという目標を達成できました。
しかし、実際にコアドメインと真摯に向き合ってみると、当初想定していた以上の効果が得られたことに気づきました。
- 責務がより明確になり、関心事にのみ集中すれば良くなる
以前は実績データの仕様変更など外部の動向に常に注意を払う必要がありましたが、インターフェースを介した関係性により、レセプト業務の本質的な責務にのみ集中できるようになりました。 - I/F 仕様書に向き合ってテストできる
インターフェースを契約として扱うことで、テストの境界が明確になりました。実績データの詳細なパターンを考慮する必要がなくなり、インターフェース仕様に基づいた厳密なテストケースの設計が可能になりました。これにより、全体の品質担保がより確実なものとなっています。 - コミュニケーションコストを最低限に抑え、コアドメインに集中できる
コアドメインが安定することで、各プロダクトチームは自身のペースで開発を進められるようになりました。結果として、チーム間での不要な調整が減り、それぞれが本質的な開発に集中できる環境が整いました。 - システム全体の開発のアジリティ向上できる
スキーマ駆動開発の採用と合わせて、コアドメインの仕様を先行して確定できるようになりました。これにより、各プロダクトチームが並行して開発を進められ、プロジェクト全体の開発サイクルが大幅に短縮されました。
これらの効果は、特に法改正対応において大きな価値があると感じています。以前は下流工程に位置していたために、多くのテストケースを開発終盤で消化する必要がありました。しかし現在は、早い段階から十分なテストを実施できるようになり、品質の確保と開発効率の両立が実現できています。
2度のリアーキテクチャを振り返る
2021 年と 2024 年の 2 度にわたるリアーキテクチャを経て、現在の設計に至りました。
この段階的なアプローチは、結果として良かったのではないか?と考えています。最初から現在のような完成形を目指すのではなく、システムとビジネスの理解を深めながら段階的に改善を重ねていったことで、本質的なシステムとして構築ができたと感じています。
特に以下の 4 つの要素は、段階的な改善の中で徐々に確立されていきました。
- コンポーネント間の依存関係の最小化
各段階で依存関係を見直し、必要最小限の結合度を実現 - Rezept as a Service を中心とした設計
システムの理解が深まる中で、サービスの本質的な価値を見出し確立 - インターフェースによる明確な契約の定義
実際の連携経験を通じて、より実用的なインターフェース設計を実現 - スキーマ駆動開発の本格的な導入
段階的な改善の集大成として、開発プロセス全体を最適化
このように段階的なリアーキテクチャを行ったことで、各フェーズでの学びを次の設計に活かすことができました。
もし最初から現在の形を目指していたら、システムの実態や運用の課題を十分理解できないまま設計を進めることとなり、かえって使いづらいシステムになっていた可能性があります。
結果として、この段階的なアプローチにより、システムの拡張性、保守性、そして法改正への対応力を現実的な形で向上させることができました。
最後に
本記事では、2 度のリアーキテクチャを通じて、アーキテクチャレベルでの依存性逆転の原則適用とその効果についてお話ししてきました。
- コアドメインの蒸留による責務の明確化
- アーキテクチャレベルでの依存性逆転による開発の並行化
- スキーマ駆動開発とテスト戦略による品質担保
- コミュニケーションコストの最適化
これらの取り組みにより、法改正という厳しい時間的制約がある中でも、高品質なシステムを提供できました。
このような大規模なリアーキテクチャを、エンジニアリング主導で進めることを支持していただいた @yuyaichihashi と @kazuis38 には深く感謝しています。
技術的な判断を信頼し、チームの裁量を認めていただいたからこそ、今回の成果を出すことができました。
また、この 2 度のリアーキテクチャに関わってくださった開発メンバーの皆様、ドメインの知見を共有してくださった事業部の方々にも心より感謝申し上げます。
プロジェクトの成功は、チーム全体の協力があってこそ実現できました。
今後も引き続き、コアドメインの洗練とシステムの進化に取り組んでいきます。
Discussion