
"NestJS를 쓰면 무조건 이 구조로 짜야 하나요?"라는 질문을 자주 받습니다. NestJS는 강력한 모듈 시스템과 의존성 주입(DI) 덕분에 매우 자유로운 아키텍처 설계를 지원합니다. 하지만 초기 단계에서 프로젝트의 복잡도를 고려하지 않고 아키텍처를 선택하면 나중에 큰 비용을 치르게 됩니다.
작은 프로젝트에 과한 아키텍처를 적용하면 보일러플레이트 코드에 지치게 되고, 반대로 거대한 프로젝트를 단순한 구조로 짜면 비대해진 서비스(Fat Service)와 스파게티 코드를 마주하게 됩니다. 따라서 상황에 맞는 선택을 할 줄 아는 판단력이 중요합니다.
NestJS와 함께 가장 많이 언급되는 세 가지 주요 아키텍처를 비교해 보겠습니다.
가장 표준적이며 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는 어떤 옷을 입혀도 잘 소화하는 프레임워크입니다. 오늘 정리한 패턴들을 이해하고, 현재 진행 중인 프로젝트의 규모에 맞는 아키텍처를 선택해 보세요. 도구보다 중요한 것은 그 도구를 다루는 개발자의 판단력입니다.