Skip to content

Instantly share code, notes, and snippets.

@mpppk
Last active April 24, 2024 03:05
Show Gist options
  • Save mpppk/609d592f25cab9312654b39f1b357c60 to your computer and use it in GitHub Desktop.
Save mpppk/609d592f25cab9312654b39f1b357c60 to your computer and use it in GitHub Desktop.
クリーンアーキテクチャ完全に理解した

2020/5/31追記: 自分用のメモに書いていたつもりだったのですが、たくさんのスターを頂けてとても嬉しいです。
と同時に、書きかけで中途半端な状態のドキュメントをご覧いただくことになっており、大変心苦しく思っています。

このドキュメントを完成させるために、今後以下のような更新を予定しています。

  • TODO部分を埋める
  • 書籍を基にした理論・原則パートと、実装例パートを分割
    • 現在は4層のレイヤそれぞれごとに原則の確認→実装時の課題リスト→実装例という構成ですが、同じリポジトリへの言及箇所がバラバラになってしまう問題がありました。更新後は、実装時の課題リストを全て洗い出した後にまとめて実装を確認する構成とする予定です。

2021/1/22追記: パートの分割と、クリーンアーキテクチャという概念の定義について追記を行いました。大部分の実装例パートを中心に、引き続きTODO部分が残っています。


クリーンアーキテクチャはRobert C. Martin(Uncle Bob)が2012年に提唱した、DBやフレークワークからの独立性を確保するためのアーキテクチャであり、以下の図が大変有名です。

CleanArchitecture.jpg

最初にクリーンアーキテクチャについて言及されたブログが書かれてからは既に7年が経過しており、書籍Clean Architecture 達人に学ぶソフトウェアの構造と設計をはじめ、日本語での情報も十分存在します。 しかし私が学び始めた時、以下の理由で理解が難しいと感じました。

  • DDDの概念や用語を流用しているが、レイヤーに関する考え方はDDDとは異なる
  • クリーンアーキテクチャの図で使われている用語と実際のパッケージ名に使われる用語が異なる
  • 人によって微妙に解釈が異なる

そこで、Clean Architectureを調べていて出てくる用語それぞれについて、(筆者が勝手に独自の解釈で解説するのではなく、)複数のソースによる解説を比較しながら共通部分や差異を抽出することにより、客観的な理解を深めることを試みます。 用語の定義は、以下の書籍やブログを参照します。

その他、具体的な実装については以下を参考にさせていただきました。

加えて、実際のコードがどのようなものになるかを把握するために、GitHubなどで公開されているリポジトリをできるだけ幅広く参照し、一般的な実装がどのようなものかについても確認します。具体的には以下のリポジトリについて実装を比較します。(注:現時点では実装比較部分の執筆はまだ終わっていません)

前提

クリーンアーキテクチャという概念をどう捉えるかについては、大きく分けて二つの意見があります。 一つは、前述の図に登場する用語や概念を用いて、4層レイヤーから成るAPアーキテクチャを指す場合です。ほとんどのシチュエーションではこちらの意味で使われます。 一方で、クリーンアーキテクチャで重要とされる「依存性のルール」、「関心の分離」、「依存関係逆転の法則(DIP)」などに着目し、図には拘らない方が良いという意見もあります。実際に提案者は、図は概要であり、クリーンアーキテクチャは4層以上にもなりうると述べています。

図の円は概要を示したものである。したがって、この4つ以外は認めないというルールはない。

-- Robert C.Martin,角 征典,高木 正弘. Clean Architecture 達人に学ぶソフトウェアの構造と設計 (Japanese Edition) (Kindle の位置No.3161-3165). Kindle 版.

この記事では前者の定義を採用します。すなわち、これ以降クリーンアーキテクチャという単語を用いた場合、4層レイヤーからなる図で表されるアーキテクチャを指すものとします。(これは、前者の定義を採用すべきであると筆者が判断/主張しているわけではありません。)

この記事のゴール

  • クリーンアーキテクチャで用いられる用語について、各書籍でどのように定義されているかを簡単に確認できるようにする
  • DDDとの用語の関係を明らかにする
  • クリーンアーキテクチャにおけるパッケージ戦略と具体的な実装について言及した記事やサンプルへのリファレンスをまとめる

事前知識

Domain Driven Design(ドメイン駆動設計)

クリーンアーキテクチャについて説明する前に、先にDomain Driven Designという設計手法について簡単に紹介します。 DDDとは何でしょうか。エリック・エヴァンスのドメイン駆動設計 (牧野祐子 今関剛 和智右桂 Eric Evans)には以下のように書かれています。

モデル駆動設計は、分析モデルと設計という二分法を捨て去り、両方の目的に使える単一のモデルを探し出す。 -- Eric Evans. エリック・エヴァンスのドメイン駆動設計 (Japanese Edition) (Kindle の位置No.1582-1583). Kindle 版.

私はこれを最初にこれを読んだとき、さっぱり理解できませんでしたが、現時点では次のように理解しています。

通常、ある業務をシステムに落とし込む時、「現実の業務を適切に抽象化した分析モデルの作成」、「分析モデルを基にした設計」という2回の射影が行われます。 具体的には、前者はユーザヒアリングによる要件抽出、後者は要件を基にした仕様書作成/実装となるでしょうか。 しかし多くの場合、実装上考慮せざるを得ない概念(永続化など)により、分析モデルには存在しない新たな抽象化が設計では必要になります。これにより分析モデルと設計はどんどん乖離していき、どれだけ緻密に分析モデルを作成しても、それが設計に反映されているという保証は無くなってしまいます。 (これらは筆者による解釈なので、正確な理解のためにはエリック・エヴァンスのドメイン駆動設計 (牧野祐子 今関剛 和智右桂 Eric Evans)を参照してください)

これらを踏まえ、エリック・エヴァンスは以下のように主張しています。

設計と、それにあいまいにしか対応していないモデルとの間の紐づけを維持することは、費用対効果が低いのだ。 (中略) 設計が、あるいは設計の中心となる部分が、ドメインモデルに紐づいていないならば、そのモデルにほとんど価値はなく、ソフトウェアが正確であるかどうかも疑わしい。同じように、モデルと設計された機能との紐づけが複雑だと理解するのが難しく、実際には、設計が変更された時に紐づけを維持できなくなる。分析と設計の間に致命的な亀裂が生じるため、それぞれの作業で得られる洞察は互いに活かされることがないのだ。 --Eric Evans. エリック・エヴァンスのドメイン駆動設計 (Japanese Edition) (Kindle の位置No.1564-1565). Kindle 版. `

そこで、分析モデルと設計を一致させることでこの問題を解決しようというアプローチがDDDです。

ソフトウェアシステムの一部を設計する際には、紐づけが明らかになるように、ドメインモデルを文字通りの意味で忠実に反映させること。モデルについて再検討し、より自然にソフトウェアに実装されるように修正すること。 (中略) 設計で使用する用語法と責務の基礎的な割り当てをモデルから引き出すこと。コードはモデルの表現となるから、コードに対する変更はモデルに対する変更になるかもしれない。その影響は、プロジェクトの他の活動全体へと適宜伝わっていかなければならない。 -- Eric Evans. エリック・エヴァンスのドメイン駆動設計 (Japanese Edition) (Kindle の位置No.1596-1598). Kindle 版.

レイヤードアーキテクチャ

レイヤードアーキテクチャはDDD本で言及されている、ドメインを隔離するためのアプリケーションアーキテクチャです。前節で述べたような、ドメインモデルが設計に忠実に反映されているかを確認するには、ソフトウェアを構成する要素のうち、ドメインを表現する責務を持つ部分がどこかを明確にしなければなりません。 そこで、レイヤードアーキテクチャでは以下の4層で責務を分離しています。

層の名前 責務
ユーザインターフェース/プレゼンテーション ユーザに情報を表示して、ユーザのコマンドを解釈する責務を負う。外部アクタは人間のユーザではなく、別のコンピュータシステムのこともある。
アプリケーション ソフトウェアが行うことになっている仕事を定義し、表現力豊かなドメインオブジェクトが問題を解決するように導く。(中略) ビジネスルールや知識を含まず、やるべき作業を調整するだけで、実際の処理は、ドメインオブジェクトによって直下のレイヤで実行される共同作業に委譲する。 (後略)
ドメイン ビジネスの概念と、ビジネスが置かれた状況に関する情報、およびビジネスルールを表す責務を負う。ビジネスの状況を反映する状態はここで制御され使用されるが、それを格納するという技術的な詳細は、インフラストラクチャに委譲される。この層がビジネスソフトウェアの核心である。
インフラストラクチャ 上位のレイヤを支える一般的な技術的機能を提供する。これには、アプリケーションのためのメッセージ送信、ドメインのための永続化、ユーザインタフェースのためのウィジェット描画などがある。インフラストラクチャ層は、ここで示す4層間における相互作用のパターンも、アーキテクチャフレームワークを通じてサポートすることがある。

-- Eric Evans. エリック・エヴァンスのドメイン駆動設計 (Japanese Edition) (Kindle の位置No.1941-1956). Kindle 版.

クリーンアーキテクチャ概要

前節のレイヤードアーキテクチャが提案されて以降も、ヘキサゴナルアーキテクチャ、オニオンアーキテクチャなどが提案されてきました。これらのアーキテクチャで用いられる用語は異なりますが、目指すところは同じであるとクリーンアーキテクチャ(The Clean Architecture翻訳)では述べられています。

これらのアーキテクチャはどれも細部は異なるけれども、とてもよく似ている。これらはいずれも同じ目的を持っている。関心の分離だ。これらはいずれも、ソフトウェアをレイヤーに分けることによって、関心の分離を達成する。どれも、最低ひとつは、ビジネスルールのためのレイヤーと、インターフェイスのためのレイヤーがある。 -- クリーンアーキテクチャ(The Clean Architecture翻訳)

クリーンアーキテクチャは、これらのアーキテクチャを単一の概念に無理なく統合する試みです。

(ヘキサゴナルアーキテクチャ、オニオンアーキテクチャ、クリーンアーキテクチャが本質的に同じであることの解説はドメイン駆動設計で実装を始めるのに一番とっつきやすいアーキテクチャは何か(DDD) - little hands' labが分かりやすかったです。)

関心の分離により、以下が実現できます。

  1. フレームワーク独立。アーキテクチャは、機能満載のソフトウェアのライブラリが手に入ることには依存しない。これは、そういったフレームワークを道具として使うことを可能にし、システムをフレームワークの限定された制約に押し込めなければならないようなことにはさせない。
  2. テスト可能。ビジネスルールは、UI、データベース、ウェブサーバー、その他外部の要素なしにテストできる。
  3. UI独立。UIは、容易に変更できる。システムの残りの部分を変更する必要はない。たとえば、ウェブUIは、ビジネスルールの変更なしに、コンソールUIと置き換えられる。
  4. データベース独立。OracleあるいはSQL Serverを、Mongo, BigTable, CoucheDBあるいは他のものと交換することができる。ビジネスルールは、データベースに拘束されない。
  5. 外部機能独立。実際のところ、ビジネスルールは、単に外側についてなにも知らない。 -- クリーンアーキテクチャ(The Clean Architecture翻訳)

そして、この関心の分離を実現するために重要な概念が、各レイヤーの依存ルールです。

このアーキテクチャを機能させる重要なルールが、依存ルールだ。このルールにおいては、ソースコードは、内側に向かってのみ依存することができる。内側の円は、外側の円についてなにも知ることはない。とくに、外側の円で宣言されたものの名前を、内側の円から言及してはならない。これは、関数、クラス、変数、あるいはその他、名前が付けられたソフトウェアのエンティティすべてに言える。

同様に、外側の円で使われているデータフォーマットを内側の円で使うべきではない。とくに、それらのフォーマットが、外側の円でフレームワークによって生成されているのであれば。外側の円のどんなものも、内側の円に影響を与えるべきではないのだ。 -- クリーンアーキテクチャ(The Clean Architecture翻訳)

クリーンアーキテクチャでは4つのレイヤーが定義されており、各レイヤーは複数の概念を持つことができます。

レイヤー名 含む概念
Enterprise Business Rules Entities
Application Business Rules Use Cases / Interactor
Interface Adapters Controllers / Presenters / Gateways
Frameworks & Drivers Web / UI / External INterfaces / Devices / DB

次節以降、それぞれのレイヤーと概念について解説します。

Enterprise Business Rules

Enterprise Business Rulesは、図の最も中心に位置する黄色い円で表現されています。レイヤードアーキテクチャにおけるドメイン層に相当する部分で、DDDによる設計が最も影響します。

Entities(エンティティー)

Entitiesはクリーンアーキテクチャ図のEnterprise Business Rulesとして書かれている唯一の概念です。クリーンアーキテクチャにおけるエンティティーの定義は以下です。

エンティティーは、大規模プロジェクトレベルのビジネスルールをカプセル化する。エンティティは、メソッドを持ったオブジェクトかもしれない、あるいは、データ構造と関数の集合かもしれない。(中略) それらは、もっとも一般的で高レベルなルールをカプセル化する。それらは、外側のなにかが変わっても、変わらなさそうなものだ。たとえば、それらのオブジェクトは、ページナビゲーションの変更やセキュリティからの影響を受けないことが期待できる。アプリケーションの動作への変更が、エンティティーレイヤーに影響を与えるべきではない。 -- クリーンアーキテクチャ(The Clean Architecture翻訳) | blog.tai2.net

エンティティとは、コンピュータシステムの内部にあるオブジェクトであり、最重要ビジネスデータを操作する最重要ビジネスルールをいくつか含んだものである。エンティティオブジェクトには、最重要ビジネスデータかそれらのデータへの簡単なアクセス手段が含まれる。エンティティのインターフェイスは、そうしたデータを操作する最重要ビジネスルールを実装した関数で構成されている。 -- Robert C.Martin,角 征典,高木 正弘. Clean Architecture 達人に学ぶソフトウェアの構造と設計 (Japanese Edition) (Kindle の位置No.2978-2982). Kindle 版.

ところで、DDDにもEntityという概念があります。こちらの定義をみてみましょう。

オブジェクトの中には、主要な定義が属性によってなされないものもある。そういうオブジェクトは同一性のつながりを表現するのであり、その同一性は、時間が経っても、異なるかたちで表現されても変わらない。そういうオブジェクトは属性が異なっていても、他のオブジェクトと一致しなければならないことがある。また、あるオブジェクトは、同じ属性を持っていたとしても、他のオブジェクトと区別しなければならない。同一性を取り違えるとデータの破損につながりかねない。主として同一性によって定義されるオブジェクトはエンティティと呼ばれる。 -- Eric Evans. エリック・エヴァンスのドメイン駆動設計 (Japanese Edition) (Kindle の位置No.2326-2332). Kindle 版.

DDDではドメインモデルとして、エンティティの他に値オブジェクトというものがありますが、クリーンアーキテクチャにおけるEntitiesはそれらを包含した、ドメインにおけるモデル全般を指していそうです。ですので、クリーンアーキテクチャを採用した場合でも、Enterprise Business Rules内にはエンティティだけでなく、DDDにおける値オブジェクトや後述のドメインサービスも登場します。

Domain Service

Domain ServiceはDDDで登場する概念です。ドメイン上重要な操作がどのエンティティや値オブジェクトにも紐づかない場合は、ドメインサービスを作成します。

ドメインから生まれる概念の中には、オブジェクトとしてモデル化すると不自然なものもある。こうしたドメインで必要な機能をエンティティや値オブジェクトの責務として押しつけると、モデルに基づくオブジェクトの定義を歪めるか、意味のない不自然なオブジェクトを追加することになる。サービスとは、モデルにおいて独立したインタフェースとして提供される操作で、エンティティと値オブジェクトのようには状態をカプセル化しない。 -- Eric Evans. エリック・エヴァンスのドメイン駆動設計 (Japanese Edition) (Kindle の位置No.2629-2634). Kindle 版.

ドメインにおけるサービスとは、そのドメインに特化したタスクをこなす、ステートレスな操作のことだ。実行すべき何かの操作があって、それを集約(10)や値オブジェクト(6)のメソッドにするのは場違いだと感じたときは、ドメインモデルの中でサービスを作るべきだと考えられる。 -- ヴァーン・ヴァーノン. 実践ドメイン駆動設計 (Japanese Edition) (Kindle の位置No.6464-6467). Kindle 版.

実装時の確認事項

Enterprise Business Rulesの設計はDDDに基づいて行っていくことになります。 DDDについて深く掘り下げるのは本記事での主要な目的ではないため、ここでは以下に絞って確認します。

  • リポジトリの扱い
  • パッケージ構成

リポジトリの扱い

実践ドメイン駆動設計 (髙木正弘 ヴァーン・ヴァーノン)では、リポジトリはサービスでは扱っても良いが、集約では扱うべきではないと述べられています。

経験上、集約の内部からリポジトリ(12)を使うことは、できる限り避けるべきだ。 -- ヴァーン・ヴァーノン. 実践ドメイン駆動設計 (Japanese Edition) (Kindle の位置No.6493-6494). Kindle 版.

ドメイン内のサービスは、必要に応じてリポジトリを使える。しかし、集約のインスタンスからリポジトリにアクセスすることは、お勧めできない。 -- ヴァーン・ヴァーノン. 実践ドメイン駆動設計 (Japanese Edition) (Kindle の位置No.6737-6738). Kindle 版.

また、ドメインサービスでリポジトリを利用する場合、リポジトリのインターフェースはEnterprise Business Rulesで実装されなければなりません。しかし、Application Business Rulesでしか利用しないリポジトリであれば、そちらでインターフェースを実装することも可能です。(TODO: 実装例について、ドメイン層とusecase層それぞれでどのようにリポジトリを実装しているか確認)

パッケージ構成

Enterprise Business Rulesレイヤーに限った話ではないのですが、クリーンアーキテクチャではレイヤーの名前をそのまま採用するのではなく、別の名前を付ける事が多いです。

Application Business Rules

Application business Rulesは図の中心から2番目、赤い円で表現されている部分です。 レイヤードアーキテクチャにおけるアプリケーション層に相当する部分で、Enterprise Business Rulesのクライアントとしてエンティティを操作します。 クリーンアーキテクチャ(The Clean Architecture翻訳)Clean Architecture 達人に学ぶソフトウェアの構造と設計 (角 征典 高木 正弘 Robert C. Martin)の両方でほぼ同じ説明がされているので、多少具体的な記述になっている後者の定義について取り上げます。

ユースケースのレイヤーのソフトウェアには、アプリケーション固有のビジネスルールが含まれている。ここには、システムのすべてのユースケースがカプセル化・実装されている。ユースケースは、エンティティに入出力するデータの流れを調整し、ユースケースの目標を達成できるように、エンティティに最重要ビジネスルールを使用するように指示を出す。 -- Robert C.Martin,角 征典,高木 正弘. Clean Architecture 達人に学ぶソフトウェアの構造と設計 (Japanese Edition) (Kindle の位置No.3161-3165). Kindle 版.

このレイヤーでは以下の概念が登場します。

用語 意味
Use Case ユースケースを表すインターフェース
Interactor Use Caseインターフェースの実装
Use Case Input Port Flow of Controlを説明する際のInput Dataの表現。
Input Data Use Caseインターフェースの引数.。例えばwebサーバであればController(後述)から渡される
Input Boundary クラス図上でのUse Caseインターフェースの表現。
Use Case Output Port Flow of Controlを説明する際のOutput Dataの表現。
Output Data Output Boundaryへ渡す値。
Output Boundary Presenter(後述)のインターフェース。実装は下層レイヤーで行われる。
Repository Repository(後述)のインターフェース。Enterprise Business Rulesレイヤーに置かれる場合もある。実装は下層レイヤーで行われる。

一気に概念が増えましたが、それぞれが具体的にどのような実装になるのかは実践クリーンアーキテクチャ with Java │ nrslibを読むのが分かりやすいです。

DDDのApplication Serviceとの対応

DDDではApplication ServiceUse Caseを表すインターフェースです。(TODO)

実装時の確認事項

Application Business Rulesレイヤーの実装では、以下を確認します。

Input BoundaryとOutput Boundaryの関係

Input BoundaryがUse Case/Interactor、Output BoundaryがPresenterになります。 この時、Use CaseがPresenterを持つのかPresenterがUse Caseを持つのか、どちらが良いのでしょうか。 Clean Architecture 達人に学ぶソフトウェアの構造と設計 (角 征典 高木 正弘 Robert C. Martin)では、ユースケースからプレゼンターを呼び出す例が紹介されています。

たとえば、ユースケースからプレゼンターを呼び出す必要があるとしよう。依存性のルールに違反するため、直接呼び出すことはできない。円の外側にある名前は、円の内側から触れることはできないからだ。したがって、ユースケースから円の内側にあるインターフェイスを呼び出すようにして、円の外側にあるプレゼンターががインターフェイスを実装することになる。 -- Robert C.Martin,角 征典,高木 正弘. Clean Architecture 達人に学ぶソフトウェアの構造と設計 (Japanese Edition) (Kindle の位置No.3199-3203). Kindle 版.

しかし、実装によっては逆の依存になっている場合もあります。

Interface Adapters

Interface AdaptersはFrameworks&DriversとApplication Business Rulesとの間でそれぞれのレイヤーで利用できるような型への相互変換を行います。ここで使われている「Interface」という単語は、プログラミング用語としてのインターフェースではないことに注意してください。クリーンアーキテクチャでの定義は以下のようになっています.

このレイヤーのソフトウェアは、アダプターの集合だ。これは、ユースケースとエンティティにもっとも便利な形式から、データベースやウェブのような外部の機能にもっとも便利な形式に、データを変換する。たとえば、このレイヤーは、GUIのMVCアーキテクチャを完全に内包するだろう。プレゼンター、ビュー、そしてコントローラーは、すべてここに属す。モデルは、コントローラーからユースケースに渡され、そして、ユースケースからプレゼンターやビューに戻される、単なるデータ構造である可能性が高い。

同じように、データはこのレイヤーで、エンティティーやユースケースにもっとも便利な形から、どんな永続化フレームワークが使われているにしろ、それにとってもっとも便利な形に変換される。例えば、データベースなど。この円よりも内側のコードは、データベースについてなにも知るべきではない。もしこのデータベースがSQLデータベースであるならば、どんなSQLであれ、このレイヤーに、もっと言うと、このレイヤーの中のデータベースに関連した部分に、制限されるべきだ。

また、このレイヤーには、その他すべてのアダプターもある。それらは、外部の形式(たとえば外部サービス)から、ユースケースとエンティティーで使われる内部形式にデータを変換するために必要なものだ。 -- クリーンアーキテクチャ(The Clean Architecture翻訳)

このレイヤーでは以下の概念が登場します。

用語 意味
Gateway Frameworks&Driversからのデータを抽象化する。RepositoryやSQLHandlerなど。
Presenter InteractorからOutput DataをOutput Boundaryを経由して受け取り、それをViewに適した形にして返す
Controller Webサーバ等からデータを受け取り、Input Dataに変換してUse Case(Input Boundary)へ渡す

実装時の確認事項

インターフェースの粒度

上記引用のように、インターフェースの例としてSQLが挙げられています。実際にClean ArchitectureでAPI Serverを構築してみる - Qiitaのように、Interface AdapterとしてSQLHandlerという例がよく出てきます。 ただ、この例はクリーンアーキテクチャの依存ルールに反しているように見えます。

このアーキテクチャを機能させる重要なルールが、依存ルールだ。このルールにおいては、ソースコードは、内側に向かってのみ依存することができる。内側の円は、外側の円についてなにも知ることはない。とくに、外側の円で宣言されたものの名前を、内側の円から言及してはならない。これは、関数、クラス、変数、あるいはその他、名前が付けられたソフトウェアのエンティティすべてに言える。 同様に、外側の円で使われているデータフォーマットを内側の円で使うべきではない。とくに、それらのフォーマットが、外側の円でフレームワークによって生成されているのであれば。外側の円のどんなものも、内側の円に影響を与えるべきではないのだ。 -- クリーンアーキテクチャ(The Clean Architecture翻訳)

とブログでは書かれていますが、SQLHandlerはFrameworks&DriversでSQLが利用できるDBを使っていることを知っていますし、仮に利用するDBをMySQLからMongoDBに変更すれば必ずInterface Adapterも変更することになるでしょう。(一応、頑張ってSQLをパースすれば、SQLHandlerを維持しながらFrameworks&Driversの実装をMongoDBにすることもできるでしょうが、現実的ではないと思います。) ただ、クリーンアーキテクチャが目指す独立性は担保されています。

  1. フレームワーク独立。アーキテクチャは、機能満載のソフトウェアのライブラリが手に入ることには依存しない。これは、そういったフレームワークを道具として使うことを可能にし、システムをフレームワークの限定された制約に押し込めなければならないようなことにはさせない。 (中略)
  2. データベース独立。OracleあるいはSQL Serverを、Mongo, BigTable, CoucheDBあるいは他のものと交換することができる。ビジネスルールは、データベースに拘束されない

SQLが利用できるDBであればFrameworks&DriversのORMに何が使われていても上層には影響はありません(フレームワーク独立)。また、仮にDBをNoSQLに変更した結果、Interface Adaptersを変更せざるを得なくなったとしても、Enterprise Business Rulesまではその影響は及びません(データベース独立)。 結局、あらゆるDBに対応できるようなGatewayを書くのは現実的ではないので、実用上のメリットを考えて外部の知識を適宜Interface Adaptersには注入していくのだと解釈しました。

リポジトリインターフェースは誰が定義するのか

RepositoryはGatewayの一つとされています。一方でこれまで見てきたように、Enterprise Business RulesやApplication Business Rulesでリポジトリのインターフェースを定義することができます。結局、Repositoryは必要となったレイヤが適宜定義すれば良いようです。

Frameworks&Drivers

Frameworks&DriversはフレームワークやDBなど、詳細な技術についてを置くためのレイヤーです。

一番外側のレイヤーは、一般に、フレームワークやツールから構成される。データベースやウェブフレームワークなどだ。一般に、このレイヤーには、多くのコードは書かない。ただし、ひとつ内側の円と通信するつなぎのコードは、ここに含まれる。 このレイヤーには、詳細がなにもかも詰め込まれる。ウェブは、詳細だ。データベースは、詳細だ。これのものが悪影響を与えることのないように、外側に保っておく。 -- クリーンアーキテクチャ(The Clean Architecture翻訳)

最も外側の円は、フレームワークやツールで構成されている。たとえば、データベースやウェブフレームワークなどである。通常、このレイヤーにはコードをあまり書かない。書くとしても、円の次の内側とやり取りするグルーコードくらいである。フレームワークとドライバのレイヤーには、詳細が詰まっている。ウェブも詳細。データベースも詳細。被害が抑えられるように、これらは外側に置いておく。 -- Robert C.Martin,角 征典,高木 正弘. Clean Architecture 達人に学ぶソフトウェアの構造と設計 (Japanese Edition) (Kindle の位置No.3182-3186). Kindle 版.

このレイヤーでは、利用する技術やフレームワークによって実装が大きく異なるため、共通で用いられる用語はあまり存在しません。 ただし、このレイヤーでの詳細は、Interface Adapterが理解できる形へ変換される必要があります。 DDDでは、これを行うためのAdapterとTranslatorという責務の分離について紹介されています。

用語 意味
Adapter(DDD) HTTPリクエストなど実装の詳細に基づく具体的な処理を行う。内部で保持しているTranslatorを用いて、上層のレイヤーで解釈可能な方にしてから返す。
Translator(DDD) 与えられた型を、より上層の型に変換する。例えばjsonを受け取ってドメインモデルにデシリアライズするなど。

その他

クリーンアーキテクチャの図では出てきませんが、実装上必要な役割について紹介します。

registry

クリーンアーキテクチャの特徴の一つは、DIPにより依存関係を下層から上層への一方向にしている点です。そのため、どこかでインターフェースと実装の紐付けを行う必要があります。この紐付けを行うコードを置くレイヤーがregistryです。その特性上、registryは他のあらゆるレイヤに依存しても良いですが、どのレイヤーからも依存されてはいけません。最もシンプルなregistryの実装は、開発者が依存関係を解決するコードを自ら書くことでしょう。依存関係が複雑な場合は、GoのwireAndroidのdaggerのような静的DIツールを利用することもできます。この場合registryにあたるコードはツールによって自動生成されます。Springのような動的DIの仕組みが組み込まれたフレームワークでは、アノテーション等により自動的に依存関係が解決されるため、registryにあたるコードは存在しません。

core / utility

処理の中には、どのレイヤーからでも利用したい汎用的なものもあります。それらについては、coreやutility等の名前で別にパッケージを切っておくことが多いです。

実装例(WIP)

ここからの記述は書きかけです。昔の記事で参考にしていたリポジトリへの言及が含まれていたり、そもそも何も書いてなかったりします。Twitter(@mpppk)で感想/コメント/マサカリを頂いたり、この記事にStarを付けてもらえるとやる気が出るのでよろしくお願いします

前章で確認した実装時の確認事項について、GitHubで公開されているサンプルがどのように実装されているかを確認します。おさらいすると、確認ポイントは以下の通りです。

  • 全体

    • パッケージ構成
    • 層を飛ばした依存
    • リポジトリインターフェースの定義レイヤ
  • Application Business Rules

    • Input / Output BoundaryとData
  • Interface Adapters

    • Gatewayの抽象化粒度
    • UseCaseからControllerへの値の返し方
    • PresenterからViewへのデータの渡し方
    • ORMの扱い

実践ドメイン駆動設計 (髙木正弘 ヴァーン・ヴァーノン)の公式サンプル(Java)です。 クリーンアーキテクチャの例では無いため、関係ありそうな部分だけ確認します。

全体的なアーキテククチャ

  • ヘキサゴナルアーキテクチャを採用しており、クリーンアーキテクチャとは構成が異なる(TODO: ソース確認)

Enterprise Business Rules

  • model/domain以下に、エンティティやドメインサービスが全て置かれている。

有料オンラインチュートリアルで利用されているサンプルのようです。 C#で書かれています。 (TODO: オンラインチュートリアル買って見る)

全体

  • CQRSパターンを採用

Enterprise Business Rules

  • エンティティはDomain、ドメインサービスはServiceとして別々にディレクトリが切られている

Application Business Rules

Interface Adapters

  • Service以下にControllerを置く
  • PresentationディレクトリにPresenterが置かれている

Frameworks&Adapters

  • Infrastructure以下に外部サービス呼び出しの実装が置かれている
  • Persistence以下に、DB関連の実装が置かれている

Androidでのクリーンアーキテクチャ実装例です。別のGitHubリポジトリにKotlin実装もあります。

全体アーキテクチャ

  • Presentation / Domain / Dataの3つのレイヤーに分けられています。
  • Presentation
    • Mapper(ViewModelとEntity間のTranslatorに相当)
    • ViewがInterfaceになっている
    • view/Adapter(TODO: DDDにおけるAdapterのことなのか確認)
  • Data
    • Repositoryの実装
    • 外部サービスへのリクエスト
    • 別スレッドへのJobの依頼
    • ローカルファイルキャッシュ
  • Domain
    • Repositoryのインタフェース定義
    • Interactor

  • データの伝搬にRxJavaが用いられています。

Enterprise Business Rules

  • domainディレクトリ直下にエンティティが置かれています
    • とはいえ、サンプルにはUserエンティティしかないので、実際は増えてきたらmodelのようなディレクトリを切るのだと思います。
  • (TODO: サービスについて書く)
  • domain/interactor`にUse CaseインターフェースとInteractorが両方置かれています。

Application Business Rules

  • domain/interactorディレクトリで実装されています。

  • クラス名にはInteractorのようなsuffixは付けられていません。

  • Input DataはInteractorの内部クラスとして実装されています。

  • 書籍での定義とは異なり、Output Boundary(Presenter)が(Input Boundary)Use Caseを持っています。

Interface Adapters

  • Controllerはない
  • Presenterのインターフェースも実装もPresenterレイヤーに置かれている
    • DomainからPresenterへの依存がある?(TODO: 調べる. Observableでなんとかなっている?)

Frameworks&Adapters

  • Viewからの入力がObservableでなんかいい感じになってる?(TODO: 調べる)

Enterprise Business Rules

  • エンティティとドメインサービスはcore/entityに置かれている
  • Application/InterfacesにServiceが定義されている

Application Business Rules

  • UseCase Input/Output Portは実装されていない
  • Presenterも経由せず、UseCaseが戻り値を返し、ControllerがそれをDTOに詰めてレスポンスとして返す

Interface Adapters

  • Interface Adapterとして切られているディレクトリは無い
    • Controllerは無い
      • SpinrgのRestControllerがentrypoints/restにあり、usecaseを直接呼び出している
    • gateway/databaseはdataproviders/databaseがgateway/databaseに相当すると言えなくも無い?
      • jdbcでDBが抽象化されているので、DB差し替えができる

Application Business Rules

  • アクティビティごとにディレクトリが切られ、それらのディレクトリと同階層にabstruct classとしてUseCaseが定義されている
  • UseCaseのInput/Output PortはUseCaseの内部インターフェースとしてUseCase.RequestValueUseCase.ReponseValueとしてそれぞれ定義されている
  • アクティビティごとのディレクトリ以下のdomain/usecaseに UseCaseの実装が置かれている
  • Input Boundary(Use Case)がOutput Boundary(Presenter)を持っています。

C#のサンプル

全体

  • コンソールアプリ、Webアプリなど複数の実装がある

Interface Adapters

  • ConsoleApp / WindowsFormsAppなどViewごとにディレクトリが切られていてその中にPresenters/Controllersなどのディレクトリが切られている
    • Interface Adapterとしてのディレクトリはない
  • ControllerがUseCaseBusを経由してUseCaseを呼び出し、その戻り値をAPIのレスポンスとして返す
  • 記事では、RepositoryがGatewayに当たると書かれているが、インターフェースはUseCase、実装はFrameworks&Driversに置かれている

調べていて感じた疑問

層を飛ばした参照をしても良いのか?

  • ddd-javaではしている
  • Clean Architectureの言い残したことでは、厳密にやる場合はやってはいけないことになってたはず(要確認)

Repositoryはドメイン層かアプリケーション/ユースケース層のどちらで持てば良いのか?

どちらもあるっぽい気がする

ドメイン層で持つ例

ユースケース層で持つ例

@Transactional
public class UserAddInteractor implements UserAddUseCase {
  @Inject
  private UserRepository userRepository;
  @Inject
  private UserAddPresenter userAddPresenter;
  
  @Override
  public void handle(UserAddInputData inputData) {
    String uuid = UUID.randomUUID().toString();
    
    User user = new User(
      new UserId(uuid),
      new UserName(inputData.getUserName()),
      inputData.getRole()
    );
    
    userRepository.save(user);
    
    UserAddOutputData outputData = new UserAddOutputData(uuid);
    userAddPresenter.output(outputData);
  }
}

-- 実践クリーンアーキテクチャ with Java │ nrslib

ORMはinfraなのかinterfaceなのか

抽象度の側面から考えると、interfaceとして適切な粒度であればそこにおいても良い。

参考: 持続可能な開発を目指す ~ ドメイン・ユースケース駆動(クリーンアーキテクチャ) + 単方向に制限した処理 + FRP - Qiita

ORマッパによるテーブル定義やエンティティ:どこまで仕事を肩代わりしてくれるかによる。DBへの永続化を完全に隠蔽してくれるなら、Interface Adapterの必要ない高機能なDomain Modelとして扱える。

が、クリーンアーキテクチャのモチベーションの一つであるフレームワーク独立を実現するには必ずinfra層に無ければならない。

ユースケースはドメインエキスパートが理解できる必要があるのか

ない気がする。どうしても永続化が入ってしまう為。

JsonAPI等を実装するときに、レスポンスをどう返せばいいのか

インフラ層でwebフレームワークを使っている場合、そこにControllerがresponseの中身を返す必要がある そのためには、UseCase内のPresenterが返す必要がある でもPresenterって戻り値を持っていいんだっけ? 実践クリーンアーキテクチャ │ nrslibの「表示のためのオブジェクト」では、以下が提案されている

  • Presenterが結果をどこかにプールして、Controllerがそれを取りに行く

  • UseCaseが戻り値を返す

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment