Martin Fowler が『Patterns of Enterprise Application Architecture』(2002)で整理した、業務ロジックの 編成方法に関する2つの対照的なアプローチ。「クリーンアーキテクチャ や DDD を毎回適用すべきか」を判断する軸になる。
トランザクションスクリプト(Transaction Script)¶
1つのビジネストランザクション(ユースケース)を1つの手続き(関数)としてそのまま書くスタイル。
def place_order(order_id, customer_id, items, db):
total = sum(item.price * item.qty for item in items)
if total > get_customer_credit_limit(customer_id, db):
raise CreditLimitExceeded()
db.execute("INSERT INTO orders ...", order_id, customer_id, total)
for item in items:
db.execute("INSERT INTO order_items ...", order_id, item.id, item.qty)
reduce_inventory(items, db)
send_confirmation_email(customer_id, order_id)メリット:理解しやすい。処理の流れを上から下に読めばよい。単純なCRUD中心のシステムでは生産性が高い
デメリット:ロジックが複数のスクリプトに重複しやすい。ビジネスルールが複雑になるほど、条件分岐だらけの 長い手続きに膨らみやすい(SLAP 違反や低凝集に陥りやすい)
ドメインモデル(Domain Model)¶
データと振る舞いを一体化したオブジェクトのネットワークとしてロジックを表現するスタイル。DDD の戦術的パターン(エンティティ、値オブジェクト、集約)はこのスタイルを実践するための語彙を提供する。
class Order:
def __init__(self, customer: Customer, items: list[OrderItem]):
self.customer = customer
self.items = items
def total(self) -> Money:
return sum((item.subtotal() for item in self.items), Money.zero())
def place(self, inventory: Inventory):
if self.total() > self.customer.credit_limit:
raise CreditLimitExceeded()
inventory.reserve(self.items)
self.status = OrderStatus.PLACEDメリット:ビジネスルールが該当するオブジェクトの近くに集まる(高凝集)。ルールの再利用や単体テストがしやすい
デメリット:オブジェクト間の関係やO/Rマッピングの設計コストが高い。単純なドメインに適用すると過剰設計になる