本項は「C# Tokyo オンライン「世界一わかりやすいClean Architecture」他」による発表の登壇原稿となります。過去に発表した.NET版の記事はこちらにアーカイブしています。
本稿のサンプルコード・PPTはこちらで公開しています。 「CC BY-SA 4.0」で公開していますので、気に入っていただけたら営利目的含め、ライセンスの範囲で自由に利用していただいて問題ありません。
また動画を以下で配信しています。よろしければご覧ください。
はじめに
まず初めに、クリーンアーキテクチャの誤解されがちな二つのことについてお話させていただきます。
その上で、クリーンアーキテクチャの本質とは何か?押さえておくべき、本当に重要だと考えている三つの事について、お話しします。
注意事項
さて本題に入る前に、少し注意事項があります。
今回お話しするのは、あくまで私の解釈です。
特に極端に要約しているので、今日お話しすることがすべてではないことはご理解ください。また明確に書籍に書かれているわけではないですが、拡大気味に解釈している箇所もありますし、書籍の記載とやや異なる説明もします。
それでも私は著者が本当に言いたかったこと、クリーンアーキテクチャでもっとも大切なことは今日お話しする三点にあると考えています。
なお異論・反論は大歓迎です。ぜひ議論させていただけると嬉しいです。
誤解されがちな二つのこと
この図をご覧になったことのある方は多いんじゃないかと思います。
実はこの図が良くできてい過ぎることが、大きな誤解を招く要因になっていると私は考えています。
この図の何がそんなに誤解を生んでいるのでしょうか?
ひとつは、この図は決してソフトウェアの関心をControllerやUseCase、Entityに分離しろという意味でもなく、レイヤーをこの4つに分割しろという意味ではないということです。
そしてふたつ目は右下のこれです。
これはデータや処理の流れを一方通行にしろという意味ではないですし、Presentationを古典的なMVCのように設計しろと言っている訳でもありません。
では、どういう意味でしょうか?
この図で本当に大切なことは、依存性は外から中だけに向かっていなくてはいけない。
というたった一点にあります。
でもじゃぁ、例えばEntitiesの状態が変わったことで、UIを受動的に変化させたい。みたいな、中から外を呼ばなければいけないときはどうすれば良いの?という疑問が、すぐに湧いてきますよね?
右下の図は、内側の変化に応じて外側を呼び出したい場合に、依存性は外から中に向けたまま、処理の流れとして中から外に戻すにはどうすればよいのか?
を例示しているものです。
そんなこと言っても、本当にあってるのか?って、今皆さん思いましたよね?
はい。もちろん根拠あっての話です。
そもそも著者は書籍の冒頭でこう明言しています。
「アーキテクチャのルールはどれも同じである!」
日本語版では帯にも書かれていますよね。
つまり、本当にすべてのソフトウェアが同一のアーキテクチャに帰結するとすれば
- レイヤーが4つ限定であったり
- UIがMVC的でMVVMなどを否定していたり
- そもそもWeb限定だったり
- 中から外の呼び出しがApplicationレイヤーとInterface Adapterレイヤーの間だけで発生する
なんて分けないですよね?
レイヤー数が4つで有る必要性がない事は、書籍にも明確に記載されています。
書籍を読めば、ちゃんと先の図があくまで例示であると書かれています。
もともとクリーンアーキテクチャの発表前は、ヘキサゴナルアーキテクチャとかオニオンアーキテクチャが広く共有されていました。そしてそれらのアーキテクチャには類似の共通点があることも知られていました。
クリーンアーキテクチャの解説は、それらの著名なアーキテクチャは、先ほどの図のように記述することで単一のアイディアに統合することを例示するために書かれた図なんです。
そう
クリーンアーキテクチャというのは、どんなソフトウェアにも適用可能な普遍的なアーキテクチャです。
誤解を恐れず言えば、クリーンアーキテクチャを採用しないという選択肢は、「基本的には」無いといって差し支えありません。
おさえるべき三つのこと
では先の図のように実装することがクリーンアーキテクチャではないとしたら、クリーンアーキテクチャについて何を抑えたらよいのでしょうか?
私はつぎの三つこそ、本質的に重要なことだと考えています。
ひとつめは先ほども話しました。
ソフトウェアは、より上位レベルの方針にのみ依存せよということ。
ふたつ目は、ひとつめを実現するために、依存性を制御の流れから分離してコントロールする必要があるということ
そして最後に、「上位レベル」とは相対的かつ再帰的であることに留意する必要がある。ということです。
さあ具体例を見てみよう!
てことで、ここからは具体例を見ながら解説したいと思います。
具体例はAndroidのアプリケーションを例にお話ししますが、まずはサンプルアプリケーションを開発することになった背景からお話ししましょう。
これは大切な意味があることです。
あるところに、田中さんという方がいました。田中さんは技術営業をしている方です。
田中さんはホットペッパーというサービスが大好きで、忙しいさなか、新しく訪れた場所で、新しいお店と出会うことが一つの生きがいです。
田中さんは、ホットペッパーが大好きなんですが、一つだけ不満があります。
それは、毎日必ず同じ条件で検索するのですが、都度明示的に検索するということ自体を面倒だと感じているということです。
そこで田中さんは、自分用のホットペッパー検索アプリを開発しようと考えました。
アプリケーションはリクルートさんのWebサービスを利用させてもらい、大好きなホットペッパーの情報を閲覧できるように作ります。
アプリは起動するとダイレクトに周辺の店舗情報を検索して一覧表示します。
そのさい、お昼の時間帯であれば自動的にランチ営業のある店舗だけを表示します。
こういったアプリケーションを、まずは伝統的なレイヤーアーキテクチャで作成し、その問題点を解説した上で、クリーンアーキテクチャを適用していく。
という流れでお話したいと思います。
Layered Architecture
全体は大きく、Presentation・Usecase・Infrastructureの三つに分割されています。
Infrastructureには、外部のWebサービスを利用するApi、位置情報を利用するlocation、時間を利用するtimeコンポーネントが含まれています。
それぞれのコンポーネントにはこちらのようなクラスが含まれていて
こんな感じで動作します。
まずViewModelからusecaseのfindが呼び出されます。
UsecaseはDeviceLocationProviderを利用して、現在の位置情報を取得し、続いてTimeProviderから現在時刻を取得します。
そしてGourmetSearchApiを呼び出して、周辺のレストラン情報を取得します。
一見、そんなに悪くないように見えますよね。しかし実際には大きな問題を含んでいます。
では一体、何が悪いのでしょうか?
それは安定度と柔軟性のバランス配分です。
このレイヤーモデルでは、柔軟性と安定度は、こちらの図のような配分になっています。
usecaseとinfrastructureの柔軟性と安定度の配分に問題があります。なぜこのような配分になるのか?
それを理解するためには、依存関係による安定度と柔軟性のトレードオフを理解する必要があります。
依存関係にあるオブジェクトがあったとします。
オブジェクトはクラスでもいいですし、コンポーネントでもいいですし、なんならサブシステムでも構いません。
それぞれを変更することを考えてみると、理解できます。
まずは依存される側を変更することを考えてみましょう。依存される側を変更すると、
依存する側は、何らかの影響を受けます。
従って、依存する側は、依存される側より安定度が低くなります。自明の理ですよね。
逆に
依存する側を変更しても
依存される側には影響を与えず、依存先を考慮せず、自由に変更できることから、依存する側の柔軟性は、相対的に高くなります。
そうです。
依存関係にあるオブジェクトの間の安定度と柔軟性は、どうしてもトレードオフの関係が発生します。
さて、これを逆説的に考えると
変更頻度の高いオブジェクトは、依存する側として、関係性を設計すべきですし
アプリケーションの本質的な価値を提供する「上位レベル」のオブジェクトは、軽微な変更の影響を受けないよう、依存される側として設計すべきです。
先ほどのモデルを振り返ってみましょう。
全体として依存の方向は、上から下へと統一されています。
ではこれらのうち、どこの変更頻度が高く、どこが本質的な価値を提供するのでしょうか?
最も変更頻度が高いのは、一般論としてUIですよね。したがってpresentationレイヤーの変更頻度が高くなります。そのため、上流に位置し、柔軟性が高くなっています。
これは適切な設計と言えるでしょう。
では、アプリケーションの本質的な価値は何でしょうか?
それは、これが開発されることになった背景に立ち戻ればはっきりします。
これら、アプリケーションを開発する目的そのものが、このアプリケーションの本質的な価値となるでしょう。
そしてその価値を提供しているのはusecaseレイヤーです。
ビジネスロジックやドメインと言い換えても良いと思います。
したがってusecaseレイヤーは、ほかの枝葉の変更に引きずられてはならず、高い安定度となるように設計されなくてはなりません。
しかし、そうなっていない点に、このモデルは問題点があります。
依存性は、より上位レベルの方針にのみ向けよ
依存性は、より本質的な価値を提供する部分。
つまり、より上位レベルの方針に向ける必要があるのです。
具体的には、依存関係をこのようになるよう設計する必要があります。
でもそれは実現可能なのでしょうか?
制御の向きはどうしても上から下にならざるを得ないなか、それと反する依存方向に設計する必要があります。
つまり、制御の流れと依存関係を分離する必要があるのです。
制御の流れと依存関係の分離
一般的に依存方向は制御の流れに引っ張られがちです。
しかし、これらは分離してコントロールすることが可能です。
そのためには二つの依存関係の間のコントラクト、つまり契約・仕様・お約束を文脈によって制御する必要があります。
まず、抽象的なコントラクトを導出することで、それぞれの具象概念をどちらも抽象概念であるコントラクトに依存させます。
そしてこのコントラクトを、どちらの文脈で定義するかによって具象概念間の依存関係をコントロールします。
具体的には、コントラクトをサーバー側の文脈で規定したとします。
すると、文脈単位でみると、クライアントがサーバーに依存している構造になりますし
逆にクライアントの文脈で規定すれば、サーバーがクライアントへ依存している構造となります。
つまり依存性逆転の原則を適用するということです。
具体例に戻りましょう。
全てを見ると、ちょっと分かりにくいので今回はusecaseとapiの関係に着目してみます。
これらのクラスのうち、ここの依存関係にフォーカスします。
具体的にはusecaseがWebサービスを利用するための呼び出しです。
ここを拡大してみると、usecaseがapiコンポーネントのインターフェースと、そのシグニチャに登場するクラスに依存しているのが見て取れます。
これら、APIのインターフェースと、そのシグニチャに登場するクラス群がレイヤーをつなぐコントラクトです。
この構造には二つの問題があります。
ひとつ コントラクトがapi側に定義されている
ひとつめは、コントラクトがapi側に定義されていることです。
まずはこれらをusecase側に移動します。
でも、これだけでは不十分です。
ふたつめ コントラクトがWeb APIの文脈で記述されている
二つ目の問題点は、このコントラクトがWebサービスの文脈で記述されているということです。
ホットペッパーのAPIを呼び出すと、このようなJSONが返却されます。
GourmetSearchApiの戻り値となるクラスは、このJSONの形状のままです。
実際の実装コードを見てみましょう。
Usecaseの関数内で、APIの戻り値を、usecaseのオブジェクトに詰めなおしています。
つまりusecaseがWebサービスに依存してしまっているのです。
ではどうすれば、よいでしょうか?
現在、usecaseとinfrastructureの間のコントラクトは、Infrastructureの文脈で記述されています。
このコントラクトをusecaseの文脈で再定義します。
つまり、ここの依存関係を断ち切ります。
そのためには、JSON依存のオブジェクトをinfrastructureへ隠蔽し、GourmetSearchApiのインターフェースはusecaseの文脈で記述しなおします。
こうします。
少し見にくいですが、GourmetSearchApiの戻り値がGourmetSearchResultからRestaurantに変更されているのが見て取れるかと思います。
そしてJSONオブジェクトからusecaseのオブジェクトへの詰め替えはInfrastructureに実装します。
こうすることで、論理的な依存の方向が下から上に変化します
その結果、制御の流れと依存関係を分離することができ、柔軟性と安定度を任意のバランスにコントロールすることができました。
そして、これを同心円状に並べなおすと・・・
あら不思議。
どこかで見たようなモデルになりましたね。
というわけで、依存性は上位レベルの方針のみに向ける必要があり、そのためには、制御の流れと依存方向を分離、コントロールしなくてはならないということに、共感いただけたのではないかと思います。
視点を上げてみよう
さて、usecaseからAPIへの依存は取り除けました。
ここで視点をアプリケーションとWebサービスの関係まで上げてみましょう。
UsecaseからWebサービスへの依存は取り除けましたが、よく見ると、そもそもアプリケーション全体がWebサービスに依存してしまっています。
これは良いのでしょうか?
結論から言うと
このアプリケーションは、田中さんが愛してやまないホットペッパーを、より便利に利用するために作ったものです。
つまり、ホットペッパーに依存すること自体が最上位レベルの方針なのです。
今回の場合、アプリケーションはWebサービスのインターフェースです。
本質的な価値つまり上位レベルの方針は、Webサービス側にあります。
つまり、アプリケーションはWebサービスに依存して良い。結果、apiコンポーネントもWebサービスに依存して良いことになります。
もちろんこのことは、アプリケーション側に腐敗防止層を設けることを否定するものではありません。
もう一度、先ほどのアプリケーション構造のモデルに戻りましょう。
ここから視点を上げると
ソリューションの構造としては、このように図示することができますよね。
ちなみにこの関係が、常に正しいという訳ではありません。
適切な関係性は背景によって変化します。
今回は、ホットペッパーを便利に利用するアプリケーションが欲しい!
という背景から端を発したので、アプリケーションが外部のWebサービスに依存して問題がありませんが
旅行サイトの横断検索サービスのようにいろんな飲食店検索サービスを横断検索したい!という背景にが起点となっているなら、このように、飲食店横断検索アプリが、中央に来ないといけません。
これまで存在しなかったような、新しい飲食店情報サービスを始めたい!しかもWebも含めたマルチプラットフォームでというのであれば、他社サービスに依存してはいけませんし、中央には飲食店情報サービスがきて、各プラットフォームのアプリケーションが周辺に来るはずです
でも、どの例の、どのプラットフォームでもアプリケーションは
同じような構造になる可能性が高いでよね?
そうです。
「上位レベル」というのは相対的かつ再帰的であり、それに留意する必要があるということです。
そもそもソフトウェアは良くフラクタルのようだと言われていますよね。
クラスとクラスがまとまってコンポーネントとなり、コンポーネントとコンポーネントがまとまってサブシステムになります。
そしてサブシステムとサブシステムがまとまってシステムになります。
ここではクラス・コンポーネント・サブシステム・システムを、ソフトウェア オブジェクトと呼ぶことにします。
ソフトウェアオブジェクトのすべてにおいて、ここまでした話の内容は適用できます。
もちろんサブシステム間は例えばWeb APIになったりするでしょうけど、どの要素間であっても、コントラクトの文脈を管理する事で、制御の流れと依存関係は制御することが可能です。
こちらに各ソフトウェアオブジェクトごとの代表的なコントラクトを上げてみました。
なんならシステム間でのやり取りがCSVとか固定長テキストであっても、そのファイルがどっちのシステムの文脈によってるかによって、システム間の依存関係はコントロールできるわけです。
「アーキテクチャのルールはどれも同じである」
という真意が何となく伝わってきたのではないでしょうか?
What is Software Architecture?
さて、ここまではどちらかというと、ボトムアップ的なアプローチで説明してきました。
ここからは少し、トップダウン的なアプローチでお話してみたいと思います。
なおここからはクリーンアーキテクチャの書籍に書かれてる内容だったり世間の共通見解だったりするわけではなく、私個人の私見が多くなります。
私はそう考えてるというお話であることにご注意ください。
さて、そもそもアーキテクチャ、ここではソフトウェアアーキテクチャですね。
ソフトウェアアーキテクチャとは何なんでしょうか?
Webや書籍上でよく見かける、ソフトウェア アーキテクチャというとたとえばこのあたりが該当するように思います。
これらは類型的なソフトウェアアーキテクチャのパターン群です。
ただし特定領域のパターンであって、ソフトウェアアーキテクチャの全てではありません。
では、ソフトウェアアーキテクチャとは何か?ドン!
うん、良く分からん。
別の意見を見てみましょう。
多分、世界的なアーキテクトの第一人者を10人上げろと言われると、多くの方がそのうちの一人として、マーティン ファウラー氏をあげるのではないかと思います。
彼はこう言っています。
ふむ。
ちなみにWikipediaには、こんなことが書かれています。
そう、厳密で共通認識の定義は存在しないんですね。
みんな気軽にアーキテクチャ・アーキテクトと言いますが定義はあいまいなんです。
とは言え、おおよその共通認識はもちろんあります。
まず、ソフトウェアアーキテクチャとはシステムアーキテクチャのうち、ソフトウェア領域のアーキテクチャを指します。
そしてソフトウェアアーキテクチャとは、ソフトウェア構築における重要な決定事項の全てを指します。 その中でもどう分割し、どう結合・相互作用するかが、特に重要です。
またソフトウェアアーキテクチャとを構築すする目的とは
- システムの実現をサポートする
- 持続可能なソフトウェアを
- バランス良く構築する為
だと、私は考えています。
最後のバランスとは、QCDだと私は考えています。 特にアーキテクチャは非機能要求からも、おおきな影響をうける傾向が強いと言われています。
Clean Architectureを考えるうえで重要なポイント
さて、アーキテクチャが仮に先のようなものだとしたとき、Clean Architectureを考える上で重要なポイントがあります。
それはこのどう分割・結合し、どう相互作用させるか?
という点です。
そして、これらを実現するために最も重要となる、ソフトウェアアーキテクチャ3種の神器があります。
それが
関心の分離、疎結合、依存性逆転の原則だと、私は考えています。
整理してみましょう。
なんらかの大きな目的を表す上位の関心があったとします。
この時、現代のソフトウェア開発の現場では、これをそのまま、モノリシックなソフトウェアとして開発したりしませんよね?
まずは粒度の小さい、個別の関心に分離します。
これが関心の分離です。
このとき関心が何か?は、このとき皆さんに見えているスコープによって異なります。
上位の関心がソリューションであれば、サブシステムかもしれませんし、上位の関心がコンポーネントであれば、クラスかもしれません。
ただ実際にはこんな単純に分離できない事もあると思っていて、例えばソリューションはユースケースに分離されるかもしれません。
これはどちらが正しいという訳ではなくて、サブシステムとビジネスユースケースはお互いに影響を与え、双方が矛盾しないよう並行で決定する必要があるのかもしれません。
ちょっとこの辺は、私の中でも完全には消化できていないとこがあります。
さて、分離した関心は、それ単独では上位の関心を満たせません。
そのため、関心は再度結合する必要があります。そこで重要なのが疎結合ですよね。
せっかく関心を分離しても、密結合してしまってはありがたみが半減してしまいます。
ただ、本当の意味での疎結合を実現するためにはもう一つの概念が必要です。
そう。依存性逆転の原則です。
ここまでお付き合いいただいた皆さんは、もうご理解されていることでしょう。
結合は疎であるだけではなく、その結合時の依存関係を適切にコントロールすることで、より効果が高まります。
そのためには、制御の流れにとらわれず、依存方向を分離し、設計する必要があります。
そうすることで、上位の安定度と、下位の柔軟性を高めます。
What is Clean Architecture?
ではクリーンアーキテクチャとはなんでしょう?
私は次のように解釈しています。
クリーンアーキテクチャというのは、ソフトウェアアーキテクチャの3種の神器を適用した、リファレンスアーキテクチャのひとつだと私は解釈しています。
Clean Architectureって不可能では?
さて、最後に、ネット上などでよく見かけるClean Architectureに対する疑問について お話したいと思います。
書籍には次のような記述があります。
要約すると、システムは外部のフレームワークなどに依存せず、サークルの外側に配置しよう。
フレームワークやデータベースなどの開発環境やツールの意思決定を遅らせられるよう、それらに依存しないように設計しよう。
という内容です。
しかし、現実的に例えばUIフレームワークやライブラリに非依存に設計することは無理もしくは無意味なんじゃないか?
データベースに依存せずGatewayを実装するなんて可能なのか?
という意見を見かけます。
例えば今回のサンプルアプリケーションではviewはGoogleが提供しているCardViewに依存しています。
これはNGなのでしょうか?
私は一定条件を満たせば問題ないと考えています。
具体的には、次のような「上位レベルの決断」の上であれば問題ないと考えています。
そもそも自作するよりサードパーティーライブラリを利用したほうが生産性も品質も高くなると判断でき、そのライブラリの変更が緩やかで、十分に後方互換が考慮されていて、そのライブラリの変化をアプリケーション側で十分に許容できると判断しているような場合に、Presentation内に限定して利用を許可する。
といった場合です。
そもそもAndroidアプリケーションを開発しようという際に、AndroidというプラットフォームやKotlinというプログラミング言語、Android StudioやGradleのような開発環境に依存しないというのはナンセンスですよね。
アーキテクチャや設計をどこまで抽象化するか、どこから具象化するかは程度の問題だと思います。
フレームワークなどの決定を遅らせることは、アプリケーション開発に自由度をもたらしますが、同時にイニシャルコストの増加や開発期間の延長を招きがちです。
早い段階で、特定の具体的なフレームワークなどを受け入れる決定をすることで、コストや開発期間の、圧縮や、品質の向上を図ることも当然検討すべきです。
そしてこれら自身を、アーキテクチャ上の「上位の方針」として受容するのは十分にありだと考えています。
Clean Architecture原理主義になる必要もありませんが、同時にAnti Clean Architectureになる必要もないのではないでしょうか?
まとめ
とうわけで、今日のお話は終わりになります。
最後に簡単にまとめましょう。
クリーンアーキテクチャは、どんなソフトウェアにも適用可能な普遍的なアーキテクチャです
クリーンアーキテクチャは3種の神器を適用した、リファレンスアーキテクチャのひとつです。
ソフトウェアアーキテクチャ3種の神器とは、関心の分離・疎結合・依存性逆転の原則だと、私は考えています。
クリーンアーキテクチャの本当に大切なことが三つあると私は考えています。
ひとつは、依存性はより上位レベルの方針、つまりオニオンアーキテクチャであれば外から中にだけ依存するべきであること
ふたつめは、制御の流れと依存方向は分離してコントロールしなさいということ。
そして最後に、上位レベルというのは、相対的・再帰的なものであるということです。
参考の図のとおりに関心を分離することや、データや処理の流れを一方通行にせよ、ということがクリーンアーキテクチャではありません。
と、著者がなんと言おうと、この解釈だからこそクリーンアーキテクチャには価値がありますし
この書籍は、そのための手段を広く紹介している、名著だと私は信じています。
というわけで
みなさん
「クリーンアーキテクチャチョットデキル」
ようになりましたね?