"NestJS를 쓰면 무조건 이 구조로 짜야 하나요?" NestJS는 강력한 모듈 시스템과 의존성 주입(DI) 덕분에 매우 자유로운 아키텍처 설계를 지원합니다. 하지만 초기 단계에서 프로젝트의 복잡도를 고려하지 않고 아키텍처를 선택하면 나중에 큰 비용을 치르게 됩니다.
작은 프로젝트에 과한 아키텍처를 적용하면 보일러플레이트 코드에 지치게 되고, 반대로 거대한 프로젝트를 단순한 구조로 짜면 비대해진 서비스(Fat Service)와 스파게티 코드를 마주하게 됩니다. 따라서 상황에 맞는 '정답'을 찾는 능력이 시니어 개발자에게는 필수적입니다.
업계에서 NestJS와 함께 가장 많이 언급되는 3가지 주요 아키텍처를 비교해 보겠습니다.
가장 표준적이며 NestJS가 기본적으로 권장하는 방식입니다.
상태를 변경하는 작업(Command)과 데이터를 조회하는 작업(Query)을 서로 다른 모델과 경로로 처리합니다.
CreateUser, UpdatePost 등 상태 변경. (Side Effect 발생)GetUserList, GetPostById 등 데이터 조회. (Side Effect 없음)도메인 로직을 핵심(Inside)에 두고, DB나 외부 라이브러리를 어댑터(Outside)로 취급합니다.
가장 익숙한 형태입니다. 단순하고 빠릅니다.
// users.service.ts @Injectable() export class UsersService { constructor(private usersRepository: UsersRepository) {} async signup(dto: SignupDto) { // 로직 처리... return this.usersRepository.save(dto); } }
@nestjs/cqrs 라이브러리를 사용합니다. 로직이 명확히 쪼개집니다.
// create-user.command.ts (명령 정의) export class CreateUserCommand { constructor(public readonly email: string) {} } // create-user.handler.ts (처리 로직) @CommandHandler(CreateUserCommand) export class CreateUserHandler implements ICommandHandler<CreateUserCommand> { async execute(command: CreateUserCommand) { // 오직 유저 생성에만 집중! } }
| 아키텍처 | 장점 | 단점 | 추천 상황 |
|---|---|---|---|
| 3-Layer | 낮은 학습 곡선, 빠른 개발 속도 | 서비스가 너무 비대해짐(Fat Service) | 소규모 ~ 중규모 프로젝트, MVP |
| CQRS | 높은 확장성, 읽기/쓰기 성능 최적화 유리 | 복잡도 증가, 코드량 급증 | 대규모 트래픽, 복잡한 비즈니스 로직 |
| Hexagonal | 인프라 변경(DB 교체 등)에 매우 강함 | 추상화가 너무 심해 코드를 파악하기 힘듦 | 장기 운영이 필요한 핵심 시스템 |
아키텍처에 '절대 선'은 없습니다. "지금 우리 팀이 이 복잡도를 감당할 수 있는가?"와 "이 서비스가 얼마나 커질 것인가?"를 고민하는 것이 더 중요합니다.
NestJS는 어떤 옷을 입혀도 잘 소화하는 프레임워크입니다. 오늘 정리한 패턴들을 이해하고, 현재 진행 중인 프로젝트의 규모에 딱 맞는 아키텍처를 선택해 보세요. 도구보다 중요한 것은 그 도구를 다루는 개발자의 판단력입니다!