Что такое DDD (Domain-Driven Design)?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое DDD (Domain-Driven Design)
DDD (Domain-Driven Design) — это методология проектирования архитектуры приложений, которая фокусируется на глубоком понимании бизнес-логики (домена) и отражении этой логики в коде. Концепция была введена Эриком Эвансом в его книге "Domain-Driven Design: Tackling Complexity in the Heart of Software" (2003) и остаётся актуальной и в 2025 году.
Основная идея
Вместо того, чтобы проектировать систему через призму технических слоёв (БД, фреймворки, UI), DDD предлагает обратный подход:
- Понимаешь бизнес — какие процессы, правила, сущности
- Проектируешь домен — моделируешь эти процессы в коде
- Обворачиваешь технологиями — БД, фреймворки становятся деталями реализации
Традиционный подход: DDD подход:
Database Business Logic (Domain)
↓ ↓
Data Models Domain Models (Entities, Value Objects)
↓ ↓
Services Services (Use Cases)
↓ ↓
Controllers Controllers (Adapters)
Ключевые понятия DDD
1. Entity (Сущность)
Объект с уникальным идентификатором, жизненный цикл которого важен:
public class Order {
private OrderId id; // Уникальный идентификатор
private CustomerId customerId;
private List<OrderItem> items;
private OrderStatus status;
private LocalDateTime createdAt;
public Order(OrderId id, CustomerId customerId) {
this.id = id;
this.customerId = customerId;
this.status = OrderStatus.PENDING;
this.items = new ArrayList<>();
}
public void addItem(Product product, int quantity) {
if (this.status != OrderStatus.PENDING) {
throw new OrderAlreadyConfirmedException();
}
this.items.add(new OrderItem(product, quantity));
}
public OrderId getId() {
return id;
}
}
2. Value Object (Объект-значение)
Объект без идентификатора, определяемый своими атрибутами. Неизменяемый (immutable):
public class Money {
private final BigDecimal amount;
private final String currency;
public Money(BigDecimal amount, String currency) {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new InvalidMoneyException("Amount cannot be negative");
}
this.amount = amount;
this.currency = currency;
}
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new CurrencyMismatchException();
}
return new Money(this.amount.add(other.amount), this.currency);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
return amount.equals(money.amount) && currency.equals(money.currency);
}
}
3. Aggregate (Агрегат)
Группа связанных объектов (Entity + Value Objects), которые рассматриваются как одна единица. Имеет корневую сущность (Aggregate Root):
public class Order { // Aggregate Root
private OrderId id;
private CustomerId customerId;
private List<OrderItem> items; // Value Objects
private Money totalAmount;
private OrderStatus status;
// Order отвечает за консистентность всего агрегата
public void addItem(Product product, int quantity) {
// Проверяем правила бизнеса
if (this.status != OrderStatus.PENDING) {
throw new OrderAlreadyConfirmedException();
}
OrderItem item = new OrderItem(product, quantity);
items.add(item);
recalculateTotalAmount();
}
private void recalculateTotalAmount() {
this.totalAmount = items.stream()
.map(OrderItem::getPrice)
.reduce(new Money(BigDecimal.ZERO, "USD"), Money::add);
}
}
4. Repository (Репозиторий)
Абстракция для доступа к данным. Работает с Aggregates, скрывая детали БД:
public interface OrderRepository {
void save(Order order);
Order findById(OrderId id);
void delete(OrderId id);
}
@Repository
public class JpaOrderRepository implements OrderRepository {
@Autowired
private JpaOrderEntity repo;
@Override
public void save(Order order) {
JpaOrderEntity entity = OrderMapper.toPersistence(order);
repo.save(entity);
}
@Override
public Order findById(OrderId id) {
return repo.findById(id.getValue())
.map(OrderMapper::toDomain)
.orElseThrow(() -> new OrderNotFoundException(id));
}
}
5. Service (Сервис)
Бизнес-логика, которая не относится к одному Entity или Aggregate:
@Service
public class CreateOrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private ProductRepository productRepository;
@Transactional
public OrderId createOrder(CustomerId customerId, List<CreateOrderItem> items) {
Order order = new Order(new OrderId(UUID.randomUUID()), customerId);
for (CreateOrderItem item : items) {
Product product = productRepository.findById(item.getProductId())
.orElseThrow(() -> new ProductNotFoundException());
order.addItem(product, item.getQuantity());
}
orderRepository.save(order);
return order.getId();
}
}
6. Domain Event (Доменное событие)
Событие, которое произошло в домене и важно для бизнеса:
public class OrderCreatedEvent {
private final OrderId orderId;
private final CustomerId customerId;
private final LocalDateTime occurredAt;
public OrderCreatedEvent(OrderId orderId, CustomerId customerId) {
this.orderId = orderId;
this.customerId = customerId;
this.occurredAt = LocalDateTime.now();
}
public OrderId getOrderId() { return orderId; }
public CustomerId getCustomerId() { return customerId; }
}
public class Order {
private List<DomainEvent> events = new ArrayList<>();
public void create() {
// ... создание логики
events.add(new OrderCreatedEvent(this.id, this.customerId));
}
public List<DomainEvent> getAndClearEvents() {
List<DomainEvent> result = new ArrayList<>(events);
events.clear();
return result;
}
}
Слои DDD архитектуры
┌─────────────────────────────────┐
│ Presentation (Controllers, Views)
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ Application (Use Cases, DTOs) ← Оркестрирует домен
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ Domain (Entities, Services) ← Бизнес-логика
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ Infrastructure (DB, External) ← Реализация деталей
└─────────────────────────────────┘
Когда использовать DDD
✅ Используй DDD когда:
- Проект большой и сложный
- Бизнес-логика нетривиальна
- Много правил и ограничений
- Нужна долгосрочная поддерживаемость
- Команда готова инвестировать время в понимание домена
❌ Не используй DDD когда:
- Маленький MVP
- Простой CRUD-функционал
- Один человек на проекте
- Нет возможности общаться с бизнесом
Плюсы и минусы
Плюсы:
- Код отражает бизнес-логику
- Легче общаться с не-программистами
- Проще поддерживать сложные системы
- Естественная модульность
Минусы:
- Много кода (Entities, Value Objects, Services)
- Кривая обучения
- Overkill для простых проектов
- Требует дисциплины от команды
DDD — это не про выбор technology stack, а про способ мышления о проблеме. Это инвестиция в качество кода на долгосрочной перспективе.