✍️

【ドメイン駆動設計】値オブジェクト「価格」の設計例

2023/12/03に公開

🎯目的

ドメイン駆動設計の具体例を記載し、ドメイン駆動設計を実践できるようにする

💡前提

この記事では、実践ドメイン駆動設計 | ヴォーン・ヴァーノン, 髙木 正弘 |本 | 通販 | Amazonを参考にしています。

💡「価格」とは

ここではECサイトで販売している商品の「価格」を取り上げます。尚、ここでの設計はあくまで例です。通常は扱うドメイン領域やユビキタス言語によって、設計が変わります。

商品の価格には、次の種類があります。

  • 通常価格
  • 割引価格
         - 期間限定での割引
         - 「5,000円以上のお買い上げで〜」などの条件付き割引
  • 会員価格
    • ECサイトで会員になっている人限定の価格
    • 会員のランク(プレミアム、スタンダード、...)などによって価格が変わることもある


通常価格


割引価格


会員価格

✍️「価格」の設計

✍️自己参照方式の実装パターン

価格
from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from typing import Optional


@dataclass(init=False, unsafe_hash=True, frozen=False)
class Price:
    amount: float
    currency: Currency
    type: PriceType
    other_price: Optional[Price]

    class PriceType(Enum):
        NORMAL_PRICE = "通常価格"
        DISCOUNT_PRICE = "割引価格"
        MEMBER_PRICE = "会員価格"

    class Currency(Enum):
        JPY = "日本円"
        USD = "米ドル"

    def __init__(self, amount: float, currency: Price.Currency, type: PriceType = PriceType.NORMAL_PRICE):
        assert (isinstance(amount, float) or isinstance(amount, int)) and amount >= 0, \
            "amountには0以上のfloat/int型を指定してください。"
        assert isinstance(currency, Price.Currency), "currencyにはPrice.Currency型を指定してください。"
        assert isinstance(type, Price.PriceType), "typeにはPrice.PriceType型を指定してください。"
        super().__setattr__("amount", amount)
        super().__setattr__("currency", currency)
        super().__setattr__("type", type)
        super().__setattr__("other_price", None)

    def set_other_price(self, price: Price) -> Price:
        self.other_price = price
        return self.other_price

    def type_of(self, price_type: PriceType) -> Optional[Price]:
        if self.type == price_type:
            return self

        if self.other_price is not None:
            return self.other_price.type_of(price_type)

        return None

✍️インターフェース方式の実装パターン

書き途中

価格(インターフェース)
class Price(abc.ABC):
    @abc.abstractmethod
    def type(self) -> PriceType:
        pass

    @abc.abstractmethod
    def match(self, consumer) -> bool:
        pass

🔗APPENDIX

Discussion