はじめに
クリーンアーキテクチャの書籍を読んだので、実際にクリーンアーキテクチャの考え方を採用したREST APIをGO言語で実装してみた。
↓↓↓↓ソースコード↓↓↓↓
https://github.com/yoshinorihisakawa/sample-api-hoop/tree/develop
この記事ではクリーンアーキテクチャの説明というよりかは、実装ベースの実践的な内容にしている。
対象読者
・クリーンアーキテクチャで実装されたソースコードを理解したい人
・クリーンアーキテクチャの右下の図がよくわからない人
・アーキテクチャについて勉強を始めた初心者
クリーンアーキテクチャとは?
クリーンアーキテクチャとは、8th Light, Inc.のブログ記事で提案されている。
一言で言うと、依存関係をコントロールし持続可能なソフトウェアを実現するための体系的な手法である。
※ DIやDIP、依存については知っていないと読みづらいかと思う。
知らなければ、以下の記事を一読しておくと理解が深まると思う。
・DI、DIP(https://qiita.com/yoshinori_hisakawa/items/a944115eb77ed9247794)
・SOLIDの原則(https://qiita.com/yoshinori_hisakawa/items/25576a62123607a696f6)
・インタフェース(https://qiita.com/yoshinori_hisakawa/items/cc094bef1caa011cb739)
クリーンアーキテクチャを採用することで嬉しいこと
それではここからはアーキテクチャについて説明していく。
クリーンアーキテクチャ、オニオンアーキテクチャなど
どのアーキテクチャでも採用することで得られることは共通していると思う。
それでは、具体的に説明して行く。
・フレームワークに依存しない
ソースコードは進化していくものであり、常に変化していくものである。
新しく便利なフレームワークに変更するときも、ドメインレイヤーに影響を与えることなく簡単に実現できる。
・テストが可能で書きやすい
各レイヤー同士が疎結合になっているため、テストが描きやすく。
少しの改修で、多くのテストが壊れることもない。
・UIから独立
UIは変わるものであり、UIが独立している事で、簡単に修正や変更が行える。
UIの変更がドメインに影響を与えることがなくなる。
・データベースから独立
どんなDBを使っても良い。
最初はpostgreSQLを使っていたがmysqlやMongo,Redshifなど変更することが出来る。
しかもドメインレイヤーなどに影響は受けない。
・外部機能独立
ドメインレイヤーは外部の事をなにも知らない。
なので外部機能を独立させることが出来る。
どうやって嬉しいことを実現するか?
ここからは「採用することで嬉しいこと」これをどう実現していくか説明していく。
ここからは、常に**「依存関係がどうなっているのか」**を意識して読む必要が出てくる。
以下の切り口で実現の方法を説明して行く。
・アーキテクチャの図を見て各レイヤーの定義を理解していく。← イメージを掴む
・パッケージ構成と実際のソースコードを見て理解していく。← イメージを実装に落とし込む
クリーンアーキテクチャの図を用いて理解 ~イメージを掴む~
これがクリーンアーキテクチャの有名な同心円の図である。
それでは各レイヤーの役割を見て行く。
Domain Model
ドメインを表すのに必要なデータとメソッドを表すレイヤー。
ここにはユビキタス言語でドメインを定義する。
依存関係:どこにも依存しない
UseCases
ビジネスロジックを実現するレイヤー。
Domain Modelなどを用いて業務手順を定義する。
依存関係:Domainに依存する
Interface Adapter
外(UI)と中(UseCases)をつなぐレイヤー。
以下の2点つの手順を定義する。
・外(UI)から受け取った値を、中(Domain Service)で扱いやすい形にする。=Controller
・中(Domain Service)から受け取ったデータを外(UI)が扱える形にする。=Presenter
上記のようにアクターが違う処理は分割するべきである。
これはSOLIDの原則の単一責任の原則を参照。
※ここはビジネスロジックでなく、技術的な関心事を書く
依存関係:UseCasesに依存する
Infrastructure
外の世界と中の世界をつなぐレイヤー
httpやAPIの通信を受け付けたり、DBとの接続などを担う。
例えば、APIなどなら受け取った値のバリデーションチェックなどを行う。
依存関係:Interface Adapterに依存する
パッケージ構成と実装を見て理解
ここまでの説明で、おそらくすごくぼんやりだけど、
クリーンアーキテクチャについてイメージが出来たかと思う。
Golangでユーザを登録・一覧取得のAPIを書いて見た。
これを使って、パッケージ構成や実装について詳しく説明して行く。
↓↓↓↓ソースコード↓↓↓↓
https://github.com/yoshinorihisakawa/sample-api-hoop/tree/develop
パッケージ構成
├── domain
│ └── model
│ ├── error.go
│ └── user.go
├── infrastructure
│ ├── api
│ │ ├── handler
│ │ │ ├── app_handler.go
│ │ │ └── user_handler.go
│ │ ├── middleware
│ │ │ └── middleware.go
│ │ ├── router
│ │ │ └── router.go
│ │ └── validater
│ │ └── validate.go
│ └── datastore
│ ├── db_mysql.go
│ └── user_repository.go
├── interface
│ ├── controllers
│ │ └── user_controller.go
│ └── presenters
│ └── user_presenter.go
├── main.go
├── registry
│ └── registry.go
├── usecase
│ ├── presenter
│ │ └── user_presenter.go
│ ├── repository
│ │ └── user_repository.go
│ └── service
│ └── user_service.go
依存関係をUMLで可視化
実装したソースコードのUMLを作って見た。
各レイヤーが下から上(外から中)にのみ依存していることがわかる。
ソースコードの説明
ソースコードを見ただけでは難解な箇所に絞って説明して行く。
1.クリーンアーキテクチャの右下の図
interfaceパッケージでは、controllersとpresentersが存在する。
controllersでは、usecaseのIFにのみ依存し、usecaseはpresentersのIFに依存する。
なのでusercaseパッケージにpresenterのIFを定義し、詳細は、interfaceで実装する構成になっている。
結果的に以下の図を実現している。
ここでわかりにくいのは、データの流れと依存関係の矢印だと思う。
依存関係はinterfaceからusecaseへの一方通行になっていて、
データの流れはcontroller→usecase→presenterとなっている。
2.依存関係を逆転しているRepository
usecaseはRepositoryのIFに依存している。
RepositoryのIFの実装は、infrastructureで行っている。
これがDIPである。
詳しくは、こちらの記事の依存関係逆転の原則の賞を参考にしてほしい
https://qiita.com/yoshinori_hisakawa/items/25576a62123607a696f6
3.DIコンテナ
registryではコンストラクタインジェクションを用いて全ての依存を解決している。
ここも以下の記事にまとめたので参照してほしい
https://qiita.com/yoshinori_hisakawa/items/a944115eb77ed9247794
わかりづらい箇所のQA
自分が勉強して行くにあたってわかりづらかった箇所をQ&Aにまとめてみた。
Q:DIPがよくわからない。
A:先になぜ依存関係をコントロールしなければいけないのかの理解をするべき。
DIPはただ依存の方向関係を変えるための手段なので、上記がわかると必然とわかってくる。
Q:依存関係ってなんで外から中になんだ?
A:AからBに依存しているとしよう。
仮にAを修正してもBには影響は一切ない。しかしBの修正はAに影響がある。
つまり変更しやすい箇所は外側(依存する側)にして行くことで、より持続可能なソースが実現できる。
Q:Domain ModelとDomain Serviceのどちらにロジックを定義するか
A:迷ったら必ずDomain Modelに書くようにしている。
ServiceレイヤーにDomain Modelで書けるロジックを書くと、変更可能なDomain Modelが作られてしまう。
オープングローズドの原則が参考になると思う。
まとめ
クリーンアーキテクチャの書籍を読んでから本当に頭の中がクリーンになった。
実際に実装してみクリーンアーキテクチャやオニオンアーキテクチャを採用する一番のメリットは
実装が楽しくなることだと思った。
参考になった記事
Clean ArchitectureでAPI Serverを構築してみる