クリーンアーキテクチャ¶
はじめに¶
本サイトにつきまして、【IT技術の知見】はじめに - 俺の技術ノート をご認識のほど宜しくお願いいたします。
01. アーキテクチャ概要¶
思想¶
ドメイン駆動設計が適する機能的アプリケーションのみでなく、あらゆる種類 (例:非機能的アプリケーションなど) のソフトウェアに適用できる。
クリーンアーキテクチャ原著の序文にて、著者は「私は今までいろいろな種類のシステムを作ってきたが、どのシステムでもアーキテクチャのルールは同じだった」と述べている。
異なるシステムでも同じルールを共有する必要がある』というようなことを述べている。
構成¶
クリーンアーキテクチャは同心円状の層で構成され、外側の層から内側の層に向かってのみ依存する。

各パターンの分類¶
オブジェクト指向型と手続き型のマルチパラダイム言語の場合、アプリケーションをクラスまたは関数で実装できる。
クリーンアーキテクチャはオブジェクト指向設計が前提にあるため、各レイヤーのパターンをクラスとして実装することが多い。
ただし、クラスとして実装するとインスタンス化の手間がある。関数として実装しても、アーキテクチャの思想に反しない場合がある (特に外側のレイヤー) 。
アーキテクチャのレイヤー別にこれを整理した。
| バックエンドレイヤー | パターン | 責務 | インターフェース/実装 |
|---|---|---|---|
| インフラストラクチャ層 | DB | データベースとの接続、コネクションの作成 | 実装 |
| EntityMappers (DTO に相当) | DB カラム型からアプリデータ型への型変換(例:BIGINT や DECIMAL を情報量が減らないデータ型に変換)、ドメインとインフラ間のオブジェクトの詰め替え |
実装 | |
| Logger | ロギング | 実装 | |
| Middleware | ミドルウェアパターン | 実装 | |
| Repositories | 実装リポジトリ | 実装 | |
| Listeners | リスナー | 関数 | |
| Routers | ルーティング | 関数 | |
| Seeder | 開発環境、CI 環境のユニットテストや E2E テスト、ステージング環境の動作確認に使用する初期データ挿入 | 実装 | |
| Drivers | SDK、ORM、CLI など | 実装 | |
| プレゼンテーション層 | Controller | データの受信と返信 | 実装 |
| RequestValidators | 受信データのバリデーション | 実装 | |
| ResponseValidators | 返信データのバリデーション (受信データと比べてデータ構造は保証されているため、なくてもいい) | 実装 | |
| Authenticators | 認証 | 実装 | |
| Input (Command とも呼ぶ) | 型変換、入力データ翻訳、フロントエンドインターフェース (あるいは受信 JSON データ) からバックエンドインターフェースへのオブジェクトへの詰め替え | 実装 | |
| Output (DTO とも呼ぶ) | 型変換、出力データ翻訳、バックエンドインターフェースからフロントエンドインターフェースへのオブジェクトへの詰め替え | 実装 | |
| ユースケース層 | Interactor | ドメイン層で定義されたドメインルールを組み合わせ、ユースケースを具現化する。 | 実装 |
| InputBoundaries | Interactor のインターフェース | インターフェース | |
| Input (Command とも呼ぶ) | 型変換、バックエンドインターフェースからユースケースのオブジェクトへの詰め替え | 実装 | |
| OutputBoundaries | Presenter のインターフェース | インターフェース | |
| Output (DTO とも呼ぶ) | 型変換、ユースケースからフロントエンドインターフェースへのオブジェクトへの詰め替え | 実装 | |
| Repositories(Gateways とも呼ぶ) | Repositories のインターフェース | インターフェース | |
| ドメイン層 | Entity | ドメインモデル、ドメインルールの定義 | 実装 |
| Id | ドメインモデルの識別子 | 実装 | |
| ValueObject | 値オブジェクト | 実装 | |
| Specification | ビジネスロジックのバリデーション | 実装 | |
| Criterion | 検索条件オブジェクト | 実装 | |
| Events | ドメインイベント | 実装 | |
| Service | ドメインサービス | 実装 |
| フロントエンドレイヤー | パターン | 責務 | インターフェース/実装 |
|---|---|---|---|
| プレゼンテーション層 | Presenter | ドメインオブジェクトから JSON データへの詰め替え | 実装 |
| Validators | フロントエンドのバリデーション | 実装 | |
| ViewModel | 状態管理、JSON データをフロントエンドインターフェースのオブジェクトへの詰め替え | 実装 | |
| UI 層 | View | UI レンダリング、CSS スタイリング | 実装 |
実装例たち¶
| 言語 | あれば書籍名 | URL |
|---|---|---|
| TypeScript | つくりながら学ぶ! ドメイン駆動設計 実践入門 | https://github.com/yamachan0625/hands-on-ddd-introduction/tree/main/chapter-21 |
| Java | なし | https://github.com/VaughnVernon/IDDD_Samples |
| Go | なし | https://github.com/victorsteven/food-app-server |
| C# | ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本 | https://github.com/nrslib/itddd |
| そのほか | なし | https://github.com/stars/hiroki-it/lists/ddd-architecture |
02. 依存方向の制御¶
ドキュメント¶
レイヤー間の依存関係の方向をドキュメントで明文化する。
graph LR
%% インターフェース層
%% リクエストの受信やレスポンスの返信に関するロジックを置く
subgraph controllers ["【インターフェース層】./controllers"]
controller
end
%% ユースケース層
%% ドメイン層のロジックを組み合わせ、ユースケースを実現するロジックを置く
subgraph usecases ["【ユースケース層】./usecases"]
usecase
end
%% ドメイン層
%% ドメインルールに関するロジックと、リポジトリのインターフェースを置く
subgraph domain ["【ドメイン層】./domain"]
domainObject
repositoryInterface
end
%% インフラストラクチャ層
%% データベースへの接続や、リポジトリの実装に関するロジックを置く
subgraph infrastructure ["【インフラストラクチャ層】./infrastructure"]
repository
database
end
controllers --> usecases
usecases --> domain
infrastructure -->|インターフェースを実装| domain
repository -->|CRUD| database
静的解析¶
レイヤー間の依存関係の方向を静的解析で制御する。
例えば、Go では golangci-lint の depguard、TypeScript では eslint-plugin-import プラグインの import/no-restricted-paths ルール、PHP では Deptrac が役立つ。
.yml
version: "2"
linters:
enable:
- depguard
settings:
depguard:
rules:
domain:
# 対象のファイル
files:
- "**/internal/domain/**"
- "!$test"
# 依存禁止のパッケージ
deny:
# アプリケーション層に依存してはいけない
- pkg: "example.com/foo/internal/application"
desc: "Domain層でApplication層をimportしてはいけません。"
# プレゼンテーション層に依存してはいけない
- pkg: "example.com/foo/internal/presentation"
desc: "Domain層でPresentation層をimportしてはいけません。"
# インフラストラクチャ層に依存してはいけない
- pkg: "example.com/foo/internal/infrastructure"
desc: "Domain層でInfrastructure層をimportしてはいけません。"
application:
# 対象のファイル
files:
- "**/internal/application/**"
- "!$test"
# 依存禁止のパッケージ
deny:
# プレゼンテーション層に依存してはいけない
- pkg: "example.com/foo/internal/presentation"
desc: "Application層でPresentation層をimportしてはいけません。"
# インフラストラクチャ層に依存してはいけない
- pkg: "example.com/foo/internal/infrastructure"
desc: "Application層でInfrastructure層をimportしてはいけません。"
TypeScript の場合の実装例
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import * as importPlugin from "eslint-plugin-import";
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.ts"],
plugins: {
import: importPlugin,
},
settings: {
"import/resolver": {
typescript: {},
},
},
rules: {
// (略)
"import/no-restricted-paths": [
"error",
{
zones: [
{
// 依存禁止のパス
// アプリケーション層に依存してはいけない
from: "./src/Application/**/*",
// 対象のファイル
target: "./src/Domain/**/!(*.spec.ts|*.test.ts)",
message:
"Domain層でApplication層をimportしてはいけません。",
},
{
// 依存禁止のパス
// プレゼンテーション層に依存してはいけない
from: "./src/Presentation/**/*",
// 対象のファイル
target: "./src/Domain/**/!(*.spec.ts|*.test.ts)",
message:
"Domain層でPresentation層をimportしてはいけません。",
},
{
// 依存禁止のパス
// インフラストラクチャ層に依存してはいけない
from: "./src/Infrastructure/**/*!(test).ts",
// 対象のファイル
target: "./src/Domain/**/!(*.spec.ts|*.test.ts)",
message:
"Domain層でInfrastructure層をimportしてはいけません。",
},
{
// 依存禁止のパス
// プレゼンテーション層に依存してはいけない
from: "./src/Presentation/**/*",
// 対象のファイル
target: "./src/Application/**/!(*.spec.ts|*.test.ts)",
message:
"Application層でPresentation層をimportしてはいけません。",
},
{
// 依存禁止のパス
// インフラストラクチャ層に依存してはいけない
from: "./src/Infrastructure/**/*",
// 対象のファイル
target: "./src/Application/**/!(*.spec.ts|*.test.ts)",
message:
"Application層でInfrastructure層をimportしてはいけません。",
},
],
},
],
},
}
);
.yaml
deptrac:
paths:
- ./src
# 層とそのディレクトリ
layers:
- name: Domain
collectors:
- type: directory
value: src/Domain/.*
- name: Application
collectors:
- type: directory
value: src/Application/.*
- name: Presentation
collectors:
- type: directory
value: src/Presentation/.*
- name: Infrastructure
collectors:
- type: directory
value: src/Infrastructure/.*
# 依存してよい層
ruleset:
# ドメイン層は、ほかの層に依存してはいけない
Domain: ~
# アプリケーション層は、ドメイン層にのみ依存してよい
Application:
- Domain
Presentation:
- Application
- Domain
Infrastructure:
- Application
- Domain