見出し画像

ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本

ドメイン駆動設計(Domain-Driven Design、以下DDD)は、複雑な業務システムの開発において、その効果が広く認められている手法。
しかし、その主要な解説書である『エリック・エヴァンスのドメイン駆動設計』や『実践ドメイン駆動設計』は専門的で難解な部分も多く、初心者にはハードルが高い。

そこで、初心者にも理解しやすくDDDの基本を解説してくれるのが、本書『ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本』。

ドメイン駆動設計とは

ドメイン、ドメインモデル、ドメインオブジェクト

まず、DDDを理解する上で重要な用語「ドメイン」、「ドメインモデル」、「ドメインオブジェクト」について。

ドメイン
ソフトウェアが解決しようとしている問題領域のこと。
その領域におけるルール、プロセス、知識、用語などの総体。
例えば、ECサイトであれば「商品の販売と購入に関する一連のプロセスやルール」がドメインとなる。

ドメインモデル
ドメイン内の物や概念を抽象化し、その関係性を整理したもの。
ECサイトの例では、「商品」「顧客」「注文」「配送」などがドメインモデルに該当する。

ドメインオブジェクト
ドメインモデルをプログラムとして表現したもの。
DDDでは、変更容易性や保守性を考慮し、幾つかの設計パターンが提案されている。

ドメイン駆動設計のアプローチ

開発者がドメイン知識を軽視すると、顧客の問題を的確に解決するソフトウェアを作ることは困難となる。
開発者はドメインと顧客への理解を深め、その知識を設計や実装に反映させることが求められる。
そこで、DDDは、ドメインに関する深い理解を基にソフトウェア設計を進めていく。

本書の解説範囲

DDDは実践的な手法であり、実際に開発現場やドメインに関わることでその効果を実感できる。
そのため書籍だけでDDDを完全に習得するのは難しい。
そこで本書ではDDDの基礎となる実装パターン(値オブジェクト、エンティティ、集約、ドメインサービスなど)に焦点を当て、これらのパターンの意図と効果を理解することで、DDDを実践するための土台を築くことを目的としている。

ドメインオブジェクトの基本 : 値オブジェクト

値オブジェクトは、ドメイン内で重要な意味を持つ「値」を表現するためのパターン。
その特徴は以下の通り。

不変性 : 一度生成された値オブジェクトの状態は変更されない
交換可能性 : 値オブジェクト同士の交換は可能である
等価性による比較 : 値オブジェクト同士の比較は、その値の等価性に基づく

住所の例

物流サービスにおける「住所」を例に、値オブジェクトの利点を見てみる。

値オブジェクトを使わない場合

class Customer:
    def __init__(self, name, street, city, postal_code, country):
        self.name = name
        self.street = street
        self.city = city
        self.postal_code = postal_code
        self.country = country

このように、住所情報をプリミティブ型(文字列など)で保持していると、住所を扱うたびに個々の属性にアクセスしなければならない。

例えば、配送先の住所と顧客の住所が一致しているか確認するコードは以下のようになる。

# 顧客オブジェクトの作成
customer = Customer("John Doe", "123 Main St", "Tokyo", "1000001", "Japan")

# 通知された配送先の情報
notified_address = {
    "street": "123 Main St",
    "city": "Tokyo",
    "postal_code": "1000001",
    "country": "Japan"
}

# 住所の一致を確かめる関数
def is_address_matching(customer, notified_address):
    return (customer.street == notified_address["street"] and
            customer.city == notified_address["city"] and
            customer.postal_code == notified_address["postal_code"] and
            customer.country == notified_address["country"])

# 一致確認
if is_address_matching(customer, notified_address):
    print("配送先の住所は購入者の住所と一致しています。")
else:
    print("配送先の住所は購入者の住所と一致していません。")

このように、住所の比較や検証を行うたびに冗長なコードを書かなければならず、保守性が低下する。

値オブジェクトを使う場合

値オブジェクトとして住所を定義し、バリデーションや比較のロジックをカプセル化する。

from dataclasses import dataclass

@dataclass(frozen=True)
class Address:
    street: str
    city: str
    postal_code: str
    country: str

    def __post_init__(self):
        self._validate()

    def _validate(self):
        # 郵便番号のバリデーション(日本の郵便番号形式をチェック)
        if not self.postal_code.isdigit() or len(self.postal_code) != 7:
            raise ValueError("郵便番号は7桁の数字でなければなりません。")
        # 国のチェック(日本のみ許容)
        if self.country != "Japan":
            raise ValueError("このシステムでは日本の住所のみを扱います。")

Customerクラスの修正

class Customer:
    def __init__(self, name, address: Address):
        self.name = name
        self.address = address

「住所」という重要な概念を値オブジェクトで実装することによって、以下のような利点が得られる。

# 値オブジェクトの使用
customer_address = Address("123 Main St", "Tokyo", "1000001", "Japan")
customer = Customer("John Doe", customer_address)
notified_address = Address("123 Main St", "Tokyo", "1000001", "Japan")

if customer.address == notified_address:
    print("配送先の住所は購入者の住所と一致しています。")
else:
    print("配送先の住所は購入者の住所と一致していません。")

簡潔な比較:住所の比較がシンプルになる
バリデーションの一元化:住所に関するバリデーションロジックがAddressクラスに集約され、他の部分で再実装する必要がなくなる。
不変性による信頼性:Addressクラスが不変であるため、一度正しく生成された住所オブジェクトは常に正しい状態を保つ。

どこまで値オブジェクト化すべきか

住所の各属性(通り、都市、郵便番号、国)をさらに個別の値オブジェクトとすることも可能だが、それが必要かどうかはドメインの要件次第。
システムでそれらの属性を個別に詳細に扱う必要がある場合は、値オブジェクト化する価値がある。
DDDでは、このようにドメインの理解に基づいて設計を検討することが重要で、また難しい部分でもある。

感想

値オブジェクト、エンティティ、ドメインサービス、アプリケーションサービスなど、DDDにおける主要な実装パターンを具体的なコード例とともに分かりやすく解説してくれている。
また、プロジェクトの構成やアプリケーションをゼロから作成する手順なども紹介されており、実践的な内容が豊富なのも嬉しい。
この本で基礎を固めた上で、『エリック・エヴァンスのドメイン駆動設計』や『実践ドメイン駆動設計』に進むことで、より深い理解が得られるのではないかと思う。

まさしく、DDDの入門書として最適な一冊だと思う。おすすめ。
※ちなみに、この記事pythonでコード例を出したが、「ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本」中はC# が用いられている。


いいなと思ったら応援しよう!

この記事が参加している募集