ドメイン駆動設計って何?一言でいうと「現実のルールをコードの中心に置く考え方」
ドメイン駆動設計(DDD)は、
「フレームワークやDBの都合ではなく、“現実世界のルール”を中心にコードを組み立てよう」
という設計の考え方です。
ここでいう「ドメイン」は、そのシステムが扱う“問題領域”のことです。
ネットショップなら「注文・在庫・支払い」、勤怠システムなら「出勤・退勤・休暇」などがドメインです。
DDDの一番大事なポイントは、次の二つです。
ビジネス側の言葉(現場の言葉)でモデル(クラス・関数)を作ること
そのモデルの中に「本当に守りたいルール(ビジネスルール)」を閉じ込めること
これができると、コードが「仕様書そのもの」に近づいていきます。
ドメインを「クラスとメソッド」に落とし込む感覚をつかむ
例:ネットショップの「カート」をドメインとして考える
シンプルなネットショップを想像してください。
「カートに商品を入れて、合計金額を計算する」というドメインです。
何も考えずに書くと、こうなりがちです。
def add_to_cart(cart: dict, item_id: int, price: int, quantity: int) -> None:
if item_id in cart:
cart[item_id]["quantity"] += quantity
else:
cart[item_id] = {"price": price, "quantity": quantity}
def calc_total(cart: dict) -> int:
total = 0
for item in cart.values():
total += item["price"] * item["quantity"]
return total
Python動きはしますが、「カート」という概念がバラバラに散らばっています。
ここにDDDの考え方を少し入れてみます。
カートを「ドメインモデル」としてクラスにする
from dataclasses import dataclass
@dataclass
class CartItem:
item_id: int
price: int
quantity: int
def add(self, quantity: int) -> None:
self.quantity += quantity
class Cart:
def __init__(self) -> None:
self._items: dict[int, CartItem] = {}
def add_item(self, item_id: int, price: int, quantity: int) -> None:
if item_id in self._items:
self._items[item_id].add(quantity)
else:
self._items[item_id] = CartItem(
item_id=item_id,
price=price,
quantity=quantity,
)
def total(self) -> int:
return sum(
item.price * item.quantity
for item in self._items.values()
)
Pythonここでやっていることはシンプルですが、意味が大きく変わっています。
「カート」という概念が Cart クラスとしてコード上に現れた
「カートの中の1行」が CartItem として表現された
「カートに商品を追加する」「合計金額を計算する」という振る舞いが、カート自身のメソッドになった
これが「ドメインをモデルとして表現する」というDDDの入り口です。
DDDの超重要キーワード:ユビキタス言語(共通言語)
DDDで一番大事なのは、実は「クラス図」ではなく「言葉」です。
ユビキタス言語(Ubiquitous Language)という考え方があります。
開発者とビジネス側(現場の人)が、
同じ言葉でドメインを話し、その言葉をそのままコードに使う、という考え方です。
例えば、現場でこういう会話がされているとします。
「カートに商品を追加する」
「カートの合計金額を出す」
この言葉を、そのままコードに持ち込みます。
add_itemtotal
もし現場で「カート行」「明細」と呼んでいるなら、CartItem ではなく LineItem と名付けるかもしれません。
大事なのは、「コードの名前が、現場の会話とズレないこと」です。
これができると、仕様変更の話がそのままコードの変更に対応しやすくなります。
「カートの合計金額に送料を含めたい」
→ Cart.total の仕様を変える、という話になる
「カート行ごとに割引を入れたい」
→ CartItem に割引のルールを持たせる、という話になる
この「言葉とコードが直結している状態」が、DDDの一番おいしいところです。
エンティティと値オブジェクトの違いを、Pythonでイメージする
エンティティ:同一性(ID)で区別されるもの
エンティティは、「IDで区別されるもの」です。
ユーザー、注文、カートなどが典型です。
from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
email: str
PythonUser は、id が変わらない限り「同じユーザー」です。
名前やメールアドレスが変わっても、「同じ人」として扱います。
値オブジェクト:値が同じなら同じもの
値オブジェクトは、「値が同じなら同じもの」として扱うものです。
お金、期間、メールアドレス、住所などが典型です。
from dataclasses import dataclass
@dataclass(frozen=True)
class Money:
amount: int
currency: str
def add(self, other: "Money") -> "Money":
if self.currency != other.currency:
raise ValueError("currency mismatch")
return Money(amount=self.amount + other.amount, currency=self.currency)
PythonMoney(1000, "JPY") が二つあったら、それは区別する必要がありません。
「1000円」という値そのものが重要です。
DDDでは、「これはエンティティか?値オブジェクトか?」を意識してモデリングします。
これによって、どこにルールを持たせるか、どこまで不変にするかが変わってきます。
DDDとテスト・品質の関係を具体的に見る
ドメインモデルは「フレームワーク抜き」でテストできる
さっきの Cart の例をテストしてみます。
def test_cart_total():
cart = Cart()
cart.add_item(item_id=1, price=1000, quantity=2)
cart.add_item(item_id=2, price=500, quantity=1)
assert cart.total() == 2500
Pythonここには、HTTP も DB も出てきません。
「カートというドメインのルール」だけをテストしています。
DDDでは、ドメインモデルをフレームワークやインフラから切り離して作るので、
こういう「純粋なドメインテスト」が書きやすくなります。
これは品質に直結します。
ビジネスルールの変更を、ドメインモデルのテストで守れる
フレームワークを変えても、ドメインのテストはそのまま使える
「ドメインを中心にテストを書く」という発想が、DDDと相性抜群です。
仕様変更が「ドメインモデルの変更」として見えるようになる
例えば、「カートの合計金額に10%の消費税を含めたい」という仕様変更が来たとします。
DDD的なコードなら、まずこう考えます。
「これはカートのルールだな」
→ Cart.total の仕様を変えるべきだな
class Cart:
TAX_RATE = 0.1
def total(self) -> int:
subtotal = sum(
item.price * item.quantity
for item in self._items.values()
)
return int(subtotal * (1 + self.TAX_RATE))
Pythonそして、テストもこう変えます。
def test_cart_total_with_tax():
cart = Cart()
cart.add_item(item_id=1, price=1000, quantity=2) # 2000
assert cart.total() == 2200 # 10%の税
Python仕様変更が、「どのクラスのどのメソッドを変える話なのか」がはっきりしている。
これがDDDの強さです。
DDDとクリーンアーキテクチャの関係をざっくり整理する
クリーンアーキテクチャとDDDは、よくセットで語られますが、役割が少し違います。
クリーンアーキテクチャ
「層」と「依存の向き」の話。
ドメインを中心に置き、外側にフレームワークやDBを追い出す考え方。
DDD
「ドメインそのものをどうモデル化するか」の話。
エンティティ、値オブジェクト、ドメインサービス、ユビキタス言語など。
ざっくり言うと、
クリーンアーキテクチャは「構造の話」
DDDは「中身(ドメイン)の話」
です。
Pythonでいうと、
クリーンアーキテクチャで「どのモジュールに何を置くか」を決める
DDDで「ドメインのクラスやメソッドをどう設計するか」を決める
という感じで組み合わせると、かなり強い設計になります。
初心者がDDDとどう付き合うといいか
いきなり「エンティティ」「集約」「境界づけられたコンテキスト」など、
DDDの全部を理解しようとすると、ほぼ確実に詰まります。
最初の一歩としては、次の二つだけ意識すれば十分です。
現場の言葉を、そのままクラス名・メソッド名にする
ビジネスルールを、ドメインモデル(クラス)の中に閉じ込める
例えば、今こういうコードがあったら、
def register_user(data: dict) -> None:
# ここにバリデーションやルールが全部書いてある
...
Pythonこれを少しだけDDD寄りにしてみます。
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そして、外側のコードは「Userを作ってvalidateするだけ」にする。
このくらいの小さな一歩でも、
「ドメインを中心に置く」感覚が少しずつ身についていきます。
まとめ(ドメイン駆動設計は「現場のルールをコードの主役にする」ための考え方)
ドメイン駆動設計を初心者目線でまとめると、こうなります。
ドメイン駆動設計は、「現実世界のルール(ドメイン)を中心に据え、そのルールを表すモデル(クラス・メソッド)をコードの主役にする」設計の考え方。
ユビキタス言語で現場の言葉とコードの名前を揃え、エンティティや値オブジェクトとしてドメインを表現することで、仕様変更が「どのクラスのどのメソッドを変える話か」が見えやすくなる。
ドメインモデルをフレームワークやDBから切り離しておくと、ドメインだけをテストできるようになり、ビジネスルールの変更にも強い、長生きするコードになっていく。

