クリーンアーキテクチャって何?一言でいうと「大事なものを真ん中に守る設計」
クリーンアーキテクチャは、アプリケーションの「大事なルール(ビジネスロジック)」を、外側の技術的なもの(Web フレームワーク、DB、外部サービスなど)から守るための設計の考え方です。
キーワードは「依存の向き」と「層(レイヤ)」です。
外側のものは、いつでも変わりうるものです。
フレームワークも DB も、数年後には別のものに変えたくなるかもしれません。
一方で、アプリの「本質的なルール」は、そう簡単には変わりません。
クリーンアーキテクチャは、「変わりやすいものが、変わりにくいものに依存するようにする」ことで、コアのロジックを長く守ろう、という発想です。
円のイメージでざっくりつかむクリーンアーキテクチャ
クリーンアーキテクチャは、よく「同心円」で説明されます。
中心に行くほど「ビジネスに近い・本質的なルール」、外側に行くほど「技術的・インフラ寄り」です。
ざっくり分けると、次のようなイメージになります。
中心に「エンティティ(ドメインルール)」
その外に「ユースケース(アプリケーションの振る舞い)」
さらに外に「インターフェース・アダプタ(DB や Web との橋渡し)」
一番外側に「フレームワーク・UI・DB・外部サービス」
重要なのは、「依存の向き」です。
外側は内側を知っていていいけれど、内側は外側を知らないようにする。
Python 的に言うと、「内側のコードは、外側のライブラリに import してはいけない」というイメージです。
具体例で見る:ユーザー登録機能をクリーンアーキテクチャで分解する
まずは「ごちゃ混ぜ版」のコード
よくある「フレームワークにべったり」な書き方を、あえてやってみます。
# app.py(フレームワークにべったりな例)
from flask import Flask, request, jsonify
import sqlite3
app = Flask(__name__)
@app.post("/users")
def register_user():
data = request.json
name = data.get("name")
email = data.get("email")
if not name:
return jsonify({"error": "name is required"}), 400
if "@" not in email:
return jsonify({"error": "invalid email"}), 400
conn = sqlite3.connect("app.db")
cur = conn.cursor()
cur.execute(
"INSERT INTO users (name, email) VALUES (?, ?)",
(name, email),
)
conn.commit()
return jsonify({"message": "ok"}), 201
PythonHTTP のこと(Flask)
DB のこと(sqlite3)
ビジネスルール(名前必須、メール形式チェック)
が全部一つの関数に詰まっています。
動くことは動きますが、次のような問題が出てきます。
フレームワークを変えたくなったら全部書き換え
DB を変えたくなっても全部書き換え
ビジネスルールだけをテストするのが難しい
ここでクリーンアーキテクチャの考え方を使って、分解していきます。
ドメイン(中心):ユーザーのルールを表す
まずは「ユーザー」という概念と、そのルールだけを切り出します。
# domain/user.py
from dataclasses import dataclass
@dataclass
class User:
name: str
email: str
def validate(self) -> None:
if not self.name:
raise ValueError("name is required")
if "@" not in self.email:
raise ValueError("invalid email")
Pythonここには、Flask も sqlite3 も出てきません。
「ユーザーとは何か」「どんなときにエラーとするか」だけが書かれています。
これが「内側の層」です。
ここは、できるだけ技術から切り離しておきたい場所です。
ユースケース層:アプリとして「何をするか」を表す
次に、「ユーザーを登録する」というユースケースを表現します。
# usecase/register_user.py
from dataclasses import dataclass
from typing import Protocol
from domain.user import User
class UserRepository(Protocol):
def save(self, user: User) -> None:
...
@dataclass
class RegisterUserInput:
name: str
email: str
@dataclass
class RegisterUserOutput:
message: str
def register_user(
repo: UserRepository,
data: RegisterUserInput,
) -> RegisterUserOutput:
user = User(name=data.name, email=data.email)
user.validate()
repo.save(user)
return RegisterUserOutput(message="ok")
Pythonここで重要なのは、DB の具体的な実装を知らないことです。UserRepository という Protocol(インターフェース)に依存していて、
「save できる何か」があれば動くようになっています。
HTTP のことも知りません。
「入力データを受け取り、ユーザーを検証して保存し、結果を返す」
というユースケースだけを担当しています。
インフラ層:DB の具体的な実装を書く
DB にユーザーを保存する実装は、外側に追い出します。
# infrastructure/sqlite_user_repository.py
import sqlite3
from domain.user import User
from usecase.register_user import UserRepository
class SqliteUserRepository(UserRepository):
def __init__(self, db_path: str) -> None:
self._db_path = db_path
def save(self, user: User) -> None:
conn = sqlite3.connect(self._db_path)
cur = conn.cursor()
cur.execute(
"INSERT INTO users (name, email) VALUES (?, ?)",
(user.name, user.email),
)
conn.commit()
conn.close()
Pythonここは sqlite3 にべったりで構いません。
でも、このコードは「ユースケース側」からは見えません。
ユースケースは UserRepository という抽象にしか依存していないからです。
プレゼンテーション層:HTTP とユースケースをつなぐ
最後に、Flask のコードは「ユースケースを呼び出すだけ」にします。
# interface/flask_app.py
from flask import Flask, request, jsonify
from usecase.register_user import (
register_user,
RegisterUserInput,
)
from infrastructure.sqlite_user_repository import SqliteUserRepository
app = Flask(__name__)
repo = SqliteUserRepository("app.db")
@app.post("/users")
def register_user_endpoint():
data = request.json or {}
input_data = RegisterUserInput(
name=data.get("name", ""),
email=data.get("email", ""),
)
try:
output = register_user(repo, input_data)
return jsonify({"message": output.message}), 201
except ValueError as e:
return jsonify({"error": str(e)}), 400
Pythonここは HTTP のことだけを考えます。
JSON から入力を取り出し、ユースケースに渡し、結果を HTTP レスポンスに変換する。
ビジネスルールは一切書きません。
こうしてみると、責務がきれいに分かれたのが分かるはずです。
依存性逆転の考え方をもう少しだけ深掘りする
クリーンアーキテクチャの核にあるのが「依存性逆転」です。
普通に書くと、「ビジネスロジックが DB やフレームワークに依存する」形になりがちです。
さっきの最初の例では、register_user が sqlite3 に直接依存していました。
これは「内側が外側に依存している」状態です。
クリーンアーキテクチャでは、これをひっくり返します。
「外側が内側に依存する」ようにします。
ユースケースは UserRepository という抽象に依存する
具体的な SqliteUserRepository は、その抽象を実装する
このとき、依存の矢印はこうなります。
SqliteUserRepository → UserRepository → register_user → User
外側から内側に向かって矢印が伸びているイメージです。
内側は、外側の存在を知りません。
この「内側は外側を知らない」という状態が、
テストのしやすさと変更のしやすさを生みます。
クリーンアーキテクチャとテスト・品質の関係
クリーンアーキテクチャは、「テストしやすさ」をかなり意識した設計です。
さっきの構成だと、次のようなテストが書きやすくなります。
ドメイン層(User.validate)のテスト
ユースケース層(register_user)のテスト(リポジトリをテスト用のダミーに差し替える)
インフラ層(SqliteUserRepository)のテスト(必要なら別途)
HTTP 層(Flask)は最小限だけテストする
例えば、ユースケースだけをテストするなら、こんな感じになります。
from usecase.register_user import (
register_user,
RegisterUserInput,
UserRepository,
)
from domain.user import User
class DummyRepo(UserRepository):
def __init__(self) -> None:
self.saved: list[User] = []
def save(self, user: User) -> None:
self.saved.append(user)
def test_register_user_success():
repo = DummyRepo()
input_data = RegisterUserInput(name="Taro", email="taro@example.com")
output = register_user(repo, input_data)
assert output.message == "ok"
assert len(repo.saved) == 1
assert repo.saved[0].email == "taro@example.com"
Pythonここでは Flask も sqlite3 も出てきません。
「ユースケースのロジック」だけを、純粋にテストできます。
これが、クリーンアーキテクチャが品質に効く一番大きな理由です。
「ビジネスロジックを、外部の技術から切り離してテストできる」状態を作るからです。
初心者がクリーンアーキテクチャとどう付き合うといいか
いきなり完璧なクリーンアーキテクチャを目指すと、ほぼ確実に挫折します。
特に小さなスクリプトや個人開発では、「やりすぎ」にもなりがちです。
最初の一歩としては、次のような意識だけでも十分価値があります。
ビジネスロジック(ルール)を、フレームワークや DB から分けてみる
「ユースケース関数」を一つ作って、そこにロジックを集める
DB や外部サービスへのアクセスは、「リポジトリ的なクラス」に追い出す
ユースケース関数を、フレームワーク抜きでテストしてみる
つまり、「層を4つきっちり作る」よりも先に、
「ビジネスロジックを外側から分離する」ことを意識するのが大事です。
クリーンアーキテクチャは「型にはめるための宗教」ではなく、
「変わりにくいものを守り、変わりやすいものを外側に追い出すための考え方」です。
そのエッセンスだけでも取り入れると、コードの寿命と品質がかなり変わります。
まとめ(クリーンアーキテクチャは「依存の向きを整えて、コアを守る設計」)
クリーンアーキテクチャを初心者目線でまとめると、こうなります。
アプリの「本質的なルール(ドメイン・ユースケース)」を中心に置き、フレームワーク・DB・外部サービスなどの「技術的なもの」を外側に追い出し、外側が内側に依存するようにする設計思想である。
依存性逆転によって、ビジネスロジックが具体的な技術に縛られなくなり、テストしやすく、変更に強い構造になる。
完璧な円やレイヤーを最初から目指す必要はなく、「ビジネスロジックをフレームワークや DB から分離する」「ユースケース関数を作る」といった小さな一歩からでも、十分に効果が出る。
