Domain‑Driven Design (DDD) – это набор принципов и практик, позволяющих построить программную систему, тесно соответствующую бизнес‑требованиям. При правильном применении DDD упрощает коммуникацию между разработчиками и экспертами предметной области, делает код более понятным и облегчает масштабирование.
Ключевые понятия DDD
Основные элементы DDD:
- Ubiquitous Language – общий язык, используемый всеми участниками проекта.
- Bounded Context – граница, внутри которой определён один набор моделей и терминов.
- Entity – объект с уникальной идентификацией, меняющий своё состояние.
- Value Object – неизменяемый объект, определяемый своей ценностью.
- Aggregate – кластер связанных сущностей, управляемый корневым агрегатом.
- Repository – абстракция доступа к агрегатам.
- Domain Service – сервис, инкапсулирующий бизнес‑логику, не принадлежащую отдельной сущности.
DDD в монолитных приложениях
В монолитной архитектуре DDD помогает разделить код на логически независимые модули, каждый из которых отвечает за свой Bounded Context. Это достигается за счёт организации слоёв (Presentation → Application → Domain → Infrastructure) и использования паттернов репозитория и фабрики.
// PHP: пример Entity и Value Object
class Money
{
private float $amount;
private string $currency;
public function construct(float $amount, string $currency)
{
$this->amount = $amount;
$this->currency = $currency;
}
public function getAmount(): float { return $this->amount; }
public function getCurrency(): string { return $this->currency; }
}
class Order
{
private int $id;
private Customer $customer;
private Money $total;
public function construct(int $id, Customer $customer, Money $total)
{
$this->id = $id;
$this->customer = $customer;
$this->total = $total;
}
// Геттеры и бизнес‑логика
public function getTotal(): Money { return $this->total; }
}
Репозиторий реализуется как отдельный слой, позволяющий менять источник данных без влияния на доменную логику.
DDD в микросервисных архитектурах
При переходе к микросервисам каждый Bounded Context обычно становится отдельным сервисом. Это приводит к нескольким важным изменениям:
- Сервис отвечает только за один контекст, что повышает автономность.
- Взаимодействие между сервисами происходит через асинхронные сообщения или API‑контракты.
- Domain Model внутри сервиса остаётся неизменным, но теперь он «выживает» в распределённой среде.
Ключевой момент – согласованность границ контекстов. При проектировании микросервисов необходимо явно определить, какие данные являются «домены», а какие – «служебные».
# Python: микросервис с FastAPI, демонстрирующий Domain Service
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class OrderDTO(BaseModel):
id: int
customer_id: int
total: float
class OrderDomainService:
def create_order(self, dto: OrderDTO) -> dict:
# Здесь могла бы быть сложная бизнес‑логика
if dto.total <= 0:
raise ValueError('Total must be positive')
# Сохранение в репозитории (условно)
return {"status": "created", "order_id": dto.id}
service = OrderDomainService()
@app.post("/orders")
def create_order(order: OrderDTO):
try:
result = service.create_order(order)
return result
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
В примере сервис инкапсулирует бизнес‑правила, а контроллер отвечает лишь за транспортный слой.
Стратегический дизайн: согласование монолита и микросервисов
При миграции от монолита к микросервисам рекомендуется использовать Strangler Fig Pattern. Сначала выделяют отдельные Bounded Contexts, реализуете их как микросервисы и постепенно перенаправляете запросы через фасад.
# Пример роутинга запросов через API‑Gateway (pseudo‑code)
if request.path.startswith('/orders'):
forward_to('order-service')
else:
forward_to('legacy-monolith')
Такой подход позволяет поддерживать работоспособность системы во время перехода и минимизировать риски.
Технические детали интеграции
Для согласования данных между сервисами используют:
- Событийный брокер (Kafka, RabbitMQ) – публикация доменных событий.
- Схемы данных (Avro, Protobuf) – гарантируют совместимость контрактов.
- API‑контракты (OpenAPI/Swagger) – документируют взаимодействие.
Важно, чтобы каждый сервис хранил только свои агрегаты, а копии данных из других контекстов были «read‑only» и обновлялись через события.
Типичные подводные камни и как их избежать
- Слишком тонкая грануляция контекстов. Деление на слишком мелкие микросервисы приводит к повышенной связанности и сложности оркестрации.
- Дублирование бизнес‑логики. При неправильном разделении иногда одна и та же логика реализуется в нескольких сервисах.
- Неправильное управление транзакциями. В микросервисах нельзя полагаться на распределённые ACID‑транзакции; используйте SAGA‑паттерн.
Регулярные ревью контекстов и автоматическое тестирование помогают поддерживать чистоту модели.
Заключение
Domain‑Driven Design – мощный инструмент, который одинаково эффективен как в монолитных, так и в микросервисных системах. Главное – правильно определить границы контекстов, поддерживать Ubiquitous Language и использовать стратегический подход к миграции.
Услуги RybinskLab
RybinskLab предлагает полный спектр услуг по разработке и трансформации бизнес‑приложений: от проектирования доменных моделей и построения монолитных систем до создания масштабируемых микросервисных архитектур с применением DDD. Наши эксперты помогут сформулировать грамотный Ubiquitous Language, определить Bounded Contexts и реализовать надёжные сервисы на PHP, Python и других современных стеков.