Фокус на предметной области (Domain) в DDD

Начало и оглавление здесь

Фокус на предметной области (Domain) в DDD означает, что разработка сосредотачивается на моделировании бизнес-логики и правил реального мира, а не на технических деталях (БД, фреймворки).

Если коротко, то основной смысл в следующем:

  • Предметная область (Domain) — это сфера бизнеса, которую решает приложение (финансы, e-commerce, медицина).
  • Код отражает реальные процессы и термины этой области, а не инфраструктуру.
  • Программисты и эксперты бизнеса совместно создают общий язык (Ubiquitous Language).

Реализация Domain с примерами на Python

1. Сущности (Entities)

Сущности - это объекты, которые имеют идентичность и жизненный цикл. Они определяются не своими атрибутами, а своей идентичностью.

class Product:
    def __init__(self, product_id: str, name: str, price: float):
        self.product_id = product_id  # Идентификатор - ключевое свойство сущности
        self.name = name
        self.price = price
    
    def change_price(self, new_price: float):
        if new_price <= 0:
            raise ValueError("Price must be positive")
        self.price = new_price
    
    def __eq__(self, other):
        if not isinstance(other, Product):
            return False
        return self.product_id == other.product_id

2. Объекты-значения (Value Objects)

Объекты-значения определяются только своими атрибутами и не имеют идентичности.

class Address:
    def __init__(self, street: str, city: str, zip_code: str):
        self.street = street
        self.city = city
        self.zip_code = zip_code
    
    def __eq__(self, other):
        if not isinstance(other, Address):
            return False
        return (self.street == other.street and 
                self.city == other.city and 
                self.zip_code == other.zip_code)

3. Агрегаты (Aggregates)

Агрегат - это кластер связанных объектов, которые рассматриваются как единое целое.

class OrderItem:
    def __init__(self, product: Product, quantity: int):
        self.product = product
        self.quantity = quantity
        self.validate()
    
    def validate(self):
        if self.quantity <= 0:
            raise ValueError("Quantity must be positive")
    
    def total_price(self):
        return self.product.price * self.quantity

class Order:
    def __init__(self, order_id: str, customer_id: str):
        self.order_id = order_id
        self.customer_id = customer_id
        self.items = []
        self.status = "created"
    
    def add_item(self, product: Product, quantity: int):
        # Проверяем, нет ли уже этого продукта в заказе
        for item in self.items:
            if item.product == product:
                item.quantity += quantity
                item.validate()
                return
        
        # Если продукта еще нет, добавляем новый элемент
        new_item = OrderItem(product, quantity)
        self.items.append(new_item)
    
    def total_amount(self):
        return sum(item.total_price() for item in self.items)
    
    def confirm(self):
        if not self.items:
            raise ValueError("Cannot confirm empty order")
        self.status = "confirmed"
    
    def cancel(self):
        self.status = "cancelled"

4. Репозитории (Repositories)

Репозитории предоставляют интерфейс для доступа к агрегатам.

from abc import ABC, abstractmethod

class OrderRepository(ABC):
    @abstractmethod
    def save(self, order: Order):
        pass
    
    @abstractmethod
    def get_by_id(self, order_id: str) -> Order:
        pass
    
    @abstractmethod
    def get_all(self) -> list[Order]:
        pass

class InMemoryOrderRepository(OrderRepository):
    def __init__(self):
        self.orders = {}
    
    def save(self, order: Order):
        self.orders[order.order_id] = order
    
    def get_by_id(self, order_id: str) -> Order:
        return self.orders.get(order_id)
    
    def get_all(self) -> list[Order]:
        return list(self.orders.values())

5. Сервисы предметной области (Domain Services)

Сервисы содержат логику, которая не принадлежит ни одной сущности или объекту-значению.

class OrderService:
    def __init__(self, order_repository: OrderRepository):
        self.order_repository = order_repository
    
    def create_order(self, customer_id: str) -> Order:
        order = Order(str(uuid.uuid4()), customer_id)
        self.order_repository.save(order)
        return order
    
    def add_product_to_order(self, order_id: str, product: Product, quantity: int):
        order = self.order_repository.get_by_id(order_id)
        if not order:
            raise ValueError("Order not found")
        
        order.add_item(product, quantity)
        self.order_repository.save(order)
    
    def confirm_order(self, order_id: str):
        order = self.order_repository.get_by_id(order_id)
        if not order:
            raise ValueError("Order not found")
        
        order.confirm()
        self.order_repository.save(order)

6. Фабрики (Factories)

Фабрики отвечают за создание сложных объектов и агрегатов.

class OrderFactory:
    @staticmethod
    def create_order(customer_id: str, items: list[tuple[Product, int]]) -> Order:
        order = Order(str(uuid.uuid4()), customer_id)
        for product, quantity in items:
            order.add_item(product, quantity)
        return order

7. События предметной области (Domain Events)

События представляют факты, которые произошли в предметной области.

class DomainEvent(ABC):
    @property
    @abstractmethod
    def occurred_on(self):
        pass

class OrderConfirmedEvent(DomainEvent):
    def __init__(self, order_id: str, customer_id: str, total_amount: float):
        self.order_id = order_id
        self.customer_id = customer_id
        self.total_amount = total_amount
        self._occurred_on = datetime.now()
    
    @property
    def occurred_on(self):
        return self._occurred_on

class Order:
    # ... предыдущий код ...
    
    def confirm(self):
        if not self.items:
            raise ValueError("Cannot confirm empty order")
        self.status = "confirmed"
        self.events.append(
            OrderConfirmedEvent(self.order_id, self.customer_id, self.total_amount())
        )
    
    def collect_events(self):
        events = self.events.copy()
        self.events.clear()
        return events

P.S.
Фокус на предметной области в DDD позволяет создавать гибкие и поддерживаемые системы, которые точно отражают бизнес-требования. Python с его гибкостью и выразительностью отлично подходит для реализации DDD-подхода.

  • Центрируйте разработку вокруг доменной модели.
  • Четко разделяйте ответственности между компонентами.
  • Инкапсулируйте бизнес-правила в доменных объектах.
  • Управляйте сложностью через ограниченные контексты.
  • Избегайте анемичных моделей.