nuits.jp blog

C#, Xamarin, WPFを中心に書いています。Microsoft MVP for Development Technologies。

世界一わかりやすいClean Architecture

本稿は「Visual Studio Users Community Japan #1」で登壇させていただいた内容の解説記事になります。

vsuc.connpass.com

本稿のスライドはこちら。

www.slideshare.net

本稿のサンプルコードはこちらで公開しています。

github.com

Clean Architectureの次の点についてお話させていただきます。

  • 誤解されがちな二つのこと
  • おさえるべき二つのこと

本項はあくまで私の解釈です。

特に極端に要約しているので、本稿がすべてではないことはご理解ください。また、書籍の記載とやや異なる説明もします。

それでも私は著者が本当に言いたかったこと、クリーンアーキテクチャでもっとも大切なことは今日お話しする二点にあると考えています。

なお異論・反論は大歓迎です。ぜひ議論させていただけると嬉しいです。

ではまずは誤解されがちなことの二つから始めましょう。

この図をご覧になったことのある方は多いんじゃないかと思います。

f:id:nuitsjp:20190914165543j:plain

出典:The Clean Architecture

https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

実はこの図が良くできてい過ぎることが、大きな誤解を招く要因になっていると私は考えています。

どういうことでしょうか?

ひとつは、この図は決してソフトウェアの関心をControllerやUseCase、Entityに分離しろという意味でもなく、レイヤーをこの4つに分割しろという意味ではないということです。

f:id:nuitsjp:20190914165710j:plain

そしてふたつ目は右下のこれです。

これもPresentationを古典的なMVCのように設計しろという意味ではないですし、データや処理の流れを一方通行にしろと言っている訳でもありません。

f:id:nuitsjp:20190914165805j:plain

では、どういう意味なのでしょうか?

先の図で本当に大切なことは、依存性は外から中だけに向かっていなくてはいけない。

というたった一点にあります。

f:id:nuitsjp:20190914165853j:plain

でもじゃぁ、例えばEntitiesの状態が変わったことで、UIを受動的に変化させたいみたいな中から外を呼ばなければいけないときはどうすれば良いの?という疑問が、すぐに湧いてきますよね?

右下の図は、内側の変化に応じて外側を呼び出したい場合に、依存性は外から中に向けたまま、処理の流れとして中から外に戻すにはどうすればよいのか?

を例示しているものです。

f:id:nuitsjp:20190914165947j:plain

さて、この解釈は正しいでしょうか?もちろん根拠があっての話です。

そもそも著者は書籍の先頭でこう明言しています。

  • The architecture rules are the same! - (邦訳:アーキテクチャのルールはどれも同じである!)

日本語版では帯にも書かれていますよね。

つまり、本当にすべてのソフトウェアが同一のアーキテクチャに帰結するとすれば

  • レイヤーが4つ限定であったり
  • UIがMVC的でMVVMなどを否定していたり
  • そもそもWeb限定だったり
  • 中から外の呼び出しがApplicationレイヤーとInterface Adapterレイヤーの間だけで発生するわけがないですし
  • 中央がEntitiesじゃなくちゃだめで、DDDするななんて

そんな分けないですよね?

書籍を読めば、ちゃんと先の図があくまで例示であると書かれています。

もともとクリーンアーキテクチャの発表前は、ヘキサゴナルアーキテクチャとかオニオンアーキテクチャが広く共有されていました。そしてそれらのアーキテクチャには類似の共通点があることも知られていました。

そしてクリーンアーキテクチャの解説には、それらの著名なアーキテクチャは、先ほどの図のように記述することで単一のアイディアに統合することを例示するために書かれた図なんです。

そう

f:id:nuitsjp:20190914170221j:plain

クリーンアーキテクチャというのは、どんなソフトウェアにも適用可能な普遍的なアーキテクチャなんです。

あえて言うと、クリーンアーキテクチャを採用しないという選択肢は、基本的には「あくまで基本的には」無いといって差し支えありません。

では先の図のように実装することがクリーンアーキテクチャではないとしたら、クリーンアーキテクチャについて何を抑えたらよいのでしょうか?

私はつぎのふたつこそ、本質的に重要なことだと考えています。

f:id:nuitsjp:20190914170312j:plain

本稿では、おもに二つ目のお話をします。

そもそも関心をどう分離するかはClean Architectureで定義するものではないので、一つ目はあまり話すこともありません。

てことで、ここからは具体例を見ながら解説したいと思います。

f:id:nuitsjp:20190914170856j:plain

コードは以下に公開しています。

github.com

せっかくですからアーキテクチャはクリーンアーキテクチャのサンプルのものをそのまま利用しましょう。

f:id:nuitsjp:20190914170952j:plain

さて、先ほどのコードをモデル化したのがこちらになります。

全体的にそう悪くないようにも見えるんですが、少なくとも二つは問題があります。ひとつは、ぱっと見から分かりますね。

f:id:nuitsjp:20190914171031j:plain

この部分でUsecaseからPresenterに依存関係ができてしまっています。依存関係はPresentaerからUsecaseである必要があります。

でも逆になっちゃっていますね?ここを次のように修正します。

f:id:nuitsjp:20190914171144j:plain

ちゃんと右下の図の通りになりましたね。処理の流れは一方通行で、でもすべての依存は外から中に向いています。

f:id:nuitsjp:20190914171337j:plain

ただ厳密にいうとまだ中から外へ依存が向かっている箇所があります。どこか?

f:id:nuitsjp:20190914171418j:plain

ここです。Gatewayがデータベースに依存しています。

GatewayはDatabaseに依存するという選択肢はもちろんありだと考えているのですが、まぁ今回はせっかくなので、ここもDBからGatewayに依存するようにしてみたいと思います。

f:id:nuitsjp:20190914171508j:plain

でもGatewayはDBを呼び出さないといけないしDB操作にはSQLが必要ですよね?

GatewayがDBに依存しないなんて実現可能なのでしょうか?

もちろん可能です。

ところでDBをGatewayに依存させるというのは、どういうことでしょう?もちろんSQLを利用しないという意味ではないですし、RDBというアーキテクチャに依存しないという意味ではないです。

システム固有の永続化データのスキーマに依存しないという意味です。*1

この部分を見てください。

f:id:nuitsjp:20190914171748j:plain

Repositoryの実装ではProductテーブルとSalesOrderDetailに対応するクラスを作ってDBアクセスし、Repositoryの中でProductSalesインスタンスを生成していましたよね?

f:id:nuitsjp:20190914171853j:plain

これらのクラスをよく見てみましょう。現在の実装は、リポジトリが完全にデータベースの文脈で記述されているのが見て取れます。

結構な列数がありますよね。

しかし今回は、この内の4項目しか本来は必要ありません。

テーブル構造にダイレクトに依存してしまうと、テーブル変更の影響をダイレクトに受けてしまいます。他の機能の影響で列が増えて、実際にはほぼ影響がないのに回帰テストをしないといけない。

みたいな事に陥ったことはありませんか?

そこでここの依存関係がDBからGatewayに向いていれば、少なくともGatewayには影響がないことが担保できます。*2

では実際にはどうするか?

制御の流れと依存方向は分離することで、コントロールできます。

f:id:nuitsjp:20190914172051j:plain

GatewayがDBへ依存しているというのはつまり、Gatewayのクラス設計がDBのスキーマ・文脈に依存していることを指しているのです。

f:id:nuitsjp:20190914172240j:plain

そうではなく、DBがGatewayの文脈で定義されていればよいという事になります。とは言え、これらのテーブルは別にこの機能のためだけにある訳じゃありませんよね。

ではどうすれば良いのか?

f:id:nuitsjp:20190914172414j:plain

最も一般的な回答は、CQRSを使いましょうという事になるのではないでしょうか?

CQRS :Command-Query Responsibility Segregation

データベースの操作を次のふたつの責務に完全に分離する方法論のことです。

  • データベースを更新するCommand
  • データベースを参照するQuery

今回のケースはQueryに該当するため、Gatewayの文脈でQuery用のDBオブジェクトを用意します。

とはいえ・・・

完ぺきなCQRSを適用するのはなかなかに困難です。場合によってはそもそもオーバースペックでさえあります。

  • CommandとQueryのデータソースの完全分離
  • ドメインイベント
  • 非同期メッセージ
  • イベントソーシング
  • 結果整合性
  • などなど

そこで今日は、データベースのViewを使った軽量な方法論を紹介します。*3

と言うわけで、適用したものがこうなりました。

f:id:nuitsjp:20190914172755j:plain

DBにはEntityのPdoructSalesの文脈に適用したViewを作成して、Repositoryからはそれを参照します。

こうすることで、DBつまり外から中に依存するように文脈が書き換えられました。

このアーキテクチャの再現性を見比べてみてください!

f:id:nuitsjp:20190914172825j:plain

ところで、CQRSに類似した概念にCQSというものがあります。

f:id:nuitsjp:20190914172850j:plain

f:id:nuitsjp:20190914172908j:plain

さてあらためて・・・

f:id:nuitsjp:20190914172939j:plain

一般的に依存方向は制御の流れに引っ張られがちです。

しかし、これらは分離してコントロールすることが可能です。

そのためには二つの依存関係の間のコントラクト、つまり契約・仕様・お約束を文脈によって制御する必要があります。

f:id:nuitsjp:20190914173059j:plain

まずは具象概念間に、抽象的なコントラクトを導出することで、それぞれの具象概念はどちらも、抽象概念であるコントラクトに依存するようにします。*4

そしてこのコントラクトをどちらの文脈で定義するかによって、具象概念間の依存関係を自由にコントロールすることができるようになります。

f:id:nuitsjp:20190914212555j:plain

f:id:nuitsjp:20190914212806j:plain

「アーキテクチャのルールはどれも同じである」 という真意が何となく伝わってきたでしょうか?

そもそもソフトウェアは良くフラクタルのようだと言われていますよね。

f:id:nuitsjp:20190914212848j:plain

クラスとクラスがまとまってコンポーネントとなり、コンポーネントとコンポーネントがまとまってサブシステムになります。そして、サブシステムとサブシステムがまとまってシステムになります。

ここではクラス・コンポーネント・サブシステム・システムを、ソフトウェア エンティティと呼ぶことにします。

ソフトウェア エンティティのすべてにおいて、ここまでした話の内容は適用できます。

もちろんサブシステム間は例えばWeb APIになったりするでしょうけど、どの要素間であっても、コントラクトの文脈を管理する事で、制御の流れと依存関係は制御することが可能です。

こちらに各ソフトウェアエンティティごとの代表的なコントラクトを上げてみました。

f:id:nuitsjp:20190914212943j:plain

なんならシステム間でのやり取りがCSVとか固定長テキストであっても、そのファイルがどっちのシステムの文脈によってるかによって、システム間の依存関係はコントロールできるわけです。

さぁ、クリーンアーキテクチャがあらゆるソフトウェアにおいて

「アーキテクチャのルールはどれも同じである」

ことを受け入れていただけましたか?

とうわけで、本項は終わりになります。最後に簡単にまとめましょう。

f:id:nuitsjp:20190914213109j:plain

以上です。

ご意見、ご感想、意見交換、お待ちしています。配慮のないマサカリ以外は!

*1:SQLやRDB製品はアプリケーションより安定的でしょうから、そちらへの依存は問題ありません。

*2:その影響をこそGatewayで吸収するという決定は全然ありです

*3:必ずしもViewを使えという訳ではありません

*4:依存性逆転の法則