← Назад к вопросам

Что такое DDD?

3.0 Senior🔥 121 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Domain-Driven Design (DDD): полное руководство

Domain-Driven Design — это философия проектирования программного обеспечения, которая ставит бизнес-логику (domain) в центр разработки. Это не просто набор паттернов, это способ думать о коде.

Основная идея DDD

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

ПЛОХО: техническое разделение
src/
  controllers/
  services/
  repositories/

ХОРОШО: разделение по доменам
src/
  features/
    user/
      domain/
        User.ts
      application/
        CreateUserUseCase.ts
      infrastructure/
        UserRepositoryImpl.ts

Четыре слоя DDD

1. Presentation Layer — как пользователь общается

export class UserController {
  constructor(private createUserUseCase: CreateUserUseCase) {}

  async create(req: Request, res: Response) {
    const user = await this.createUserUseCase.execute(req.body);
    res.json(user);
  }
}

2. Application Layer — координация между доменом и внешним миром

export class CreateUserUseCase {
  constructor(
    private userRepository: IUserRepository,
    private emailService: IEmailService
  ) {}

  async execute(command: CreateUserCommand): Promise<User> {
    const existing = await this.userRepository.findByEmail(command.email);
    if (existing) {
      throw new EmailAlreadyExistsError();
    }

    const user = new User(
      generateId(),
      command.email,
      command.password,
      new Date()
    );

    await this.userRepository.save(user);
    await this.emailService.sendWelcome(user.email);

    return user;
  }
}

3. Domain Layer — чистая бизнес-логика (сердце приложения)

export class User {
  constructor(
    private id: string,
    private email: string,
    private hashedPassword: string,
    private createdAt: Date
  ) {}

  authenticate(plainPassword: string): boolean {
    return bcrypt.compareSync(plainPassword, this.hashedPassword);
  }

  updateEmail(newEmail: string): void {
    if (!this.isValidEmail(newEmail)) {
      throw new InvalidEmailError();
    }
    this.email = newEmail;
  }

  private isValidEmail(email: string): boolean {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }

  getId(): string { return this.id; }
  getEmail(): string { return this.email; }
}

4. Infrastructure Layer — технические детали

export class UserRepositoryImpl implements IUserRepository {
  constructor(private db: Database) {}

  async save(user: User): Promise<void> {
    await this.db.query(
      'INSERT INTO users (id, email, password, created_at) VALUES ($1, $2, $3, $4)',
      [user.getId(), user.getEmail(), user.getHashedPassword(), user.getCreatedAt()]
    );
  }

  async findByEmail(email: string): Promise<User | null> {
    const row = await this.db.query(
      'SELECT * FROM users WHERE email = $1',
      [email]
    );
    if (!row) return null;
    return new User(row.id, row.email, row.password, row.created_at);
  }
}

Ключевые концепции DDD

1. Aggregates — группировка сущностей

export class Post {
  private id: string;
  private comments: Comment[] = [];

  addComment(comment: Comment): void {
    if (this.comments.length >= 100) {
      throw new MaxCommentsExceededError();
    }
    this.comments.push(comment);
  }

  getComments(): Comment[] {
    return [...this.comments];
  }
}

2. Value Objects — объекты без идентичности

export class Email {
  constructor(private value: string) {
    if (!this.isValid(value)) {
      throw new InvalidEmailError();
    }
  }

  private isValid(email: string): boolean {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }

  equals(other: Email): boolean {
    return this.value === other.value;
  }

  toString(): string {
    return this.value;
  }
}

3. Domain Events — коммуникация между доменами

export class UserRegisteredEvent {
  constructor(
    public userId: string,
    public email: string,
    public timestamp: Date
  ) {}
}

export class User {
  private domainEvents: DomainEvent[] = [];

  static create(email: string, password: string): User {
    const user = new User(generateId(), email, password, new Date());
    user.addDomainEvent(new UserRegisteredEvent(
      user.id,
      user.email,
      new Date()
    ));
    return user;
  }

  getDomainEvents(): DomainEvent[] {
    return this.domainEvents;
  }
}

export class CreateUserUseCase {
  async execute(command: CreateUserCommand): Promise<User> {
    const user = User.create(command.email, command.password);
    await this.userRepository.save(user);

    for (const event of user.getDomainEvents()) {
      await this.eventPublisher.publish(event);
    }

    return user;
  }
}

DDD в реальном проекте: E-commerce

src/
  features/
    orders/
      domain/
        Order.ts
        OrderLine.ts
        OrderStatus.ts
      application/
        CreateOrderUseCase.ts
      infrastructure/
        OrderRepositoryImpl.ts
      presentation/
        OrderController.ts
    products/
      domain/
        Product.ts
        Price.ts
      application/
        FindProductUseCase.ts
      infrastructure/
        ProductRepositoryImpl.ts

Преимущества DDD

  • Простота в понимании: код отражает бизнес-логику
  • Меньше bugs: бизнес-правила инкапсулированы
  • Масштабируемость: можно split на микросервисы
  • Поддерживаемость: изменения в одном domain не влияют на другие
  • Переиспользуемость: domain logic независим от transport layer

Когда использовать DDD

  • Большие проекты с сложной бизнес-логикой
  • Долгосрочные проекты которые будут развиваться
  • Когда команда растет
  • Когда есть четкие бизнес-домены

Когда NOT использовать DDD

  • Маленькие CRUD приложения
  • MVP которые нужно быстро запустить
  • Простые скрипты или утилиты
Что такое DDD? | PrepBro