保守性と拡張性を高めるためのSOLID原則とシステム設計
第12話:基本原則とクリーンアーキテクチャ
(最終更新日:2024.12.01)
(絵が小さい場合はスマホを横に)
クリーンアーキテクチャーとは?
今回は、システム設計における保守性と拡張性の重要性を解説し、その実現に役立つ基本原則とクリーンアーキテクチャの概要を紹介する。
保守性とは変更に対する柔軟性、拡張性とは新しい機能を追加する際の影響の少なさを意味し、
これらを両立するためには設計の明確な指針が必要になる。
クリーンアーキテクチャは、依存性を整理し、ビジネスロジックを外部から独立させる設計手法であり、
これを活用することでシステム全体の品質を向上させる方法を説明する。
1.アーキテクチャーの重要性
第1回で扱う「基本原則とクリーンアーキテクチャの紹介」の具体的な内容を詳しく説明する。 ここでは、保守性と拡張性がなぜ重要なのか、そしてそれを実現するためのアーキテクチャの考え方について解説する。
1.1 保守性と拡張性の定義
保守性と拡張性の定義は以下のとおりになる。
- 保守性:ソフトウェアを修正する際の容易さを指す。コードが理解しやすく、修正が簡単に行える状態、その保ちやすさを指す。
- 拡張性:新しい機能を追加、既存機能を改善する際の容易さを指す。システムがスムーズに進化できるように設計されていることが重要になる。
1.2 保守性と拡張性が重要な理由
保守性と拡張性が高いシステムは、長期的なコスト削減に寄与する。
修正や機能追加にかかる負担が軽減されるため、システムが長期間使用される場合でも、開発チームは効率的に作業を進めることができる。
また、保守性の高さはバグを減らし、品質を高い状態で維持するのに役立つ。
一方、拡張性の高さは、変化するビジネス要件や技術の進化に迅速に対応できるため、競争力を保つ上でも重要だ。
さらに、こうした設計はコードの理解や変更を容易にするため、開発者の生産性を向上させ、新たな機能や機能改善に集中できる環境を提供する。
1.3 目的に合ったアーキテクチャの重要性
システムアーキテクチャは、保守性と拡張性を確保するための基盤となる重要な役割を担う。
システムの目的を考慮したうえで、適切に設計されたアーキテクチャは、システム全体を柔軟で変更に強い構造にする。
つまり、アーキテクチャーの選択はシステムの保守性や拡張性に大きな影響を与える。
例えば、モノリシックなアーキテクチャは初期開発が容易である一方で、システムが大規模化すると保守が困難になる傾向がある。
一方、マイクロサービスアーキテクチャは、サービスごとに独立して変更や拡張が可能であるため、柔軟性に優れているが、
初期の設計と管理には高度なスキルが必要だ。
この中でクリーンアーキテクチャは、保守性と拡張性を最大化するアプローチとして働く。
ビジネスロジックとインフラストラクチャコードを明確に分離することで、システムの変更や拡張を容易にする。
本章では、このアーキテクチャの基本原則を概説し、次章以降でさらに詳細を掘り下げる。
開発の目的に合ったアーキテクチャーを選ぶ
2.クリーンアーキテクチャーの基本
クリーンアーキテクチャは、システムの保守性と拡張性を高めるために、依存関係の構造を明確にしたアーキテクチャスタイルだ。
このアーキテクチャは、ソフトウェアが長期間にわたって維持し、変化に強くなるように設計する。
クリーンアーキテクチャの根本的な考えには「依存関係は内側に向かってのみ流れるべき」という思想がある。
これは、ビジネスロジックなどの抽象的な概念が、インフラストラクチャーやユーザーインターフェースなどの詳細(具体)に依存しないようにするためのものだ。
2.1 クリーンアーキテクチャの層構造
クリーンアーキテクチャは、一般的に以下の層で構成される。内側から外側の順番で説明する。
- エンティティ:ビジネスルールや業務上のルールを表す。最も内側の層で、他のすべての層から独立している
- ユースケース:アプリケーション固有の業務ルールを表し、エンティティとインターフェースする層
- インターフェースアダプター:外部のインターフェースやデータベースとユースケースをつなぐ役割を果たす層
- フレームワークとドライバ:データベースやUI、Webフレームワークなどの外部の技術的な詳細を扱う、最も外側の層
依存関係は常に内側に向かって流れ、外側の層は内側の層に依存することはできない。 これにより、ビジネスロジックは技術的な詳細から隔離され、変更に強い構造を持つことができる。
2.2 具体は抽象に依存するという考え方
主に下記2つの原則に従う。
依存性逆転の原則:クリーンアーキテクチャの核心となる考え方の一つが「具体は抽象に依存するべきであり、その逆はない」というものだ。
具体的な実装(例えば、データベースやUIなどの技術的な詳細)は、抽象的なビジネスロジックやルールに依存する。
抽象層の役割:抽象層(インターフェースや抽象クラスなど)は、アプリケーションの主要なビジネスロジックを定義し、
具体的な実装はこの抽象層に従って動作する。
これにより、具体的な実装を変更しても、ビジネスロジックに影響を与えることなく、システムを進化させることが可能になる。
具体は抽象に従う
2.3 具体例
下記にその具体例を2つ示す。
リポジトリパターン:データベースアクセスを抽象化するリポジトリパターンを例にとって説明する。
ビジネスロジックは「リポジトリインターフェース」に依存し、その具体的な実装(SQL、NoSQLなど)は外部の詳細として扱う。
これにより、データベース技術を変更する場合でも、ビジネスロジックには影響がない。
依存性注入(Dependency Injection):
抽象層(インターフェースや抽象クラスなど)は、アプリケーションの主要なビジネスロジックを定義し、
具体的な実装はこの抽象層に従って動作する。
これにより、具体的な実装を変更しても、ビジネスロジックに影響を与えることなく、システムを進化させることが可能になる。
2.4 メリットと注意点
このアプローチにより、システムはより柔軟で保守しやすくなる。
また、技術的な詳細の変更がビジネスロジックに影響を与えないため、システムの拡張が容易になる。
しかしながら、抽象層が過剰になると、システムが複雑になりすぎる可能性があるため、バランスが重要だ。
また、開発初期における設計やインターフェースの決定が適切でない場合、後々の変更が難しくなることもある。
3.依存性逆転の法則
依存関係の逆転原則は、オブジェクト指向設計のSOLID原則の一つであり、 システム設計において「高レベルのモジュール(ビジネスロジック)は低レベルのモジュール(技術的な詳細)に依存すべきではない」という原則だ。 代わりに、両者は共通の抽象に依存するべきだというのが、本原則の定義である。これにより、システムを柔軟に拡張することが可能になり、 詳細部分(具体)の変更が、高レベルのビジネスロジックに影響を与えないようになる。
3.1 依存関係の逆転の実現方法
依存関係を逆転させるために、インターフェースや抽象クラスを導入する。
これにより、高レベルのモジュールは具体的な実装ではなく、抽象的なインターフェースに依存することになる。
詳しい説明は、第9話に示しているので、そちらを参考にしてほしい。
また、依存性を逆転させる具体的な方法に、依存性注入というテクニックがあり、こちらは第10話に詳しく記している。
3.2 リポジトリパターンを用いたDIPの適用
例えば、データベースにアクセスするためのリポジトリパターンを考える。
通常、ビジネスロジックはデータベースアクセスの具体的な実装に依存するが、
これを逆転させることで、ビジネスロジックが特定のデータベース技術に依存しないように設計する。
まずは、IUserRepositoryというインターフェースを定義し、ユーザー情報の取得や保存を行うメソッドを宣言する。
インターフェースの定義
次にインターフェースに依存する高レベルモジュールを作成する。 このクラス、ビジネスロジックはIUserRepositoryインターフェースに依存し、実装は気にする必要がない。
高レベルモジュールの作成
次に低レベルの実装クラスを作成する。 実際のデータベースアクセスを行うクラス(MySqlUserRepository)は、IUserRepositoryインターフェースを実装する。 このクラスは、ビジネスロジックに直接影響を与えることなく、変更が可能である。
低レベルモジュールの作成
UserServiceは、IUserRepositoryの実装を依存性注入によって受け取る。 これにより、UserServiceはどのデータベース技術を使用するかを意識せずに動作することができる。 振る舞いを決めているMySqlUserRepositoryインスタンスが、ビジネスロジックのUserServiceに注入(実装)される形だ。 注入するインスタンスをPosgreSqlUserRepositoryにすれば、PostgreSQLのデータベースを操作することができるし、 他のDBの操作を記述したクラスのインスタンスを注入すれば、そのDBを操作することができる。
4.まとめ
今回は、保守性と拡張性を高めるシステム設計の基本原則として、単一責任の原則や依存関係逆転の原則などを解説した。 また、クリーンアーキテクチャの概念を通じて、ビジネスロジックを中心に据え、外部依存を減らす設計手法を紹介した。 これにより、変更に強く、新機能追加が容易なシステムを構築する方法を理解した。 これらの原則は、開発者が持続可能で品質の高いソフトウェアを設計するための基盤となる。