Domain Models

Моделирование предметной области

Что такое Domain Model?

Domain Model — модель предметной области, которая отражает сущности, их свойства, отношения и бизнес-правила реального мира.

🔹 Domain Model vs Data Model

# Data Model — отражает структуру БД
class UserTable:
    id: int
    name: str
    email: str
    created_at: datetime

# Domain Model — отражает бизнес-логику
class User:
    def __init__(self, user_id: int, name: str, email: Email):
        self.id = user_id
        self.name = name
        self.email = email  # Value Object
    
    def change_email(self, new_email: Email):
        # Бизнес-логика
        if self.email == new_email:
            return
        self.email = new_email
        self._domain_events.append(UserEmailChangedEvent(self.id, new_email))
    
    def is_premium(self) -> bool:
        # Бизнес-логика
        return self.balance.amount > 1000.0

🔹 Компоненты Domain Model

  • Entities — сущности с идентификатором
  • Value Objects — объекты-значения
  • Aggregates — группы связанных объектов
  • Domain Services — бизнес-логика вне Entity
  • Repositories — абстракция хранилища
  • Domain Events — события предметной области

Rich Domain Model

❌ Anemic Domain Model

# ❌ Модель без поведения
class User:
    def __init__(self):
        self.id = None
        self.name = None
        self.email = None
        self.balance = None

# Вся логика в сервисах
class UserService:
    def validate_user(self, user: User):
        return "@" in user.email
    
    def change_email(self, user: User, new_email: str):
        user.email = new_email
    
    def is_premium(self, user: User):
        return user.balance > 1000
    
    def upgrade_to_premium(self, user: User):
        if user.balance > 1000:
            user.premium = True

# Проблемы:
# - Модель — просто "мешок с данными"
# - Логика размазана по сервисам
# - Нарушение инкапсуляции

✅ Rich Domain Model

# ✅ Модель с поведением
class User:
    def __init__(self, user_id: int, name: str, email: Email, balance: Money):
        self.id = user_id
        self.name = name
        self.email = email  # Value Object
        self.balance = balance  # Value Object
        self._premium_features_enabled = False
    
    def change_email(self, new_email: Email):
        # Логика в модели
        if self.email == new_email:
            return
        self.email = new_email
        self._domain_events.append(UserEmailChangedEvent(self.id, new_email))
    
    def is_premium(self) -> bool:
        # Логика в модели
        return self.balance.amount > 1000.0
    
    def upgrade_to_premium(self):
        # Логика в модели
        if self.is_premium():
            raise ValueError("User is already premium")
        if not self._premium_features_enabled:
            self._premium_features_enabled = True
            self._domain_events.append(UserUpgradedToPremiumEvent(self.id))
    
    def validate(self) -> bool:
        # Валидация в модели
        return self.email.is_valid() and len(self.name) > 0

# Преимущества:
# - Логика рядом с данными
# - Инкапсуляция
# - Легче понять и поддерживать

Пример: E-Commerce Domain Model

🔹 Entities и Value Objects

# Value Objects
class Email:
    def __init__(self, address: str):
        if not self._is_valid(address):
            raise ValueError("Invalid email")
        self._address = address.lower()
    
    def _is_valid(self, address: str) -> bool:
        return "@" in address and "." in address.split("@")[1]

class Money:
    def __init__(self, amount: float, currency: str):
        if amount < 0:
            raise ValueError("Amount cannot be negative")
        self._amount = amount
        self._currency = currency

class Address:
    def __init__(self, street: str, city: str, country: str, zip_code: str):
        self._street = street
        self._city = city
        self._country = country
        self._zip_code = zip_code

# Entities
class User:
    def __init__(self, user_id: int, name: str, email: Email, address: Address):
        self.id = user_id
        self.name = name
        self.email = email
        self.address = address

Когда выбирать Domain Model

  • Сложный домен: много инвариантов, правил, алгоритмов, состояний.
  • Долгая жизнь продукта: эволюция требований, чтение/поддержка важнее скорости старта.
  • Команда > 3-5 человек: нужен общий язык (Ubiquitous Language) и чёткие границы.
  • Если CRUD, простой флоу, прототип → начните с Transaction Script/Active Record и эволюционируйте.

Domain Events

🔹 Domain Events

from dataclasses import dataclass
from datetime import datetime

@dataclass
class DomainEvent:
    aggregate_id: str
    event_type: str
    occurred_at: datetime
    data: dict

@dataclass
class UserEmailChangedEvent(DomainEvent):
    user_id: int
    old_email: str
    new_email: str

@dataclass
class OrderConfirmedEvent(DomainEvent):
    order_id: int
    user_id: int
    total: float

class User:
    def __init__(self, user_id: int, name: str, email: Email):
        self.id = user_id
        self.name = name
        self.email = email
        self._domain_events = []
    
    def change_email(self, new_email: Email):
        old_email = self.email
        self.email = new_email
        self._domain_events.append(
            UserEmailChangedEvent(
                aggregate_id=self.id,
                event_type="user_email_changed",
                occurred_at=datetime.now(),
                data={"old_email": str(old_email), "new_email": str(new_email)},
                user_id=self.id,
                old_email=str(old_email),
                new_email=str(new_email)
            )
        )
    
    def get_domain_events(self) -> List[DomainEvent]:
        events = self._domain_events.copy()
        self._domain_events.clear()
        return events

🎯 Ключевые принципы Domain Model

  1. Отражение бизнеса — модель отражает предметную область
  2. Rich Model — модель содержит бизнес-логику
  3. Инкапсуляция — защита инвариантов и бизнес-правил
  4. Независимость — модель не зависит от инфраструктуры
  5. Ubiquitous Language — код говорит на языке бизнеса
"Domain Model — это не про данные. Это про бизнес.
Domain Model отражает сущности и правила реального мира, а не структуру базы данных."