📖

なぜ4層?なぜ円形?なぜ内向き?クリーンアーキテクチャと技術への姿勢 #ヌーラボブログリレー2025冬

に公開

ヌーラボブログリレー2025冬サムネイル

この記事はヌーラボブログリレー2025冬の18日目として投稿しています

クリーンアーキテクチャとは何か

こんにちは、foo_543674と申します。

今回は、クリーンアーキテクチャという設計手法について解説しようと思います。クリーンアーキテクチャは、すでに多くの優れた記事で解説されているテーマです。しかし今回は、少し違った視点から、
「クリーンアーキテクチャの図は見たことがあるけど、結局よくわからない」
「具体的なコード例を真似てみたけど、なぜそうするのかピンとこない」
そんな経験がある方に向けた記事です。

もしよろしければ、最後までご覧いただけると幸いです。

誰もが一度は見る『同心円』

クリーンアーキテクチャと聞いて、多くの人が思い浮かべるのは、あの有名な「同心円の図」だと思います。

クリーンアーキテクチャの同心円図

この図を見て、「なるほど、4層に分ければいいのか」「UseCaseクラスやEntityクラスを作ればいいのか」「インターフェース(言語によってはtraitなど)を作ればいいのか」といった、クラス名や構文レベルで理解することが多いのではないでしょうか。

確かに間違いではありません。しかし、もう一歩踏み込んで考えてみたいと思います。

なぜ?

ここで、いくつか問いを投げかけてみます。

「なぜ4層なのか?」

3層ではダメなのでしょうか?5層ではダメなのでしょうか?4という数字に何か特別な意味があるのでしょうか?

「なぜ円形なのか?」

六角形ではダメなのでしょうか?レイヤーケーキのような階層構造ではダメなのでしょうか?

「なぜ依存関係を内側に向けるのか?」

外側ではダメなのでしょうか?双方向ではダメなのでしょうか?

これらの問いに答えるためには、クリーンアーキテクチャが何を解決しようとしているのかという本質を理解する必要があります。

クリーンアーキテクチャが解決する問題

ソフトウェアは変化する

ソフトウェア開発において、唯一確実なことは「要件は変わる」ということです。

  • ビジネスルールが変わる
  • UIのデザインが変わる
  • 使用するフレームワークが変わる
  • データの保存先が変わる
  • 外部APIの仕様が変わる

こうした変更は避けられません。問題は、変更が起きたとき、どれだけのコードを修正しなければならないかです。

変更の影響範囲

理想的には:

  • UIのデザインが変わっても、ビジネスルールは変更不要
  • データベースを変えても、ビジネスロジックは変更不要
  • 外部APIが変わっても、コアのロジックは変更不要

しかし、依存関係が適切に管理されていないと:

  • UIの変更が、ビジネスルールの変更を引き起こす
  • データベースの変更が、アプリケーション全体の書き換えを強いる
  • フレームワークのアップデートが、全ての層に影響する

これらは技術的負債と言われるものとなり、多くのエンジニアがその解消のために日々苦労しているかと存じます。

クリーンアーキテクチャの本質

変更頻度の違いを認識する

ソフトウェアには、変更頻度の異なる複数の関心事が存在します。

変わりにくいもの:

  • ビジネスの本質的なルール
  • ドメインの概念や制約
  • アプリケーションの核となる処理

変わりやすいもの:

  • UIの表現方法
  • データの保存方法
  • 外部システムとの連携方法
  • 使用する技術スタック

この違いを無視すると、変わりやすいものと変わりにくいものが密結合し、変更のたびに広範囲な修正が必要になります。

さらに、変更の頻度だけでなく、変更のイニシアチブが誰にあるかという観点も重要となります。

ソフトウェアの変更には、自分たちがコントロールできないものがあります:

1. イニシアチブが自分たちにある変更

  • ビジネスルールの追加・変更
  • 新機能の開発
  • UIの改善

これらは、自分たちのタイミングでコントロールできます。リソースに余裕がある時期に計画的に進められます。

2. イニシアチブが外部にある変更

  • データベースのEOL(End of Life)
  • Redisなどのミドルウェアのサポート終了
  • 使用しているライブラリやフレームワークの非推奨化
  • セキュリティパッチの適用

これらは、自分たちではコントロールできず、ある日突然対応する必要が出てきます。無論、各ベンダーは余裕を持って発表をしてくれることが多いですが、自分たちのためだけにサポートの延長などをしてくれることは少ないでしょう。

例えば、PostgreSQLやRedisといった重要なインフラのEOLと、ビジネス上の重要な機能追加の時期が被ってしまったらどうしましょう?
どちらも待ったなしです。しかし、開発リソースは限られています。 その結果、リリースが遅れるか、品質が犠牲になるか、あるいはチームが疲弊して倒れるか……最悪のシナリオが待っています。

また、たとえビジネスルールの変更や、新規機能の開発でも、例えば法律の変更といった外的要因でコントロールできないものになってしまうケースも存在し得ます。

クリーンアーキテクチャで依存を切り離しておけば、こうした外部要因による強制的な変更の影響を最小限に抑えられます。データベースのEOL対応がビジネスロジックに波及しないため、両方を並行して進めることが現実的になるのです。

依存の方向を制御する

クリーンアーキテクチャの核心は、「変わりにくいもの」を「変わりやすいもの」から守ることです。

そのための原則は:

1. 変わりやすいものから、変わりにくいものへ依存する

変わりやすいUI層は、変わりにくいビジネスルール層を知っている。しかし、ビジネスルール層は、UI層のことを知らない。

2. 変わりにくいものは、変わりやすいものを知らない

ビジネスルールは、それがWebで使われるのか、CLIで使われるのか、モバイルアプリで使われるのかを知らない。知る必要もない。

3. 依存の向きを一方向に保つ

依存関係は常に一方向(外側から内側へ)に統一し、循環参照や双方向の依存を避ける。

これらの原則により、変わりやすいものが変わっても、変わりにくいものは影響を受けません。変更の影響範囲を限定できます。

抽象化による依存の逆転

しかし、現実には「変わりにくいもの」が「変わりやすいもの」を使う必要があります。

例えば:

  • ビジネスロジックがデータを保存したい(でもDBは変わりやすい)
  • ビジネスロジックが外部APIを呼びたい(でもAPIは変わりやすい)

ここで抽象化が登場します。

ビジネスロジックは、具体的なデータベースではなく、「データを保存・取得する能力」という抽象的な概念に依存します。そして、具体的なデータベース実装は、その抽象的な概念を実現します。

これにより、依存の方向が逆転します。次の項で詳しく説明致しますが、ビジネスロジックが「自分がどう使いたいか、何を求めているか」をインターフェースとして定義し、変わりやすい実装がそのインターフェースに従って、「能力を実現するための詳細」を提供します。

同心円の図の意味

ここで、最初の同心円の図に戻りましょう。

円の中心に近いほど、変わりにくい

  • Entities: ビジネスの本質、最も変わりにくい
  • Use Cases: アプリケーション固有のロジック、比較的変わりにくい
  • Interface Adapters: 外部とのやりとりを変換、やや変わりやすい
  • Frameworks & Drivers: 具体的な技術、最も変わりやすい

依存関係は内側に向く

矢印は常に、外側(変わりやすい)から内側(変わりにくい)へ。

なぜ「円」で表現されるのか

クリーンアーキテクチャの象徴とも言える「同心円」。なぜ従来のレイヤードアーキテクチャのような「階層(積み上げ型)」ではなく、あえて「円」で表現されているのでしょうか?

ここには、「概念的なメッセージ(方針と詳細)」と「技術的な仕組み(DIP)」という、2つの理由が込められています。

  1. 「詳細」を「端」に配置する意図(概念的な理由)

提唱者であるロバート・C・マーチン氏は、円の中心を「方針(Policy)」、外側を「詳細(Detail)」と定義しています。

一般的な階層図(3層アーキテクチャなど)では、構造上どうしてもデータベースが「一番下」に描かれます。物理的な感覚として、下にあるものは「土台」として捉えられやすく、視覚的に「システムはデータベースの上に成り立っている」という印象を与えやすい側面があります。

しかし、クリーンアーキテクチャでは、データベースやWeb UIは中心のビジネスルールから見れば「着脱可能なプラグイン(詳細)」であると考えます。

  • 中心(高レベル): ビジネスの価値そのものであり、変わりにくい領域。
  • 外側(低レベル): UIやDBなど、技術の進化に合わせて交換可能な領域。

円形にすることで、データベースは土台(Foundation)というよりも、UIと同じく外側(Peripheral)にある要素の一つであるという関係性を、より直感的に表現しているのです。

  1. 処理の流れと依存の方向の分離(技術的な理由)

そして、この構造を実現するために不可欠なのが、依存性逆転の原則(DIP: Dependency Inversion Principle)です。

階層構造の図では、一般的に「処理の流れ」と「依存の方向」が上から下へと一致しているイメージがあります(例:UI → ロジック → DB)。 しかし、クリーンアーキテクチャの円の中では、これらが一致しない箇所があります。

  • 処理の流れ: UseCase(中) → データベースへの保存処理(外)へと向かう。
  • 依存の方向: UseCase(中) ← データベースの実装(外)から内側へ向かう。

「処理は外へ向かって行われるが、コードの依存関係は内側を守るように向いている」。 一見矛盾するように見えるこの高度な構成を、インターフェースを介して実現しています。

円の図は、入出力(I/O)としての処理の流れは内向きにも外向きにも発生するが、依存の矢印は常に中心に向かう一方通行であるという、このアーキテクチャ特有のルールをシンプルに表していると言えます。

4層という数字に絶対的な意味はない

変更頻度に応じて関心事を分離し、依存の方向を制御することが重要なので、3層でも5層でも、この原則が守られていればよい。

表現方法は様々

ヘキサゴナルアーキテクチャ、オニオンアーキテクチャなど、表現方法は様々。しかし、「依存の方向を内側に向け、変更の影響を局所化する」という本質は同じです。

クリーンアーキテクチャがもたらすもの

変更に強い

クリーンアーキテクチャでは、UIを変更してもビジネスロジックには影響せず、データベースを変更してもユースケースには影響しません。変更の影響が局所的に限定されます。

テストしやすい

クリーンアーキテクチャでは、外部依存を持たないビジネスロジックを単体で完結してテストできます。データベースもWebサーバーも不要です。

フレームワークに依存しない

クリーンアーキテクチャでは、特定のフレームワークに縛られません。フレームワークは「外側」の実装詳細として扱われ、必要であれば交換することができます。

段階的な実装が可能

クリーンアーキテクチャでは、ビジネスロジックを先に実装し、後からUIやデータベースを選ぶことができます。デモやプロトタイプも作りやすくなります。

ここで重要なのが、判断の先送りという考え方です。

不確定要素が多い開発初期

開発初期段階では、「要件はわかっているけど不確定事項が多い」という状況がよくあります。また、開発の後の段階になって初めて出てくる制限事項もあります。

こうした状況で、最初からデータベースやフレームワークを確定させる必要はありません。ドメイン層の開発が一段落するまで、仮のインメモリDBやシンプルな実装で進めることができます。実際の要件や制約が明確になってから、最適な技術を選択すればよいのです。

技術移行時の柔軟性

例えば、RDBMSからNoSQLへデータベースを移行したい場合を考えてみましょう。

移行時のトラブルを見越して、並行運用期間を設けたいと仮定します。密結合なアーキテクチャでも、移行期間中に二重書き込みすることで、並行運用期間を設けることは技術的には可能です。しかし、データベースアクセスがコード全体に散らばっているため、修正箇所が膨大になり、影響範囲も広くなります。

一方、クリーンアーキテクチャでは、以下のように進められます:

  1. 新DB用のインターフェース実装を新規作成
  2. 両方に書き込むインターフェース実装を新規作成
  3. ドメイン層やアプリケーション層は変更せず、依存の設定で実装を差し替えるだけ

既存コードの修正ではなく、新規開発と古いコードの廃棄で対応できます。移行が完了したら、旧DB用の実装と両方に書き込む実装を削除すればよいだけです。既存コードを修正するのは基本的に面倒ですが、この方法ならそれを避けられます。

このように、重要な技術判断を適切なタイミングまで先送りでき、かつ移行時の柔軟性も高いのが、クリーンアーキテクチャの大きなメリットです。

クリーンアーキテクチャを導入する前に考えるべきこと

ここまでクリーンアーキテクチャのメリットを強調してきましたが、全てのプロジェクトに適用すべき「銀の弾丸」ではありません。

オーバーエンジニアリングのリスク

真に使い捨てのプロトタイプや、ビジネスロジックがほぼ存在しない単純なCRUDアプリでは、層を分けるコスト(コード量の増加や複雑さ)がメリットを上回ることがあります。

ただし注意すべきは、「開発期間が短い」ことと「プロダクトの寿命が短い」ことは別問題だということです。例えば、3ヶ月で開発したシステムが5年間運用される場合、長期的なメンテナンスコストを考えると、初期の設計投資は十分に回収できます。

ボイラープレートコードの増加

レイヤー間でデータをやり取りする際に、データ転送オブジェクト(DTO)やレイヤー固有のモデルへのマッピング処理が頻繁に発生します。このデータ変換のオーバーヘッドが、開発の手間や実行時のパフォーマンスに影響を与えることがあります。

学習コストと技術レベル要件

クリーンアーキテクチャの概念を深く理解し、適切に実装するには、チーム全体の高い技術力と共通認識が不可欠です。導入初期の学習コストや開発コストが非常に高く、経験の浅い開発者が多いチームでは、その恩恵を受ける前に破綻してしまう可能性もあります。

何を守りたいかを明確にする

クリーンアーキテクチャを導入する前に、「何を守りたいのか」を明確にすることが重要です。変更頻度が高い部分はどこか、将来的にどのような変更が予想されるか、そのために払うコストは妥当か。これらを慎重に検討した上で、プロジェクトの特性に応じて適用すべきかどうかを判断することが求められます。

例えば、私はWebアプリケーションを開発する時にフレームワークを選ぶ際、ドメイン層がどれだけピュアな言語仕様だけで実現できるか(フレームワークへの依存を切り離せるか)を基準に選んだりします。

具体的には、Entityにフレームワーク固有のアノテーションや属性を付与しなければならないようなフレームワークの場合、慎重に検討するようにしています。もちろん、これは「そのフレームワークが悪い」という意味ではありません。状況によっては「設定より規約」的なフレームワークを使うのが適切なケースもあります。

ただし、これは「開発効率を重視する」というような単純な話ではありません。重要なのは「なぜそうするのか」を理解した上での判断です。例えば、私はCQRS[1]の考え方を部分的に取り入れ、複雑なビジネスロジックを守る「書き込み側(Command)」には厳格なレイヤー構造を適用する一方、パフォーマンスが命である「読み込み側(Query)」ではあえてレイヤーを排除し、インフラの能力を最大限引き出す構成にすることがあります。

「抽象化」という武器の特性を理解し、局面に応じて「ここはこの設計パターンを使うべきだ」と正しく判断できるようになると、「変わりにくいものを、変わりやすいものから守る」というクリーンアーキテクチャの本質を体現できると思います。

最後に

ここまで本記事をご覧いただいて、誠にありがとうございます。
長らく文章が続きましたが、実はここからお話しすることが、私がこの記事を通じて本当に伝えたかった「本題」となります。それは、技術と向き合うための「抽象化」という姿勢についてです。

さて、本記事では、あえて具体的なコードを使わずに、クリーンアーキテクチャの説明を行いました。もしかしたら「わかりづらい」「具体的な実装例が見たい」と感じた方もいらっしゃるかもしれません。その「もどかしさ」を感じさせてしまった点については、申し訳ありません。

なぜ、あえて具体例を避けたのか。
それは、具体例には「分かりやすい」という大きなメリットがある反面、「具体的な形」に意識が集中してしまい、本質的な意図が見えにくくなるという側面があるからです。

これは、私自身も含め、多くのエンジニアが一度は直面し、今なお向き合い続けている課題ではないでしょうか。

具体例が持つ「分かりやすさ」の罠

スクラムの例

例えば「スクラム」という方法論があります。
スクラムを学び始める時、私たちはまず具体的なプラクティスから入ることが多いかもしれません。「毎朝スタンドアップミーティングをする」「2週間でスプリントを切る」「ふせんを使う」。これらは具体的で実践しやすいものです。

しかし、これらの「形式」を導入しただけで、スクラムの本質である「透明性・検査・適応」が自動的に手に入るわけではありません。

もちろん、学びの入り口として『形から入る』ことはとても大切です。しかし、そこで止まってしまうと、「形は整っているのに、なぜかうまくいかない」という状況に陥ることがあります。これは、形式を導入することに一生懸命になりすぎて、その奥にある「なぜそれをやるのか(Why)」という本質的な問いかけが後回しになってしまうのが原因ではないかなと思っています。

私自身、スクラムについて学ぶ過程で、深く知れば知るほど、その導入には文化や考え方の一新など、相応の覚悟と環境整備が必要であることに気づき、安易に形だけ取り入れることの危うさを感じました。

関数型プログラミングの例

関数型プログラミングも同様です。

「forループを使わず、map/filter/reduceを使う」「変数を使わず、constで宣言する」といったテクニックは、確かに分かりやすい入り口です。

しかし、関数型プログラミングの本質は「参照透過性」「不変性」「合成可能性」といった数学的な性質にあります。なぜmap/filterを使うのか、なぜ不変性が重要なのかを理解することで、より深い活用が可能になります。

私もまだまだ関数型言語を深く理解したとは言えず、日々学ぶことが多い状態です。

ドメイン駆動設計の例

ドメイン駆動設計という開発手法があります。
こちらも、「Repositoryパターンを使えばドメイン駆動設計になる」という入り口は、具体的で分かりやすく、変化も感じ取りやすいので、非常に魅力的なのはわかります。

確かに、Repositoryパターンは「変化しやすいインフラの詳細からドメイン知識を守る」という重要な考え方を体現した手段の一つです。一方で、ドメイン駆動設計には、ドメインエキスパートのアサイン、要件を引き出すためのヒアリング手法、様々な業務分析のフォーマットと言った、要件定義から始まる「ドメインを中心とした開発アプローチ」という、より大きな枠組みがあります。

具体例やパターンは強力な武器ですが、「手段」が「目的」に入れ替わってしまうという落とし穴も持っているのです。

抽象的な概念を「抽象的なまま」

具体例から入るのは、学習のステップとして自然なことです。しかし、そこからもう一歩進んで、「知識の抽象化」を通じて抽象的な概念を抽象的なまま腹落ちさせるというプロセスを経ることで、技術力は一段階引き上がると私は考えています。

抽象的な概念を理解することには、大きなメリットがあります。

  1. 応用が利く:言語やフレームワークが変わっても、共通の「原則」として知識を再利用できます。
  2. 判断ができる:「ここは原則を守るべき」「ここはトレードオフで崩してもいい」という応用的な判断が可能になります。
  3. 新しい知識と統合できる:異なる技術の背後にある共通原則が見えてきます。

技術を学ぶとき、分かりやすい具体例(How)に飛びつきたくなる気持ちをぐっと抑えて、「なぜそうするのか(Why)」「何を解決しようとしているのか(What)」という抽象的な問いに向き合ってみる。

そうすることで、一見難解なアーキテクチャも、実はシンプルな原則の積み重ねであることに気づけるはずです。

本記事が、その「本質への一歩」を踏み出すきっかけになれば幸いです。

脚注
  1. CQRSとは、「Command Query Responsibility Segregation」の略で、書き込み(Command)と読み込み(Query)の責務を完全に「隔離(Segregation)」し、アプリケーション層だけでなくストレージやプロセスも含めた全体アーキテクチャレベルで別々のサービスとして扱う設計手法。ここでは、その考え方を部分的にアプリケーションレイヤーレベルで適用する例として紹介。 ↩︎

Discussion