Моделирование предметной области
Domain 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
# ❌ Модель без поведения
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
# Проблемы:
# - Модель — просто "мешок с данными"
# - Логика размазана по сервисам
# - Нарушение инкапсуляции
# ✅ Модель с поведением
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
# Преимущества:
# - Логика рядом с данными
# - Инкапсуляция
# - Легче понять и поддерживать
# 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
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 — это не про данные. Это про бизнес.
Domain Model отражает сущности и правила реального мира, а не структуру базы данных."