TL; DR
以前、同じ様な題材の記事 を書いたのですが、 The Clean Architecture翻訳(いわゆるボブおじさん) を改めて読み直したり、アプリを何個か実装していく上で認識を改めましたので、改訂版として現在の考え方をまとめました。
今回、サーバーサイドの方がシンプルだったので、そちらを中心に説明していき、クライアントサイドは差分を中心に説明していきます。
サーバーサイドから見た Clean Architecture
大まかな流れとして、以下のようなAPIが実施されたとします。
- ユーザーからリクエストを受信
- DBからデータを取得し、それを利用して外部サービスからデータを取得
- 外部サービスから取得したデータをJSONにして返す
これを ボブおじさんの同心円 で出てくる登場人物にあわせた図にすると以下のようになります。(F/W & Drivers
に登場しているのは Go のライブラリとしています)
尚、 Entity
と Use Case
は Business Rules
として纏めています。
ボブおじさんとの違い
ボブおじさんでは、依存関係は必ず円の内側を向くとなっています。しかし、次の理由により、 Interface Adapter
を中心に内と外の両方に依存方向を向くように考えました。
-
F/W & Drivers
はライブラリ依存のコードが基本となるため変更が効かない - GIN や SpringWeb などの一般的な
Web F/W
を見ても、リクエストに対してのハンドラーを紐付けることで、そこにコールバックを返す形になる- この事から I/F を定義しているのは
F/W & Drivers
であり、Interface Adapter
がその実装を満たす形になっている
- この事から I/F を定義しているのは
-
DB
やHTTPクライアント
からデータを取得する場合も、Interface Adapter
がF/W & Drivers
を利用する形となっている -
Business Rules
とF/W & Drivers
は、相互にお互いを知らないという関係は保たれているので、関心の分離
は出来ていると捉える-
Business Rules
はフレームワークやデバイス、プラットフォームに依存しないので、(開発言語がマルチプラットフォーム対応可能ならば)複数プラットフォームに流用ができる
-
-
F/W & Drivers
が、Interface Adapter
とBusiness Rules
どちらにも依存しないので、モジュールとしての独立性が高く、単体で細かいテストが可能
Interface Adapter の役割
上の図の F/W & Drivers
と Business Rules
を並べてみました。
この図の凹凸(凸しかないですが)を各層への I/F(言語的な Interface
ではなく、ただの出入口として読んでください) とした際に、凹凸の位置や大きさが異なることからこのままではF/W & Drivers
と Business Rules
は連携できないことがわかります。
これ自体は 関心の分離
を行っているのであれば当然のように起こり得ることです。
そこで登場するのが Interface Adapter
となります。
上記の通り、 Interface Adapter
は F/W & Drivers
と Business Rules
両方に依存しているので、両者の I/F を知っている形になります。
そこで、 Interface Adapter
は両者の I/F の満たすような実装をしつつ、お互いをバイパスしてあげるようにします。
まさに名前の通り F/W & Drivers
と Business Rules
の I/F
に対する Adapter
となります。
Wiki より抜粋
Adapter パターンを用いると、既存のクラスに対して修正を加えることなく、インタフェースを変更することができる。Adapter パターンを実現するための手法として継承を利用した手法と委譲を利用した手法が存在する。
クライアントサイドから見た Clean Architecture
続いてクライアントサイドの図です。
サーバーサイドの図と違っている点としては下記の事柄くらいで、 Interface Adapter
を中心に F/W & Drivers
と Business Rules
の 関心の分離
を行っている点は一緒です。
良く View
のテストを書くのが大変という話が出ますが、 View
の I/F を満たした Stub
を作ることで Business Rules
と切り離した表示のみを確認するためのテストが書きやすくなっています。
サーバーサイドとの図の違い
-
UIスレッド
とバッググラウンドスレッド
が出てくるので、オブザーバーパターン
(Rx的に書いてます)で状態の変化を監視する形を取っておりその辺を赤線で表現しています -
View
契機の処理以外にも、スケジューラーなどによってView
を介さない場合も書いてます
実際の所
今回はあくまで理想的な形を説明していますが、どういう単位で実装して、どこをテストするのかなど、現場のコンセンサスに応じて依存性を反転させて I/F を書かく手間を省いたりしています。
理想を追いすぎても費用対効果が悪かったり現場が疲弊するだけだったりするので、あえて理想から崩す勇気も必要だと考えます。
まとめ
以前の記事から2、3ヶ月しか経ってないのに、ほぼ考えをガラ変させてしまいました。
また、よく見られる Clean Architecture
を取り上げた記事とも考え方が異なると思っています。
何が言いたいかというと、 常により良い形を求めて進化しよう
って事で、
ここに書いてあることがゴールだとは思っていないのでまた考えを改めるかもしれないですが、生暖かく見ていただければと思います。