AI時代だからこそ、ドメイン駆動開発とクリーンアーキテクチャが重要
はい、どうもこんにちは佐藤です!
「AI に任せれば何でも作れる時代に、アーキテクチャなんて不要では?」
最近そんな声を聞くことが増えました。
これまで、銀行案件・ CRM ・基幹業務系のシステムを長くやってきた私の意見では……。
むしろ逆です!
AI 時代だからこそ、ドメイン駆動開発( DDD )とクリーンアーキテクチャが最強になります。その理由を書いていきますね!
最近よく使うアーキテクチャ
FastAPI で業務システムを作るとき、私はこんな構成を取ることが多いです。
├─ application
│ ├─ command # 入力 DTO
│ ├─ result # 出力 DTO
│ └─ usecase
├─ domain
│ ├─ company
│ │ ├─ entity.py
│ │ ├─ domain_service.py
│ │ └─ repository.py # リポジトリインターフェース
│ ├─ contact
│ │ ├─ entity.py
│ │ ├─ domain_service.py
│ │ └─ repository.py
│ ├─ invoice
│ │ ├─ entity.py
│ │ ├─ domain_service.py
│ │ └─ repository.py
│ └─ ...(業務単位でどんどん増えていく)
├─ infrastructure
│ ├─ company
│ │ ├─ model.py
│ │ └─ repository_impl.py # リポジトリ実装クラス
│ ├─ contact
│ │ ├─ model.py
│ │ └─ repository_impl.py
│ └─ ...
├─ interface
│ ├─ company
│ │ ├─ controller.py
│ │ ├─ request.py
│ │ └─ response.py
│ ├─ contact
│ │ ├─ controller.py
│ │ ├─ request.py
│ │ └─ response.py
│ └─ ...
└─ test
├─ application
├─ domain
└─ ...
interface → application → domain → infrastructure という、いわゆる**オニオンアーキテクチャ(クリーンアーキテクチャ)**の構成です。外側が内側を呼ぶ。内側は外側を知らない。この一方向の依存が、すべての出発点になります。
それぞれの層を順番に見ていきましょう。
インターフェース層:入出口の番人
インターフェース層は、コントローラー・リクエスト・レスポンスを管理する場所です。バックエンドであれば HTTP の入口になりますし、アーキテクチャによっては画面も担当します。
ここで意識しているのが「コントローラーを通らないとドメインにアクセスできない」という制約です。
ドメイン層のリポジトリは、インターフェース(抽象クラス)だけを公開しています。その実装、つまり実際の DB アクセスは infrastructure 層にあります。 DI(依存性注入)を使って差し込む構造になっているため、コントローラー経由でないと永続化処理が走りません。
これにより何が嬉しいかというと、テストがとても書きやすくなるのです。ビジネスロジックのテスト時はフェイク実装を差し込めばいい。 DB を立ち上げずに済むテストがたくさん増えますよね。
ドメイン層:業務知識の砦
ここが一番大切な層です。
ドメイン層は業務単位でまとめた塊を扱います。 DDD のアグリゲートの思想で設計しており、例えば「請求書」であればヘッダーテーブル・明細テーブル・履歴テーブルがひとつの塊として扱われます。
domain/
└─ invoice/
├─ invoice.py # アグリゲートルート
├─ invoice_detail.py # 明細エンティティ
├─ invoice_history.py # 履歴エンティティ
└─ invoice_repository.py # リポジトリインターフェース
このドメイン層に書くのは「どのような状態を持つのか」と「どのような操作ができるのか」です。
前回の記事で紹介した状態遷移図と BEAM 分析がここに集約されています。「下書き状態の請求書に承認操作はできない」「差し戻された請求書は再申請できる」といったルールが、ドメインオブジェクトの中に閉じ込められているのです。
業務ルールがここに固まるため、インターフェースが増えても・インフラが変わっても、核心のロジックは揺らぎません。とにかく硬い。
インフラストラクチャ層:永続化の担当
infrastructure 層は、ドメイン層のリポジトリインターフェースの実装を担います。 SQLAlchemy の ORM でも、 NoSQL でも、外部 API でも、ここに差し込む形です。
ドメイン層はデータベースを知りません。「どんな DB でも来い」という構造になっています。
将来 PostgreSQL から DynamoDB に乗り換えることになっても、影響を受けるのは infrastructure 層だけ。ドメインのビジネスロジックは一切変えなくて済みますよね。
アプリケーション層:ドメインのオーケストラ
application 層は、複数のドメインをまたいで処理を組み立てる場所です。「会社と担当者と請求書を組み合わせて売上レポートを作る」といった操作がここに書かれます。
ドメインをまたぐことが前提なので、フォルダ分けはドメインごとではなく入力と出力で分けています。
application/
├─ command/ # 入力 DTO(Command オブジェクト)
├─ result/ # 出力 DTO
└─ usecase/ # ユースケース実装
各 DTO からリポジトリエンティティへの変換もこの層が担います。インターフェース層からコマンドが入ってきたら、ユースケースがドメインエンティティに変換して処理を走らせ、結果を DTO に詰めて返す。入力と出力の型が明確なので、ひとつひとつの処理がとても読みやすくなります。
AI 時代にこの構成が刺さる理由
「冗長すぎない?」と感じた方もいますよね。確かに、コード量は多くなります。 DB が変わったら、ドメインエンティティ・インフラモデル・DTO 、それぞれを書き換える必要があるのも事実です。
でも、今は AI がそのコードを書いてくれます。
書く量の問題は AI が解決してくれるのです。では、残る問題は何か。それは「AI が正しいコードを書けているかどうかの確認」です。
今の AI コーディングエージェントは、テストコードを実行してから成果物として渡してくれます。このサイクルを有効に使うには、テストが走りやすい構造が必要です。
このアーキテクチャは、テストとの相性が抜群です。
# ドメイン層のテスト(DB 不要)
def test_invoice_cannot_approve_from_draft():
invoice = Invoice(status="draft")
with pytest.raises(InvalidStateTransitionError):
invoice.approve()
# アプリケーション層のテスト(リポジトリをフェイクで差し替え)
def test_create_invoice_usecase():
repo = FakeInvoiceRepository()
usecase = CreateInvoiceUseCase(repo)
result = usecase.execute(CreateInvoiceCommand(...))
assert result.invoice_id is not None
ドメイン層のテストは DB すら不要です。ビジネスルールを純粋な Python でテストできます。インフラストラクチャ層だけ実際の DB を使い、残りはフェイク実装で済む。層ごとにテスト戦略を分けられるのです。
ひとつひとつのメソッドの入力と出力が明確なので、人間も AI も読みやすく、 AI が正しいテストを書きやすい構造になっています。 AI をうまく使うには、ファイルの役割分担がはっきりしていることと、テストが書きやすい設計が必要です。このアーキテクチャはその両方を満たしてくれます。
まとめ
というわけで、AI 時代だからこそ DDD とクリーンアーキテクチャが重要だよ!って話でした!
あ。正確には、「書く量は AI が解決してくれるから、設計の硬さに全振りしよう」 って感じですねっ。
コードを書くコストが限りなくゼロに近づいた今、設計に妥協するメリットはほとんどありません。役割が明確に分かれた構造こそが、 AI の生成サイクルを最大限に活かしてくれます。ドメインに業務知識を閉じ込め、テストで品質を担保する。これが AI 時代の堅実なシステム開発です。
ぜひみなさんも、次のプロジェクトから取り入れてみてください!