Что такое Domain-Driven Design (DDD) и как вы его применяли?

Ответ

Domain-Driven Design (DDD) — это подход к разработке сложного программного обеспечения, который фокусируется на доменной логике (бизнес-правилах) и ее точном отражении в коде.

Ключевые строительные блоки DDD, которые я применял:

  1. Агрегат (Aggregate) — кластер связанных объектов (Entity и Value Object), рассматриваемых как единое целое. Доступ к ним идет через корень агрегата (Aggregate Root).
  2. Entity — объект, имеющий уникальный идентификатор и жизненный цикл.
  3. Value Object — иммутабельный объект, идентифицируемый по значениям его полей (например, Money с суммой и валютой).
  4. Репозиторий (Repository) — абстракция для хранения и извлечения агрегатов, скрывающая детали инфраструктуры.
  5. Сервис домена (Domain Service) — операция, которая не естественным образом принадлежит какой-либо Entity или Value Object.

Пример фрагмента доменного слоя на Java:

// Value Object
public record CustomerId(UUID value) {}

// Aggregate Root (Entity)
public class Order {
    private OrderId id;
    private CustomerId customerId;
    private List<OrderLine> lines; // OrderLine — это Entity внутри агрегата
    private OrderStatus status;

    // Доменная логика внутри агрегата
    public void addItem(ProductId productId, Quantity quantity, Price price) {
        // Проверка инвариантов (бизнес-правил)
        Objects.requireNonNull(productId);
        if (quantity.value() <= 0) {
            throw new IllegalArgumentException("Quantity must be positive");
        }
        this.lines.add(new OrderLine(productId, quantity, price));
    }

    public void cancel() {
        if (!status.canBeCancelled()) {
            throw new IllegalStateException("Order cannot be cancelled");
        }
        this.status = OrderStatus.CANCELLED;
    }
}

// Repository интерфейс в доменном слое
public interface OrderRepository {
    Optional<Order> findById(OrderId id);
    void save(Order order);
}

Применял DDD в проектах со сложной и изменчивой бизнес-логикой. Это помогает:

  • Сделать код понятным для экспертов предметной области.
  • Изолировать ядро системы от инфраструктурных деталей (БД, фреймворки).
  • Управлять сложностью через ограниченные контексты (Bounded Contexts).

Ответ 18+ 🔞

Вот, слушай, смотри, как оно на самом деле бывает. Сидишь ты такой, пыхтишь над очередным проектом, а там бизнес-логика такая, что мама не горюй. Вроде «добавить товар в корзину», а на деле — проверь остатки, примени три скидки, убедись, что клиент не в чёрном списке, и ещё хуй знает что. И вот тут-то, блядь, и выплывает эта самая Domain-Driven Design (DDD).

Не какая-то абстрактная хуйня, а реальный способ не сойти с ума. Всё крутится вокруг домена — то есть, вокруг этих самых бизнес-правил, которые заказчик тебе так красочно на пальцах объясняет. Задача — чтобы код стал их прямым отражением, а не этой спагетти-кашей из SQL-запросов и проверок в контроллере.

Вот на чём всё держится, основные киты, блядь:

  1. Агрегат (Aggregate) — это типа святой святых, такая банда объектов, которых нельзя трогать поодиночке. Как семья, блядь. Есть корень агрегата (Aggregate Root) — главный по тарелкам, через него со всеми и общаешься. Снаружи виден только он, а внутри у него свои Entity и Value Object орудуют.
  2. Entity — объект с характером. У него есть паспорт (уникальный ID), и он меняется со временем. Заказ, Пользователь — вот это всё.
  3. Value Object — объект без амбиций. Идентифицируется не по паспорту, а по тому, что в карманах лежит. Деньги (сумма + валюта), адрес (улица, дом). Создал — и забыл, он неизменяемый, как скала, блядь.
  4. Репозиторий (Repository) — этакий волшебный сундук. Говоришь ему: «Дай заказ №5» или «Сохрани этот заказ». А как он там внутри, в базе или в файле, это его проблемы. Абстракция, ёпта!
  5. Сервис домена (Domain Service) — операция, которой не место ни в Entity, ни в Value Object. Например, сложный расчёт, который требует данных из кучи разных агрегатов. Такой утилитарный мужик, который всем помогает.

Смотри, как это в коде выглядит, на живом примере:

// Value Object — просто данные, иммутабельные
public record CustomerId(UUID value) {}

// А вот это — корень агрегата! Entity, главный по заказу.
public class Order {
    private OrderId id;
    private CustomerId customerId;
    private List<OrderLine> lines; // OrderLine — это уже внутренняя Entity, своя
    private OrderStatus status;

    // А вот и доменная логика, прямо тут, в теле! Не где-то в сервисе на 1000 строк.
    public void addItem(ProductId productId, Quantity quantity, Price price) {
        // Проверяем инварианты, бизнес-правила, ёба!
        Objects.requireNonNull(productId);
        if (quantity.value() <= 0) {
            throw new IllegalArgumentException("Количество должно быть положительным, мудила!");
        }
        this.lines.add(new OrderLine(productId, quantity, price));
    }

    public void cancel() {
        // Нельзя отменить уже отправленный заказ! Правило.
        if (!status.canBeCancelled()) {
            throw new IllegalStateException("Заказ уже не отменить, проехали!");
        }
        this.status = OrderStatus.CANCELLED;
        // Тут ещё могут события какие-то генерироваться, но это уже другая история.
    }
}

// Репозиторий — интерфейс, абстракция. Реализация — где-то в инфраструктуре.
public interface OrderRepository {
    Optional<Order> findById(OrderId id);
    void save(Order order);
}

Где это всё применять? Да там, где логика реально ебёт мозг, а не где «CRUD для блога». Когда за «простой» операцией стоит десяток проверок и правил. DDD помогает:

  • Сделать так, чтобы заказчик и разработчик начинали говорить на одном языке. Код становится документом.
  • Отделить святое (бизнес-правила) от грешного (базы данных, веб-фреймворки, апишки). Ядро системы чистое.
  • Разрулить адскую сложность через ограниченные контексты (Bounded Contexts). Типа, в контексте «Доставка» у «Заказа» одни свойства, а в контексте «Оплата» — другие. И они не лезут друг другу в тарелку. Красота, ёпта!