クリーンアーキテクチャ入門 - 達人に学ぶソフトウェアの構造と設計 を読んだ
はじめに
最近、「Clean Architecture 達人に学ぶソフトウェアの構造と設計」を読んで、クリーンアーキテクチャとSOLID原則について学ぶことができました。今回は、その中で得た知識を共有したいと思います。
ここから下の文章は、自分の感想や簡単なコードをChatGPTに再生性してもらいました。
依存性の管理とインターフェースの活用
クリーンアーキテクチャの目的は、コンポーネント間の依存性をインターフェースを使ってコントロールすることです。特に、依存性逆転の法則を実践することが重要です。依存性逆転の法則とは、抽象(ビジネスロジック)が詳細(具体的な実装、例えばどのデータベースを使うか、など)に依存しないように設計することです。これにより、アプリケーションの柔軟性が向上し、変更に強くなります。例えば、データベースへの保存を行う場合、次のようなRepositoryインターフェースを作成して依存性をコントロールできます。
// db_repostiroy.py
from abc import ABC, abstractmethod
class Repository(ABC):
@abstractmethod
def save(self, data):
pass
class DatabaseRepository(Repository):
def save(self, data):
# DBへの保存処理
pass
// business_logic.py
from db_repostiroy import Repository
class BusinessLogic:
def __init__(self, repository: Repository):
self.repository = repository
def some_logic(self, data):
# ビジネスロジック処理
self.repository.save(data)
// main.py
from business_logic import BusinessLogic
from db_repostiroy import DatabaseRepository
if __name__ == "__main__":
repository = DatabaseRepository()
business = BusinessLogic(repository=repository)
business.some_logic()
上のコードでは、Repositoryインターフェースが存在する事で、BusinessLogicクラスは、どのようなデータベースを使うのか知る必要が無くなっています。例えば、DatabaseRepositoryがsqliteでの処理を書いていても、postgresqlでの処理を書いていても、オンプレミス上にデータを保存しても、クラウド上にデータを保存しても、BusinessLogicクラスは、Repositoryインターフェースのsaveメソッドにしか興味がないからです。このようにして、ビジネスロジックは実装の詳細を知らずにすみます。
もう少し詳しく書くと、BusinessLogicクラスがRepositoryインターフェースに依存しています。具体的には、BusinessLogicのコンストラクタでrepository: Repositoryという形でインターフェースを引数として受け取り、some_logicメソッド内でself.repository.save(data)を使ってインターフェースを経由してデータを保存しています。
一方で、Repositoryインターフェースやその具体的な実装であるDatabaseRepositoryクラスは、BusinessLogicに直接依存していません。このように、依存性逆転の法則を適用することで、ビジネスロジックが具体的な実装に依存せず、インターフェースを介して間接的に依存することができます。
クラスで機能をまとめる際の工夫
機能をクラスでまとめる際には、使う人を一人にするという考え方が重要です。例えば、給料計算をするクラスが財務の人と総務の人に使われる場合、財務用と総務用にクラスを分けるようにしましょう。(責任を持つ相手を単一にするという事です。)
具体的な処理の呼び出しを抽象化
具体的な処理をするクラスを直接呼び出すのではなく、Factoryパターンのようなものを使って呼び出すことで、具体的な処理が変わっても呼び出す側は変更を気にしなくて済みます。これがインターフェースを使って依存性をコントロールする考え方の核心です。
例えば、上に書いたコードでは、Repositoryインターフェースを使う事で抽象化を行っています。
ビジネスロジックの独立性
クリーンアーキテクチャでは、ビジネスロジックは他のものに依存させないことが推奨されます。Factory等で作成した抽象クラスを使っておくことで、依存性をコントロールできます。
ディレクトリ構造の工夫
クリーンアーキテクチャを実践すると、ディレクトリが増えることがありますが、実際に複雑なアプリケーションやシステムを開発する際には、最初の段階でディレクトリ構造を整理しておくことが、後々の開発をスムーズに進めるために重要です。
追加で述べたいこととして、SOLID原則がクラスだけでなく、コンポーネント設計においても非常に重要であるという点です。SOLID原則は以下の5つの原則から構成されており、これらの原則はクリーンアーキテクチャと相互補完的な関係にあります。
開発の楽さ
クリーンアーキテクチャを実践することで、ビジネスロジックやデータベースから依存される場所以外は修正が容易になり、単純な機能の追加もルールに従ってコードを追加するだけで済むようになります。
シンプルなアプリケーションへの適用
外部のものに依存しないシンプルなアプリケーションに対しては、ディレクトリを多く作らなくても問題ないという柔軟さもクリーンアーキテクチャの良い点です。適切な構造を見つけるには、実際にいくつかのプロジェクトを経験し、バランスを見つけられるようになることが重要です。
テスト容易性の向上
クリーンアーキテクチャを適用することで、各コンポーネントがインターフェースを介して疎結合になるため、テストが容易になります。単体テストを行う際に、依存しているコンポーネントをモック化することが簡単にできます。
from unittest.mock import MagicMock
def test_business_logic_save():
mock_repository = MagicMock(spec=Repository)
business_logic = BusinessLogic(mock_repository)
data = "test_data"
business_logic.some_logic(data)
mock_repository.save.assert_called_once_with(data
SOLID原則
SOLID原則がクラスだけでなく、コンポーネント設計においても適用でき、かつ、重要であると感じました。SOLID原則は以下の5つの原則から構成されており、これらの原則はクリーンアーキテクチャと相互補完的な関係にあります。
単一責任の原則 (Single Responsibility Principle, SRP)
オープン・クローズドの原則 (Open/Closed Principle, OCP)
リスコフの置換原則 (Liskov Substitution Principle, LSP)
インターフェース分離の原則 (Interface Segregation Principle, ISP)
依存性逆転の原則 (Dependency Inversion Principle, DIP)
コンポーネント設計においてもSOLID原則を適用することで、依存性の管理が容易になり、メンテナンスや拡張性が向上します。例えば、単一責任の原則(SRP)は、コンポーネントが1つの責任だけを持つべきであることを示しており、これによりコンポーネントが他のコンポーネントと疎結合になり、変更やテストが容易になります。
また、依存性逆転の原則(DIP)は、高レベルの(より抽象的な)モジュールが低レベルの(詳細)モジュールに依存せず、両者が抽象(インターフェイス)に依存するべきであることを示しています。これにより、コンポーネント間の依存性が抽象化され、柔軟な構造が実現できます。インターフェイスをどちらのモジュールに組み込むかで、依存性をコントロール出来ます。
最後に
クリーンアーキテクチャを理解し実践することは、品質の高いソフトウェア開発に大きく寄与します。依存性逆転の法則を適用し、インターフェースを活用することで、アプリケーションの構造を柔軟に保ち、変更や拡張に強いソフトウェアを作成することができます。また、クラス設計やディレクトリ構造に注意を払い、開発の楽さやテスト容易性を向上させることで、プロジェクト全体の品質と効率が向上します。(と感じました。)