1. はじめに
クリーンアーキテクチャは“実物”で理解するのが一番早い
抽象的な説明だとイマイチわからなかったため、本記事は「動くコード」を通して理解することを目的としています。
「商品価格を取得する簡易アプリ」を例として扱っていきます。
2. クリーンアーキテクチャとは
クリーンアーキテクチャは
- ビジネスロジック(ドメイン)を中心に置く
- 外側の層に依存しないようにする
- 内側 → 外側の依存は禁止。外側 → 内側はOK。
という“依存方向”のルールがあるアーキテクチャです。
特に重要なのはこれだけ:
UI や DB が変わっても、ビジネスロジックは壊れないようにする
🚨 なぜ、わざわざ面倒な分離をするのか? (アンチパターン)
コードを分離しない場合、以下のような「密結合」が起きがちです。
# 理想: シンプルなビジネスルールだけ
class Item:
def get_price_from_db(self):
# ❌ DBアクセスコードがここに混入!
# MySQL接続ライブラリを使ってSELECT文を発行...
pass
もしItemクラスの中にDBアクセスコードが書かれていたら、以下の問題に直面します。
- テストの困難さ:
Itemクラスのテストをするたびに、実際にDBに接続しなければならない。 - 変更のコスト: DBをMySQLからPostgreSQLに変えたくなったとき、ビジネスロジックの核である
Itemクラスまで修正しなければならない。
クリーンアーキテクチャは、この「外部の事情(DB, UI, フレームワーク)」から「ビジネスの核」を隔離し、変更に強い設計を目指すための設計原則なのです。
3. サンプルアプリの説明
今回作るのは以下:
- Controllerが「商品ID」を受け取る
- UseCaseが価格を取得する処理を行う
- RepositoryがDBから商品情報を取得する
- Domain(Entity)がビジネスルールを持つ
4. Domain(エンティティ) — ビジネスロジックの中心
まずは最も内側の Domain(Entity)。
- DBにもUIにも依存しない
- 純粋なビジネスルールだけ書く
# domain/item.py
from dataclasses import dataclass
@dataclass
class Item:
id: int
price: int
def validate_price(self):
"""価格に関するビジネスルール"""
if self.price < 0:
raise ValueError("Price must be >= 0")
def calculate_tax(self, rate: float = 0.1):
"""価格計算に関するロジック"""
return int(self.price * rate)
ポイント:
Itemはシンプルなモデル- 価格が負数ならエラーにするなど、ビジネスルールはここに置く
- DBの型やSQL、Webフレームワークの事情は絶対に入れないのが重要です。
5. UseCase(Application)— アプリケーションの流れを定義する
UseCase は「アプリの目的」をコードで表現する層であり、各層の中で最も重要な役割を果たします。
🔑 依存性逆転の原則 (DIP) とインターフェース
UseCaseがRepository(DBアクセス)を利用したい場合、具体的な実装(MySQLなど)に依存してはいけません。そこで、まず「契約」であるインターフェース(抽象クラス)を定義します。
# usecase/repository.py
from abc import ABC, abstractmethod
from domain.item import Item # Domainへの依存はOK (内側)
class ItemRepository(ABC):
"""
これは「Itemを取得する機能」の契約(インターフェース)です。
DB実装の詳細を知りません。
"""
@abstractmethod
def find_by_id(self, item_id: int) -> Item | None:
pass
次に、UseCase本体。
# usecase/get_item_price.py
from dataclasses import dataclass
from usecase.repository import ItemRepository # 契約への依存はOK
# from infrastructure.item_repository_mysql import ItemRepositoryMySQL # 🚨 やってはいけない!
@dataclass
class GetItemPriceInput:
item_id: int
@dataclass
class GetItemPriceOutput:
item_id: int
price: int
tax: int # 項目を追加
class GetItemPriceUseCase:
def __init__(self, repository: ItemRepository):
# ここでItemRepositoryの「契約」を受け取る。
# MySQLの実装なのか、メモリ上の実装なのかは知らない。
self.repository = repository
def execute(self, input_data: GetItemPriceInput) -> GetItemPriceOutput:
item = self.repository.find_by_id(input_data.item_id)
if item is None:
raise ValueError("Item not found")
# Domainのロジックを使用
item.validate_price()
calculated_tax = item.calculate_tax()
return GetItemPriceOutput(
item_id=item.id,
price=item.price,
tax=calculated_tax
)
ポイント:
UseCaseは、具体的なDB実装を知らず、ItemRepositoryというインターフェースだけに依存しています。- これが「依存性逆転の原則(DIP)」です。UseCaseはビジネスの流れを定義する層であり、外部の技術に左右されてはいけないため、依存を内側に向けます。
5. Infrastructure — DBアクセスなど外部の世界
ここでは、UseCaseで定義したItemRepositoryのインターフェースを、具体的な外部技術(MySQLなど)で実装します。
# infrastructure/item_repository_mysql.py
from usecase.repository import ItemRepository # 外側が内側の契約に依存
from domain.item import Item
class ItemRepositoryMySQL(ItemRepository):
"""
ItemRepositoryインターフェースの実装。
MySQL接続、SQL発行など、外部とのやり取りをすべてここに閉じ込める。
"""
def find_by_id(self, item_id: int) -> Item | None:
# 本来はMySQLへの接続やSQL発行を書く
# 今回はサンプルとして疑似的に返す
data = {
1: 1000,
2: 500,
}
price = data.get(item_id)
if price is None:
return None
# 取得したデータをDomainのItemに戻す
return Item(id=item_id, price=price)
ポイント:
- SQLやDB接続などの外部の世界はここに閉じ込めます。
- もしDBをPostgreSQLに変える場合、
ItemRepositoryPostgreSQLというクラスを新しく作り、この層だけを変更すれば済みます。UseCaseとDomainは不変です。
6. Interface Adapter(Controller)— 入出力の変換
Webのリクエストを受けてUseCaseを呼び出し、レスポンスを整形する層です。
# controller/item_controller.py
from usecase.get_item_price import (
GetItemPriceUseCase,
GetItemPriceInput
)
from infrastructure.item_repository_mysql import ItemRepositoryMySQL
# Infrastructureへの依存はOK (外側)
class ItemController:
def get_item_price(self, item_id: int):
# 依存関係の注入(DI)
# この層で、使うRepositoryの実装(MySQL)を指定し、UseCaseに渡す
repo = ItemRepositoryMySQL()
usecase = GetItemPriceUseCase(repo)
# 1. 入力データの変換
input_data = GetItemPriceInput(item_id=item_id)
# 2. UseCaseの実行
output = usecase.execute(input_data)
# 3. 出力データの変換 (JSONやHTTPレスポンス形式へ)
return {
"itemId": output.item_id,
"price": output.price,
"tax": output.tax,
"message": "Success"
}
ポイント:
- Controllerは、UseCaseを呼ぶだけです。
- ここで、UI/Webフレームワーク特有の処理(リクエストの解析、JSONの生成など)を行います。
7. 全体の流れ(コードで追う)
GET /item/1 的なリクエストが来たら、コードは以下の流れを辿ります。
- Controller:
itemIdを受け取り、ItemRepositoryMySQLをUseCaseに渡す
- UseCase:
repository.find_by_id()を呼び出す(契約)。 - Infrastructure (MySQL): 実際にDBに接続し、データを取得。
Itemインスタンスを生成して返す。 - UseCase: 取得した
Itemのビジネスルール(validate_price,calculate_tax)をチェック・実行。 - Controller: UseCaseから受け取った
OutputDataをJSONに成形してレスポンス。
どの層も役割が明確であり、依存の方向は常に内側(Domain)に向かって流れていることが確認できます。
8. メリット
クリーンアーキテクチャの導入は、初期のコード量は増えますが、それは未来への投資です。
🛡️ メリット
- テストの容易性:
UseCaseやDomainのビジネスロジックは、外部の要素(DBなど)と完全に分離されているため、インメモリのリポジトリ(モック)を使って高速かつ安定してテストできます。 - 技術の切り替え: 「データベースを変えたい」「UIをWebからCLI(コマンドライン)に変えたい」といった技術的な要望が出たとき、
InfrastructureやInterface Adapter層だけを修正すれば済みます。ビジネスの核は手つかずで済みます。
この設計を実践することで、変更に強い、柔軟なソフトウェアに一歩近づけます。











