Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

トランザクションスクリプト vs ドメインモデル

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マッピングの設計コストが高い。単純なドメインに適用すると過剰設計になる

使い分け

  • ロジックが単純なCRUDや、一度きりのバッチ処理が中心:トランザクションスクリプトで十分。 YAGNI の観点からもドメインモデルは過剰

  • ビジネスルールが複雑で、条件分岐が絡み合うコアドメイン:ドメインモデル(+ DDD)が保守性で優る

同一システム内でも、コアドメインはドメインモデルで、周辺の単純なCRUD機能はトランザクションスクリプトで、 というように使い分けることも多い(DDDの「コアドメイン」の考え方に合致する)。