Какой у вас опыт работы с Domain-Driven Design (DDD)?

Ответ

Применял DDD в нескольких проектах со сложной предметной областью, например, в системах управления логистикой и финансовых сервисах. Основной опыт сосредоточен на стратегическом и тактическом проектировании.

Стратегический дизайн:

  • Проводил Event Storming и работал с экспертами предметной области для выявления Bounded Contexts.
  • Определял Ubiquitous Language и следил за его соблюдением в коде и документации.
  • Проектировал Context Maps для интеграции между контекстами (Customer/Supplier, Anti-Corruption Layer).

Тактический дизайн:

  • Реализовывал богатые доменные модели с Aggregates, Entities и Value Objects.
  • Использовал Domain Events для реактивности и слабой связности. Например, при подтверждении заказа генерировалось событие OrderConfirmed, которое триггерило процессы в контексте доставки и бухгалтерии.
  • Изолировал доменную логику с помощью Repositories и Domain Services.

Пример Value Object для денежной суммы:

public record Money : IValueObject
{
    public decimal Amount { get; }
    public Currency Currency { get; }

    private Money(decimal amount, Currency currency)
    {
        Amount = amount;
        Currency = currency;
    }

    public static Money Create(decimal amount, Currency currency)
    {
        if (amount < 0) throw new DomainException("Amount cannot be negative.");
        return new Money(amount, currency);
    }

    public Money Add(Money other)
    {
        if (Currency != other.Currency) throw new DomainException("Cannot add different currencies.");
        return Create(Amount + other.Amount, Currency);
    }
}

Основные сложности, с которыми сталкивался: отказ от CRUD-мышления в пользу моделирования процессов, управление консистентностью агрегатов и рефакторинг legacy-кода к чистой архитектуре.

Ответ 18+ 🔞

Ну и история, блядь. DDD применял, да не просто так, а в таких проектах, где предметная область — это пиздец какой лабиринт. Логистика, финансы — там без бутылки не разберёшься, кто кому и сколько должен, и чей груз где сейчас ебётся. Терпения ноль ебать, но зато мозги прокачал овердохуища.

Этот ваш стратегический дизайн: Тут главное — не накосячить с границами. Собирались с экспертами, проводили Event Storming. Картина маслом: доска вся в стикерах, куча криков «это же разные процессы, ёпта!». Выявляли Bounded Contexts, чтобы каждый модуль знал свою жопу и не лез в чужую. Определяли Ubiquitous Language — общий язык, на котором и бизнес говорит, и код пишется. А то бывает, бизнес говорит «клиент», а в коде у тебя User, Customer и Client в одном классе — пизда рулю и конец проекту. Ещё рисовали Context Maps, чтобы понять, как эти изолированные куски друг с другом общаться будут. Где-то отношения «заказчик-поставщик», а где-то, чтобы грязные данные из легаси не просочились, строили Anti-Corruption Layer — этакий шлюз, который всю хуйню отфильтрует.

Тактический дизайн — тут уже код писать: Тут уже лепил эти самые Aggregates, Entities и Value Objects. Главная идея — чтобы бизнес-логика была в одном месте, а не размазана по двадцати сервисам. Использовал Domain Events для связности. Ну, например, заказ подтвердился — бабах, событие OrderConfirmed улетело. Его подхватывают другие контексты: один начинает маршрут строить, другой — счёт выставлять. Красота, а не жизнь. Всё это изолировал через Repositories и Domain Services, чтобы база данных не мозолила глаза домену.

Вот, смотри, пример Value Object для денег. Чтобы везде не таскать просто decimal amount, а была сущность с поведением и валидацией:

public record Money : IValueObject
{
    public decimal Amount { get; }
    public Currency Currency { get; }

    private Money(decimal amount, Currency currency)
    {
        Amount = amount;
        Currency = currency;
    }

    public static Money Create(decimal amount, Currency currency)
    {
        if (amount < 0) throw new DomainException("Amount cannot be negative.");
        return new Money(amount, currency);
    }

    public Money Add(Money other)
    {
        if (Currency != other.Currency) throw new DomainException("Cannot add different currencies.");
        return Create(Amount + other.Amount, Currency);
    }
}

А сложности, бля... их было как говна за баней. Самое тяжкое — это выкинуть из головы CRUD-мышление. Все привыкли: взял запись, поменял поле, сохранил. А тут надо думать процессами, инвариантами. Управление консистентностью агрегатов — тоже та ещё песня, чтобы не получилось, что данные наполовину обновились. Ну и, конечно, рефакторинг legacy-кода под эту чистую архитектуру — это как разбирать завал в квартире старого пьяницы: то бутылка из-под дивана выкатится, то носки прикипели к полу. Но когда получается — чувствуешь себя богом, ёба.