Что такое DDD (Domain-Driven Design)?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое DDD (Domain-Driven Design)
DDD — это методология проектирования архитектуры программного обеспечения, которая фокусируется на глубоком понимании бизнес-логики (домена) и переводит её в код. DDD помогает создавать системы, которые тесно связаны с реальными бизнес-процессами.
Основной принцип DDD
Делай код похожим на язык бизнеса, не наоборот. Если у вас есть термины вроде "заказ", "покупатель", "платёж" — они должны быть видны в коде как классы и методы.
Ключевые концепции DDD
1. Ubiquitous Language (Всеобщий язык)
Все участники проекта (разработчики, бизнес-аналитики, клиенты) используют один и тот же словарь. Это предотвращает недопонимание.
# Неправильно — назва не отражает бизнес
class Order:
def process(self):
self.status = "processed"
# Правильно — названия из Ubiquitous Language
class Order:
def confirm_payment(self) -> None:
"""Подтверждает платёж заказа в системе"""
self.status = OrderStatus.PAYMENT_CONFIRMED
self.paid_at = datetime.now(UTC)
2. Entities (Сущности)
Объекты с уникальным идентификатором. Их личность важнее состояния.
from dataclasses import dataclass
from uuid import UUID
@dataclass
class Customer:
"""Клиент системы — сущность с уникальным ID"""
id: UUID
email: str
phone: str
def __eq__(self, other):
# Два клиента — один и тот же, если у них одинаковый ID
if not isinstance(other, Customer):
return False
return self.id == other.id
def __hash__(self):
return hash(self.id)
3. Value Objects (Объекты-значения)
Объекты без уникального идентификатора. Их личность определяется значением всех атрибутов.
from dataclasses import dataclass
from typing import NewType
Money = NewType('Money', float)
@dataclass(frozen=True) # Неизменяемость
class Price:
"""Объект-значение: цена не зависит от ID, важно значение"""
amount: Money
currency: str
def __eq__(self, other):
# Две цены равны, если равны amount и currency
if not isinstance(other, Price):
return False
return self.amount == other.amount and self.currency == other.currency
def add(self, other: 'Price') -> 'Price':
if self.currency != other.currency:
raise ValueError("Cannot add prices in different currencies")
return Price(self.amount + other.amount, self.currency)
4. Aggregates (Агрегаты)
Группа сущностей и объектов-значений, которые работают вместе и имеют один корень (root entity). Агрегат — это граница транзакции и консистентности.
from dataclasses import dataclass, field
from typing import List
from uuid import UUID, uuid4
from enum import Enum
class OrderStatus(Enum):
PENDING = "pending"
CONFIRMED = "confirmed"
SHIPPED = "shipped"
DELIVERED = "delivered"
@dataclass
class OrderItem:
"""Value Object — элемент заказа"""
product_id: UUID
quantity: int
price: Price
@dataclass
class Order: # Aggregate Root
"""Корень агрегата Order"""
id: UUID
customer_id: UUID
items: List[OrderItem] = field(default_factory=list)
status: OrderStatus = OrderStatus.PENDING
total_price: Price = field(default_factory=lambda: Price(0, "USD"))
def add_item(self, item: OrderItem) -> None:
"""Методы агрегата управляют консистентностью"""
if self.status != OrderStatus.PENDING:
raise ValueError("Cannot add items to non-pending order")
self.items.append(item)
self.total_price = self.total_price.add(item.price)
def confirm(self) -> None:
if not self.items:
raise ValueError("Cannot confirm empty order")
self.status = OrderStatus.CONFIRMED
5. Domain Services (Доменные сервисы)
Операции, которые не принадлежат ни одной сущности или объекту-значению. Например, расчёт скидки, проверка доступности товара.
from abc import ABC, abstractmethod
class PricingService:
"""Доменный сервис для расчёта цен"""
def calculate_discount(self, customer_lifetime_value: Money) -> float:
"""Рассчитывает скидку на основе жизненной стоимости клиента"""
if customer_lifetime_value > 10000:
return 0.15 # 15% скидка
elif customer_lifetime_value > 5000:
return 0.10 # 10% скидка
return 0.0
class InventoryService:
"""Доменный сервис для управления инвентарём"""
def __init__(self, repository):
self.repository = repository
def is_product_available(self, product_id: UUID, quantity: int) -> bool:
"""Проверяет доступность товара"""
product = self.repository.find(product_id)
return product.stock >= quantity
6. Repositories (Хранилища)
Абстракции для получения и сохранения агрегатов. Скрывают детали персистентности.
from abc import ABC, abstractmethod
class OrderRepository(ABC):
"""Интерфейс хранилища заказов"""
@abstractmethod
def save(self, order: Order) -> None:
pass
@abstractmethod
def find_by_id(self, order_id: UUID) -> Order:
pass
@abstractmethod
def find_by_customer(self, customer_id: UUID) -> List[Order]:
pass
class SQLOrderRepository(OrderRepository):
"""Конкретная реализация с БД"""
def __init__(self, db_session):
self.db_session = db_session
def save(self, order: Order) -> None:
# Преобразуем доменный объект в ORM модель
db_order = OrderModel(...)
self.db_session.add(db_order)
self.db_session.commit()
def find_by_id(self, order_id: UUID) -> Order:
db_order = self.db_session.query(OrderModel).filter_by(id=order_id).first()
if not db_order:
raise OrderNotFound()
# Преобразуем ORM модель в доменный объект
return self._to_domain(db_order)
7. Use Cases / Application Services (Прикладные сервисы)
Реализуют бизнес-сценарии, используя доменные объекты и сервисы.
from dataclasses import dataclass
@dataclass
class CreateOrderRequest:
customer_id: UUID
items: List[dict] # [{"product_id": ..., "quantity": ...}]
class CreateOrderUseCase:
def __init__(self, order_repository: OrderRepository,
pricing_service: PricingService):
self.order_repository = order_repository
self.pricing_service = pricing_service
def execute(self, request: CreateOrderRequest) -> UUID:
# Создаём агрегат
order = Order(id=uuid4(), customer_id=request.customer_id)
# Добавляем товары с доменной логикой
for item_data in request.items:
item = OrderItem(
product_id=item_data["product_id"],
quantity=item_data["quantity"],
price=Price(item_data["price"], "USD")
)
order.add_item(item)
# Применяем скидку через доменный сервис
discount = self.pricing_service.calculate_discount(customer_lifetime_value=5000)
if discount > 0:
discounted_price = Price(order.total_price.amount * (1 - discount), "USD")
order.total_price = discounted_price
# Сохраняем агрегат
self.order_repository.save(order)
return order.id
Преимущества DDD
- Гибкость — изменения в бизнес-логике легко отражаются в коде
- Понятность — код читается как документация бизнес-процессов
- Масштабируемость — легко расширять новыми функциями
- Тестируемость — доменная логика не зависит от фреймворков
- Команда на одной волне — нет путаницы между разработчиками и бизнесом
Когда использовать DDD
- Сложная бизнес-логика
- Долгоживущие проекты
- Команда из 5+ разработчиков
- Часто меняющиеся требования
Когда НЕ использовать DDD
- CRUD приложения (блог, простой чат)
- Однодневные проекты
- Очень простая логика
DDD — это философия проектирования. Её стоит изучать и применять для сложных систем, где бизнес-логика — сердце приложения.