Какая архитектура помимо микросервисной реализована в Nest.js?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Архитектурные подходы в 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 компонентами — это уже продвинутая архитектура, которая обеспечивает масштабируемость и поддерживаемость.