Что такое Domain-Driven Design (DDD) и как его применять в Node.js?

«Что такое Domain-Driven Design (DDD) и как его применять в Node.js?» — вопрос из категории Архитектура, который задают на 26% собеседований Node.js Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Ключевые строительные блоки DDD в Node.js:

  1. Сущность (Entity) — объект с уникальным идентификатором:

    class User {
    constructor(
    public readonly id: string,
    public email: string,
    private passwordHash: string
    ) {}
    
    changeEmail(newEmail: string) {
    // Бизнес-правило: валидация email
    if (!this.isValidEmail(newEmail)) {
      throw new Error('Invalid email');
    }
    this.email = newEmail;
    }
    }
  2. Объект-значение (Value Object) — неизменяемый объект без идентификатора:

    class Money {
    constructor(
    public readonly amount: number,
    public readonly currency: string
    ) {}
    
    add(other: Money): Money {
    if (this.currency !== other.currency) {
      throw new Error('Cannot add different currencies');
    }
    return new Money(this.amount + other.amount, this.currency);
    }
    }
  3. Агрегат (Aggregate) — группа связанных объектов с корневым Aggregate Root:

    class Order {
    private items: OrderItem[] = [];
    
    constructor(
    public readonly id: string,
    private customerId: string,
    private status: OrderStatus
    ) {}
    
    addItem(productId: string, quantity: number, price: Money) {
    // Инвариант: нельзя добавлять товары в завершенный заказ
    if (this.status === OrderStatus.COMPLETED) {
      throw new Error('Cannot modify completed order');
    }
    
    this.items.push(new OrderItem(productId, quantity, price));
    }
    
    calculateTotal(): Money {
    return this.items.reduce(
      (total, item) => total.add(item.calculateSubtotal()),
      new Money(0, 'USD')
    );
    }
    }

Практическое применение в Node.js:

  • Использую TypeScript для явного моделирования домена через интерфейсы и классы
  • Разделяю код на слои: Domain (чистая бизнес-логика), Application (координация), Infrastructure (база данных, внешние API)
  • Применяю паттерн Repository для доступа к данным, сохраняя доменную модель независимой от базы данных
  • Использую события домена (Domain Events) для связи между агрегатами

В последнем проекте электронной коммерции DDD помог нам четко отделить сложные бизнес-правила расчета скидок и налогов от инфраструктурного кода, что упростило тестирование и позволило быстрее реагировать на изменения требований бизнеса.