# KT#0012-kanda クリーンアーキテクチャ ###### tags: `アーキテクチャ` ## クリーンアーキテクチャ ### ってなに? システムとかアプリケーションのアーキテクチャのひとつ。ドメイン駆動設計カテゴリに属する。 変更やテストにひたすら強くなるために考えられている。 Robert C. Martinとかいうおっさんがちょっと前に提唱しました。 完全に理解したければ以下の文献を読みましょう。僕も読みましたが、読了後は完全に理解した気分になれます。 https://www.amazon.co.jp/dp/B07FSBHS2V/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1 ~~ちなみに本当に完全に理解したければ下の記事を読めばいいです。~~ https://gist.github.com/mpppk/609d592f25cab9312654b39f1b357c60 ### もうちょい詳しく みなさん、「アーキテクチャ」ってなんのために考えるか知っていますか? *※ここでいうアーキテクチャとは、システムの組み合わせの話ではなく、もう少し下位レイヤーのコーディングレベルの話をしています。* ただ動くものを作るだけならひたすら思いつくままコードを書けばそれなりのものは動きます(文献でも「世の中にはそういうアプリケーションがたくさんある」と言われている)。 ただ、想像に難くないように、そのままだと以下のような問題が考えられます。 - もっとこうしてほしいやにぃ〜という要望の変更に即座に対応できない。どこ変えたらええねん。 - とりあえず変えてみたわ。そしたら他のところ動かんくなったわ。 - そもそも変えてみたところすら動くかどうかテストできへん。 - DBをOracleからMySQLに変えたわ。SQL仕様微妙に変わって草。 - 前任者のコード読めなさすぎワロタ つまるところ、これらは開発の **「変更」と「テスト」** という領域に深く影響を与えます。 これらの問題点を未然に防ぎハッピーな開発を行うために誕生したのが「クリーンアーキテクチャ」です。 (筆者のおっさん曰く)アーキテクチャとは **、「利用者」のためではなく「開発者」のため** に考えられるものです。 ### 関心の分離 クリーンアーキテクチャが登場するまでの、「ヘキサゴナルアーキテクチャ」だの、「オニオンアーキテクチャ」だの、「●●アーキテクチャ」がいろいろありました。 これらは名前は違えど、結局の所、「システムをレイヤー構造にし、各レイヤーの責務を明確にする」というアイデアに基づいていました。 このページの最初で、「ドメイン駆動設計」という言葉が出てきましたが、これは「業務領域(=ドメイン)をちゃんとモデルに抽象的に落とし込んで、そのモデルに忠実に実装しような」っていう思想です(わからん)。 ということは、システム化したい業務の核となるロジックがあるはずです。例えば保険や融資であれば「申込」とか「審査」とかがそういうロジックにあたります。これらはまさしく業務内容そのものです。 一方で、そのシステムをどんな画面から操作するかとか、どんな形式で情報をやり取りするかなんてものは業務内容とは関係ありません。あくまでシステムとユーザの間に存在するインターフェースの問題でしかありません。 他にも、データをどこに保存するのかなんて話もあります。こういうことを考えていくと、それぞれの「関心事」に応じてレイヤーを分けて設計したほうがなんとなく良さそうに見えます。これを「関心の分離」といいます。 上の例でいえば、 - 業務ロジック - ユーザとのインターフェース - DBや外部サービスとの接続 などは別レイヤーに分けられるでしょう。 関心の分離をすると具体的にどのように嬉しいのでしょうか。文献では以下のようにまとめられています。 - **フレームワーク独立**。アーキテクチャは、機能満載のソフトウェアのライブラリが手に入ることには依存しない。これは、そういったフレームワークを道具として使うことを可能にし、システムをフレームワークの限定された制約に押し込めなければならないようなことにはさせない。 - **テスト可能**。ビジネスルールは、UI、データベース、ウェブサーバー、その他外部の要素なしにテストできる。 - **UI独立**。UIは、容易に変更できる。システムの残りの部分を変更する必要はない。たとえば、ウェブUIは、ビジネスルールの変更なしに、コンソールUIと置き換えられる。 - **データベース独立**。OracleあるいはSQL Serverを、Mongo, BigTable, CoucheDBあるいは他のものと交換することができる。ビジネスルールは、データベースに拘束されない。 - **外部機能独立**。実際のところ、ビジネスルールは、単に外側についてなにも知らない。 よくわかりませんが、「独立」という言葉がキーワードのように見えます。レイヤーをいい感じに独立させれば、いい感じにテストできたり開発できたりしそうです(語彙力)。 ### 依存方向 クリーンアーキテクチャでは、独立性を表現するのに「依存」という言葉を使います。 先程「独立」という言葉が独り歩きしましたが、各レイヤーが完全にお互いに独立することはないでしょう。例えば、「申込」という業務に必要な情報をDBから取り出す場合、どんなデータを扱うべきかは「申込」に**依存**します。「申込」が複雑に変化すれば、DBとのやり取りもそれに応じてややこしくなるでしょう。 ただし、逆に言えば**レイヤー間の依存方向を正しく管理すれば、その変化・変更への対応は容易**です。というか、それが自然です。先程の例では、「申込」の変化にDB接続が影響を受けることはあっても、DB接続の変化に合わせて「申込」が影響を受けることはありえません。 何故か?「申込」は業務のコアロジック、すなわち「**ドメイン**」だからです。 クリーンアーキテクチャでは、業務領域を表現するドメインを中心とし、各レイヤーの依存方向を厳密に定義することにより、変更やテストに強いアーキテクチャを実現します(はず)。 ### というわけでこの絵を見てくれ  すごく・・・わかりにくいです・・・。 クリーンアーキテクチャで画像検索するとだいたいこの絵が出てきますが、この絵で表現したいことを理解すると明日からクリーンアーキテクチャをドヤ顔で語れます。 ここでは4つのレイヤーが登場します。 - Enterprise Business Rules(EBR) - Application Business Rules(ABR) - Interface Adapters(IA) - Frameworks & Drivers(F&D) 円の名前=各レイヤーの名前であり、円の中に書かれているのは各レイヤーに含まれるコンポーネントの例です。 大事なのは矢印の向きです。**外側の円は内側の円に依存し、内側の円は決して外側の円に依存しません。** 順番にサクッと説明します。 ### Enterprise Business Rules(EBR) ビジネスロジックやモデルそのものを記述します。さっきの例だと「申込」とか「審査」などのロジックや、「契約者」といったモデルも含めます。これらはドメイン駆動設計では「エンティティ」と呼ばれます。 業務の核となる部分なので、めったに変更されません(そのはず)。 そのため、円の中心に位置し、他の円から一方的に依存されます。 ### Application Business Rules(ABR) EBR層の中にあるエンティティを操作します。例えば、「審査」ロジックを呼び出してその結果に応じて「契約」ロジックを実行し、そのあと「契約者」モデルのステータスを変更する・・・という具合です。 このような一連の処理フローは、クリーンアーキテクチャでは「ユースケース」と呼ばれます。 ABR層ではこのユースケースを定義するのが主な役割です。 ### Interface Adapters(IA) ABR層と、最外殻のF&D層とをつなげる役割を果たします。例えばABR層からDBに接続したい場合、ABR層はそれがMySQLなのかOracleなのか、はたまたDynamoDBなのかは意識しません。大事なのは、「データを保存すれば永続化される」ということが確かであることであり、その実現方法はなんでもいいのです。しかし、実際にDBに接続する場合は、その接続先に合わせた仕様にする必要があります。このように、呼び出す側は抽象的に「データの永続化」を望むのに対し、呼び出される側は具体的な「実装」を伴っています。この仲介を担うのがIA層です。 これは入力にも同じことがいえます。ABR層にデータが渡されるとき、それが画面から送信されたデータなのか、REST通信なのか、SOAP通信なのかはABR層にとって関係ありません。これらはコントローラー(MVCでお馴染みですね)で吸収され、ABR層には必要なデータのみが送られます。 具体的にABR層がデータを取得したいときを考えます。このとき、ABR層はIA層の「データ取得」処理を呼び出します。 IA層では、SQL文を用いた処理を記述し、F&D層のSQLハンドラを呼び出します。 F&D層のSQLハンドラは受け取ったSQL文を、接続先に応じて適切に実行します。 ### Frameworks & Drivers(F&D) 外部サービスなどに接続する実装を担います。 ゴリゴリに具体的な実装が行われます。 ### あれ?ちょっとまって? IA層の説明を読んで「あれ?」と思った人もいるのではないでしょうか。 実はこのまま実装すると「依存方向」がおかしなことになります。 そもそも「依存方向」ってどのように決まるのでしょうか。 あるコンポーネントAがコンポーネントBのメソッドを呼び出すとします。 もしコンポーネントAに何かしら変更が起こったとしても、呼び出されるコンポーネントBには何も影響がありません。コンポーネントBは呼び出されたときに粛々と決まっている処理を行うだけです。 ところが、コンポーネントBに変更が発生したときは違います。それを呼び出すコンポーネントA側にも影響が波及します。 このように、コンポーネントBの変更が呼び出し側のコンポーネントAに波及するとき、「コンポーネントAはコンポーネントBに依存している」といいます。 となると、先程のIA層の説明では矛盾が生じます。 明らかに「ABR層がIA層のメソッドを呼び出し」、さらに「IA層がF&D層のメソッドを呼び出し」ています。これでは内側の円が外側の円に依存してしまっています。 クリーンアーキテクチャでは、これは「依存性の逆転」という手法で解決します。 ### 依存性の逆転 みなさん、「インターフェース」という言葉を知っていますか? 先程とは意味が違い、プログラミング用語としての「インターフェース」です。 あるクラスの「ふるまい」のみを定義したものを意味しますが、これを利用します。 つまり、「**呼び出し側レイヤーでインターフェースを定義し、呼び出される側レイヤーでそれを実装**」します。 すると、呼び出される側の実装が呼び出す側の定義に従う必要がある、すなわち依存する必要があるので、ちゃんと外側の円が内側の円に依存するようになりました。なんということでしょう。 先程の例では、ABR層で、「実装はともかく、これを呼べばDBに保存してくれるであろうクラス」のインターフェース①を定義します。 IA層で、「実装はともかく、これを呼べばSQLを実行してくれるであろうクラス」のインターフェース②を定義します。さらに、インターフェース①を実装し、「これを呼べばDBに保存してくれるクラス」を作ります。このクラスからは、インターフェース②を参照しています。 F&D層で、インターフェース②を実装します。 これにより、呼び出す方向はあくまでABR層→IA層→F&D層ですが、依存方向はF&D層→IA層→ABR層となります。**ね?簡単でしょ?** ### まとめ これでも元文献の1割ぐらいしか伝えられていませんが、個人的には、円(レイヤー)の依存方向と、それを保つための仕組み(依存性の逆転)が大事な部分だと思います。 システム化対象の業務を理解し、コアになる部分をエンティティとして円の中心に置きます。 それらエンティティを操作できるレイヤーを外側に配置し、アダプターとなる層を挟んで外部接続用レイヤーで包みます。 各レイヤーは内側に向かって依存するので、内側の円のテストはそれより内側で完結しますし、モックなどの差し替えも容易です。もし内側のレイヤーから外側のレイヤーのメソッドを呼び出すときには「依存性の逆転」を利用します。 つまるところ、**このアーキテクチャの目的は最初にも行ったとおり「変更」と「テスト」に強くなること**です。 最も変更が多いのは外部要因によるものでしょう。すなわち最外殻のF&Dレイヤーです。しかし**外側のレイヤーは何からも依存されない**ので、変更の影響は最小限で済みます。 また、テストは内側のレイヤーから順番にやっていけばよいでしょう。もし外部接続の部分がまだ実装できていないとしても、内側で定義されたインターフェースを実装したモックに差し替えればいいだけです。あるレイヤーのテスト時には、それより内側のレイヤーが正常に動作することが保証されます。 ### で、このアーキテクチャどうなん? 言うは易く行うは難しです。 そもそも「これはちゃんとクリーンアーキテクチャに則っていますね」って判断できる人が皆無なので、徹底することは難しいでしょう。 しかし、厳密に則らずとも、アイデアを借りてなんちゃってクリーンアーキテクチャとしてでもそれなりの開発の効率性向上には寄与すると思います。実際に自分が関わっているプロジェクトでは少なからずクリーンアーキテクチャの影響を受けているので。 たぶん、こういう●●アーキテクチャはこれからもいろいろ出続けるんでしょうけど、どれも似通った部分はあると思うので、クリーンアーキテクチャをしっかり覚えておけば、学びやすいんではないでしょうか。
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up