Golang*Echo: 実践クリーンアーキテクチャ
はじめに
最近golang * echoで開発を個人で実施しておりディレクトリ構成どうしようかなぁと悩んだ結果クリーンアーキテクチャに習った実装をあまりやったことないなぁとふと思い実践してみましたので、構成について共有します。
今回はgolang * echoですが、他の言語やある程度自由度のあるFWであれば展開できると思いますし、少しでもクリーンアーキテクチャについて知るだけでも何かしら役立ってくれるのでは。と思います。
クリーンアーキテクチャとは✨
クリーンアーキテクチャ (Clean Architecture) は、ソフトウェアの関心事を分離し、変更に強く、テストしやすいシステムを構築するための設計思想の一つです。Robert C. Martin (Uncle Bob) によって提唱されました。
このアーキテクチャの中心的な考え方は、依存性のルール (Dependency Rule) です。ソースコードの依存性は、常に内側(ビジネスロジックの中心)に向かうべきであり、外側のレイヤー(UI、データベース、フレームワークなど)の詳細を内側のレイヤーが知るべきではありません。
クリーンアーキテクチャの主な構成要素(レイヤー)
一般的に以下の4つの同心円で表現されます。
-
Entities (エンティティ):
- アプリケーション全体で共通の、最もコアなビジネスロジックとデータ構造をカプセル化します。
- 企業のビジネスルールそのものであり、外部の変更(UIやデータベースの変更など)の影響を最も受けにくい部分です。
-
Use Cases (ユースケース / Interactors):
- アプリケーション固有のビジネスロジックを実装します。
- エンティティを操作し、特定のアプリケーションの目的を達成するためのフローを制御します。
- このレイヤーは、UIやデータベースなどの外部要素から独立しています。
-
Interface Adapters (インターフェースアダプター):
- ユースケースやエンティティにとって都合の良い形式と、外部のツール(データベース、Webフレームワーク、UIなど)にとって都合の良い形式との間でデータを変換する役割を担います。
- 例: Controllers, Presenters, Gateways (Repositoriesの実装など)。
-
Frameworks & Drivers (フレームワーク & ドライバ):
- 最も外側のレイヤーで、具体的なツールやフレームワーク(Webフレームワーク、データベースドライバ、UIフレームワークなど)を含みます。
- いわゆる「詳細」であり、ビジネスロジックのコアからは最も遠い存在です。
クリーンアーキテクチャのメリット 👍
- テスト容易性: ビジネスロジックがUIやデータベースから独立しているため、単体テストや結合テストが容易になります。
- 独立性: UI、データベース、フレームワークなどの外部要素を比較的容易に変更・交換できます。ビジネスロジックへの影響を最小限に抑えられます。
- 保守性・拡張性: 関心事が明確に分離されているため、コードの理解が容易になり、機能追加や修正が特定のレイヤーに限定されやすくなります。
- 再利用性: コアなビジネスロジック(エンティティやユースケース)は、異なるアプリケーションやインターフェースで再利用できる可能性があります。
クリーンアーキテクチャのデメリット・考慮点 🤔
- 初期コストの増加: レイヤー間のマッピング処理やインターフェースの定義など、初期の実装コストが増加する傾向があります。
- 複雑性: 小規模なアプリケーションや単純なCRUD処理がメインのアプリケーションにとっては、過剰な設計となり、不必要に複雑になる可能性があります。
- 学習コスト: 設計原則や依存関係のルールを理解し、適切に適用するには学習が必要です。
- コード量の増加: 各レイヤー間のインターフェースやDTO(Data Transfer Object)の定義により、全体のコード量が増えることがあります。
ディレクトリ構造と各層の説明 📂
Golang Echoでクリーンアーキテクチャを実装する際の一般的なディレクトリ構造と、各層の役割を説明します。これは一例であり、プロジェクトの規模やチームの慣習によって調整してください。
僕はClerkを利用しているのでClerkが含まれていたりします。
EntityをDataBaseのモデルとは別でビジネスエンティティで永続化は保証しないものとなるのでここは注意です。
your_project_root/
├── cmd/
│ └── api/
│ └── main.go // アプリケーションのエントリーポイント、DIの配線、HTTPサーバーの起動 (Infrastructure)
├── internal/ // プロジェクト内部でのみ使用されるプライベートなコード
│ ├── domain/ // ドメイン層 (Entities)
│ │ ├── entity/ // ビジネスエンティティ (コアなビジネスオブジェクトとルール)
│ │ │ └── user.go
│ │ └── service/ // (オプション) ドメインサービス (単一エンティティに属さないドメインロジック)
│ ├── usecase/ // アプリケーション層 (Use Cases)
│ │ ├── interactor/ // ユースケースの具象実装
│ │ │ └── user_interactor.go
│ │ ├── repository/ // リポジトリインターフェース (データ永続化の抽象)
│ │ │ └── user_repository.go
│ │ └── dto/ // (オプション) ユースケースの入出力用DTO
│ │ └── user_dto.go
│ ├── adapter/ // インターフェースアダプター層
│ │ ├── controller/ // HTTPリクエストハンドラ (Echoコントローラ)、リクエスト/レスポンスDTO、ミドルウェア
│ │ │ ├── user_controller.go
│ │ │ ├── middleware/
│ │ │ │ └── auth_middleware.go
│ │ │ └── dto/ // コントローラ層のレスポンス/リクエストモデル
│ │ │ └── user_response.go
│ │ └── gateway/ // データ永続化や外部サービスアクセスの実装 (リポジトリ実装など)
│ │ └── db/
│ │ ├── user_gorm_repository.go // UserRepositoryインターフェースの具象実装
│ │ └── models/ // データベースモデル (永続化モデル、例: GORMモデル)
│ │ └── user_db_model.go
│ └── infrastructure/ // インフラストラクチャ層
│ ├── clerk/ // (例) Clerk SDKの初期化、Clerkクライアント管理
│ ├── config/ // 設定ファイルや環境変数の読み込み・管理
│ ├── database/ // データベース接続の初期化、設定
│ └── web/ // Webフレームワーク固有の設定 (Echoインスタンス生成、グローバルミドルウェア、ルーティング設定)
│ └── router/
│ └── router.go // ルーティング定義
├── pkg/ // (オプション) 外部に公開しても良い共有ライブラリコード
├── migration/ // データベースマイグレーションスクリプト
├── go.mod
├── go.sum
└── ... (Makefile, Dockerfile, .env など)
さいごに
意外とインターフェース使って、責務分離して、、みたいな実装を今までしてこなかったので役割/責務の分離についての理解が今までなんとなく理解しているようでも、このファイル・実装ってどこの層におくべきか悩む機会もあり、結果的に理解が深まった気がします。
正直、個人開発ならもっとバリバリ書きたい。めんどくさい。と思う時もありました...
Discussion