初めに
現在、クリーンアーキテクチャ設計を元にして開発している案件があったので自分用のメモとして記事を書きました。
拙い文章もあるかとは存じますがご了承ください。
クリーンアーキテクチャについて
引用元: Clean Architecture 達人に学ぶソフトウェアの構造と設計
こちら二つの図はネットなどでクリーンアーキテクチャについて説明をされる時よく使われる図です。
クラス | 説明 |
---|---|
Controller/Presenter | APIのコントローラー |
View | APIのレスポンス |
Input Boundary/ Output Boundary | ユースケースのインタフェース |
Use Case Interactor | ユースケースの実装クラス |
Input Data | ユースケースの関数の引数 |
Output Data | ユースケースの関数の戻り値 |
Entities | ビジネスロジックの実装クラス |
Data Access Interface | リポジトリのインタフェース |
Data Access | リポジトリの実装クラス |
案件で実際に使用されているクリーンアーキテクチャ設計
案件ではすでにテンプレートが存在し、下記のような図のようにクラスを絞り実装しております。
クラス | 説明 |
---|---|
Controller | APIのコントローラー(インターフェースアダプター層) |
Use Case Interactor | ユースケースの実装クラス(ユースケース層) |
Input | ユースケースの関数の引数(ユースケース層) |
Output | ユースケースの関数の戻り値のインターフェース(ユースケース層) |
Presenter | ユースケースの関数の戻り値の実装クラス(インターフェースアダプター層) |
Domain Service | ドメインを跨いでの操作を担当するクラス |
Domain Model | ビジネスロジックの実装クラス(エンティティ層) |
Repository Interface | リポジトリのインターフェース(ユースケース層。本来であればインターフェースアダプター層) |
Repository | リポジトリの実装クラス(インターフェースアダプター層。フレームワークおよびドライバー層) |
Data Access Object | DBを参照・更新するためのオブジェクト (インターフェースアダプター層。GORMなどが該当する) |
project_root/
├── handler/
│ └── api/
│ └── main.go // エントリーポイント
│ └── api.go // APIのセットアップ(Controllerをまとめている)
├── pkg/
│ ├── interfaces/
│ │ ├── gateway/
│ │ │ ├── api/
│ │ │ │ └── example_controller.go // Controller
│ │ │ └── repositories/
│ │ │ └── example_repository.go // Repository (Implemention)
│ │ └── presenters/
│ │ └── example_presenter.go // Presenter
│ ├── domain/
│ │ └── model/
│ │ └── example_entity.go // Entities
│ └── usecases/
│ ├── interactor/
│ │ └── example_interactor.go // Use Case Interactor
│ ├── input/
│ │ └── example_input.go // Input
│ ├── output/
│ │ └── example_output.go // Output
│ └── repositories/
│ └── example_repository.go // Repository Interface
└── configs/
└── config.go // 設定関連
クリーンアーキテクチャを採用する理由と設計のポイント
1. クリーンアーキテクチャを採用する理由
クリーンアーキテクチャは、ソフトウェアシステムの設計において、柔軟性、保守性、テストのしやすさを向上させるために採用されます。以下では、クリーンアーキテクチャの設計する際に大切なことを箇条書きでまとめてみました。
2. 設計する際に大切なこと
- 依存性の逆転の原則(DIP)
- 高水準モジュール(ビジネスロジック)は低水準モジュール(インフラストラクチャ)に依存せず、インターフェースに依存すべき。
- ドメイン層(ビジネスルールなどが配置される)が技術的な詳細に依存しないようにする上でこの原則を使う。
-
インターフェースの使用をするメリット
- リポジトリやサービス層などでインターフェースを利用し、実装の詳細を隠蔽する。クリーンアーキテクチャでは、インターフェースを利用して依存関係の逆転を実現します。具体的には、ビジネスロジックやユースケースは、実装の詳細に依存せず、インターフェースに依存します。これにより、実際の実装を変更する場合でも、ビジネスロジックやユースケースのコードには影響を与えません。以下にそのメリットを具体的に説明します:
-
分離の明確化:
- インターフェースを使用することで、ビジネスロジックと実装の詳細(データベースアクセスや外部API呼び出しなど)を明確に分離できます。これにより、システムの特定の部分の変更が他の部分に影響を及ぼすのを防ぐことができます。
-
テスト可能性の向上:
- インターフェースを介して依存関係を注入することで、ユニットテストやモックの作成が容易になります。各レイヤーが独立しているため、モックやスタブを使用してユニットテストを簡単に作成できるようにする。
- Go言語の単体テストに関しては自分も記事を投稿しているのでぜひご覧ください
- 記事
-
再利用性の向上:
- インターフェースは特定の実装に依存しないため、異なるインフラストラクチャやフレームワークで再利用することが可能です。これにより、同じビジネスロジックを異なる環境で動作させることができます。
-
依存性の注入(DI)との相性が良い:
- インターフェースを使用することで、依存性の注入(DI)パターンを効果的に利用できます。DIコンテナを使用することで、依存関係の解決を自動化し、コードの管理をシンプルにすることができます。Go言語ではwireなどが使用され依存管理をしています。
-
具体例:
- 例えば、リポジトリパターンを使用する場合、リポジトリのインターフェースを定義して、ビジネスロジック(ユースケース)はそのインターフェースを使用します。リポジトリの具体的な実装は、データベースの種類に依存しますが、ビジネスロジックはその詳細を気にする必要がありません。インターフェースを切り替えるだけで、異なるデータベースを簡単にサポートできます。
- 単一責任の原則(SRP)
- 各層が一つだけの責任を持つためにこの原則を使う。Entities層(ドメイン層)はビジネスルールを担当し、Frameworks & Drivers層(インフラ層)は技術の詳細を担当するなど。
-
単一責任の原則(SRP) は、クラスやモジュールが1つの「責任」だけに集中するように設計することを強調します。これにより、システムの一部に変更が及ぶ際に、その影響範囲を最小限に抑えることができます。各層がこれに従うことで、以下のようなメリットがあります:
-
変更の容易さ:
- 各クラスやモジュールが1つの責任に特化しているため、その特定の責任に関する変更が他の部分に波及するリスクが減少します。例えば、ビジネスルールの変更が技術詳細に影響を与えないようにできます。
-
理解と保守の簡略化:
- クラスやモジュールが単一の責任を持つことで、その役割が明確になり、新しい開発者やメンテナンスを担当する開発者が理解しやすくなります。また、バグ修正や新機能の追加が容易になります。
-
再利用性の向上:
- 単一責任のクラスやモジュールは、他のプロジェクトやコンテキストでも再利用しやすいという特性があります。一つの責任に特化しているため、再利用が求められる場所に簡単に適用できます。
-
テストの効率化:
- 単一責任のクラスやモジュールは、その振る舞いが限定的で予測可能であるため、テストが容易になります。モジュールごとに明確なインターフェースを持たせることで、モックオブジェクトやスタブを活用してユニットテストの作成や実行が簡単になります。
-
変更の容易さ:
- レイヤーの明確な定義
- クリーンアーキテクチャの各レイヤー(エンティティ、ユースケース、インターフェースアダプター、フレームワークとドライバー)を明確に分けること。
- ただ上記の円状の図のように全部に分ける必要は必須ではなく、、依存関係をしっかりコントロールすることが出来れはディレクトリ構成やパッケージ構成はカスタマイズしてもよいようです。
- 参考元:クリーンアーキテクチャを少し説明できるようになれる記事
- 継続的なリファクタリング
- システムが成長し変化する中で、継続的にコードを改善しクリーンな状態を保つ。
- インターフェースを使用しているため、データの出入り口さえ変更がなければ他に影響を及ぼすことなく、リファクタリングできます。
まとめ
クリーンアーキテクチャを採用することで、ソフトウェア開発の品質と効率を向上させることができるなと感じました。
特に実装をしていて感じたのはリファクタリングのしやすさとテストの効率化です。
参考記事
- https://zenn.dev/sre_holdings/articles/a57f088e9ca07d
- https://qiita.com/o-y/items/755cb47f733687e8331f
- https://gist.github.com/mpppk/609d592f25cab9312654b39f1b357c60
- https://speakerdeck.com/shimabox/ni-ren-hua-dewan-quan-nili-jie-surukurinakitekutiya
- https://zenn.dev/castingone_dev/articles/77ecac84226521