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

Какая архитектура помимо микросервисной реализована в Nest.js?

2.0 Middle🔥 91 комментариев
#Архитектура и паттерны#Фреймворки и библиотеки

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

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

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

Архитектурные подходы в Nest.js

Nest.js — это фреймворк, который поддерживает несколько архитектурных паттернов. Разберу, что можно реализовать помимо микросервисов.

Монолитная архитектура (Monolithic)

Классический подход — одно приложение, содержащее всю логику:

// app.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
import { OrdersModule } from './orders/orders.module';
import { ProductsModule } from './products/products.module';

@Module({
  imports: [UsersModule, OrdersModule, ProductsModule],
})
export class AppModule {}

Плюсы:

  • Простота разработки и развертывания
  • Легко отлаживать и тестировать
  • Хорошо для MVP и небольших приложений

Минусы:

  • Сложность масштабирования
  • Высокий coupling между модулями
  • Сложные CI/CD процессы при частых релизах

Многоуровневая архитектура (Layered Architecture)

Разделение на слои: контроллеры → сервисы → репозитории → БД.

// users.controller.ts
@Controller('users')
export class UsersController {
  constructor(private usersService: UsersService) {}

  @Get(':id')
  getUser(@Param('id') id: string) {
    return this.usersService.getUser(id);
  }
}

// users.service.ts
@Injectable()
export class UsersService {
  constructor(private usersRepository: UsersRepository) {}

  getUser(id: string) {
    return this.usersRepository.findById(id);
  }
}

// users.repository.ts
@Injectable()
export class UsersRepository {
  async findById(id: string) {
    return this.db.query(`SELECT * FROM users WHERE id = $1`, [id]);
  }
}

Структура:

├── controllers/      (HTTP endpoints)
├── services/        (Business logic)
├── repositories/    (Data access)
└── models/          (Entities)

Плюсы:

  • Чистое разделение ответственности
  • Легко тестировать каждый слой
  • Хорошо документируется

Минусы:

  • Может быть многоуровневым (extra layer complexity)

Domain-Driven Design (DDD)

Направлена на моделирование предметной области:

src/
├── domain/
│   ├── entities/
│   │   └── User.ts
│   ├── value-objects/
│   │   └── Email.ts
│   ├── repositories/
│   │   └── UserRepository.interface.ts
│   └── services/
│       └── UserDomainService.ts
├── application/
│   ├── dto/
│   │   └── CreateUserDto.ts
│   └── use-cases/
│       └── CreateUserUseCase.ts
└── infrastructure/
    ├── persistence/
    │   └── TypeOrmUserRepository.ts
    └── http/
        └── UserController.ts
// domain/entities/User.ts
export class User {
  private id: string;
  private email: Email;
  private name: string;

  constructor(id: string, email: Email, name: string) {
    this.id = id;
    this.email = email;
    this.name = name;
  }

  getEmail(): Email {
    return this.email;
  }
}

// application/use-cases/CreateUserUseCase.ts
@Injectable()
export class CreateUserUseCase {
  constructor(private userRepository: UserRepository) {}

  async execute(dto: CreateUserDto) {
    const user = new User(
      uuidv4(),
      new Email(dto.email),
      dto.name
    );
    await this.userRepository.save(user);
    return user;
  }
}

Плюсы:

  • Бизнес-логика отделена от инфраструктуры
  • Легче менять DB или фреймворк
  • Понятнее для domain experts

Минусы:

  • Больше кода и классов
  • Сложнее для простых проектов

Clean Architecture

Похожа на DDD, но с чётким направлением зависимостей:

Презентация (Controllers)
Применение (Use Cases)
Домен (Entities, Rules)
Инфраструктура (DB, APIs)

Правило: зависимости должны идти только внутрь (от внешних к внутренним слоям).

// domain/repositories/user.repository.ts (интерфейс)
export interface IUserRepository {
  findById(id: string): Promise<User>;
  save(user: User): Promise<void>;
}

// infrastructure/persistence/typeorm-user.repository.ts (реализация)
@Injectable()
export class TypeOrmUserRepository implements IUserRepository {
  async findById(id: string): Promise<User> {
    // TypeORM реализация
  }
}

// application/use-cases/get-user.use-case.ts
@Injectable()
export class GetUserUseCase {
  constructor(
    @Inject('UserRepository')
    private userRepository: IUserRepository
  ) {}

  async execute(id: string): Promise<User> {
    return this.userRepository.findById(id);
  }
}

Плюсы:

  • Полная независимость от фреймворка
  • Легко менять реализацию
  • Отличная тестируемость

Гексагональная архитектура (Ports & Adapters)

Приложение как целое, изолированное от внешнего мира:

// ports/user.repository.port.ts
export interface UserRepositoryPort {
  findById(id: string): Promise<User>;
  save(user: User): Promise<void>;
}

// adapters/persistence/mysql-user.repository.ts
@Injectable()
export class MysqlUserRepository implements UserRepositoryPort {
  // MySQL реализация
}

// adapters/persistence/postgres-user.repository.ts
@Injectable()
export class PostgresUserRepository implements UserRepositoryPort {
  // PostgreSQL реализация
}

Плюсы:

  • Очень гибко менять адаптеры
  • Идеально для интеграций

CQRS (Command Query Responsibility Segregation)

Разделение чтения и записи:

// commands
@Command('CreateUserCommand')
export class CreateUserCommand {
  constructor(public name: string, public email: string) {}
}

// handlers
@CommandHandler(CreateUserCommand)
export class CreateUserHandler {
  constructor(private userRepository: UserRepository) {}

  async execute(command: CreateUserCommand) {
    // Создание пользователя
  }
}

// queries
@Query('GetUserQuery')
export class GetUserQuery {
  constructor(public id: string) {}
}

// handlers
@QueryHandler(GetUserQuery)
export class GetUserHandler {
  constructor(private userReadModel: UserReadModel) {}

  async execute(query: GetUserQuery) {
    // Чтение из оптимизированной модели
  }
}

Плюсы:

  • Разные оптимизации для чтения и записи
  • Легче масштабировать

Event-Driven архитектура

Когда компоненты общаются через события:

@Injectable()
export class UserCreatedEvent {
  constructor(public userId: string, public email: string) {}
}

// Publishing
@Injectable()
export class CreateUserUseCase {
  constructor(
    private eventEmitter: EventEmitter2,
    private userRepository: UserRepository
  ) {}

  async execute(dto: CreateUserDto) {
    const user = new User(dto.email, dto.name);
    await this.userRepository.save(user);
    
    this.eventEmitter.emit('user.created', new UserCreatedEvent(user.id, user.email));
  }
}

// Subscribing
@Injectable()
export class SendWelcomeEmailListener {
  @OnEvent('user.created')
  async handleUserCreatedEvent(event: UserCreatedEvent) {
    // Отправка приветственного письма
  }
}

Плюсы:

  • Слабая связанность (loose coupling)
  • Масштабируемость

Выбор архитектуры

Монолит лучше всего для малых проектов с низкой сложностью, идеален для MVP и стартапов. Многоуровневая архитектура подходит для средних проектов с средней сложностью и стандартных приложений. DDD рекомендуется для больших проектов с высокой сложностью и комплексной бизнес-логикой. Clean Architecture идеальна для больших долгосрочных проектов. CQRS применяется в больших высоконагруженных системах. Event-Driven используется в средних и больших распределённых системах.

В реальных проектах часто комбинируют подходы. Например, монолит с DDD и Event-Driven компонентами — это уже продвинутая архитектура, которая обеспечивает масштабируемость и поддерживаемость.

Какая архитектура помимо микросервисной реализована в Nest.js? | PrepBro