안녕하세요, 백엔드 개발자 버즈입니다. 최근 회사 업무와 다양한 사유로 개인 블로그와 GitHub 활동에 다소 소홀했습니다. 그동안 회사 프로젝트 개발 및 새로운 기술 스택의 도입 때문에 눈코 뜰 새 없이 바쁜 시간을 보냈기 때문인데요. 오늘은 그 과정에서 겪었던 Spring Framework 기반 백엔드 환경에서 Node.js (Express & NestJS) 생태계로 주력 스택을 전환하며 배운 점과 겪었던 시행착오를 솔직하게 정리해 보려고 합니다.
당시 회사 프로젝트 아키텍처상 Spring Boot 기반 서비스에서 Express로, 더 나아가 NestJS로의 마이그레이션 및 확장 계획이 있어 자연스럽게 JavaScript/TypeScript 환경의 백엔드 시스템을 본격적으로 마주하게 되었습니다.
1. 멀티스레드(Spring) vs 단일스레드 이벤트 루프(Node.js)
Spring 환경(Tomcat 기본 설정 등)에서는 클라이언트의 요청마다 스레드를 하나씩 할당하여 동기식 블로킹(Blocking) 방식으로 작업을 처리하는 Thread-per-request 모델이 보편적입니다. 반면, Node.js는 싱글스레드(Single Thread) 기반으로 작동하며, 비블로킹 I/O(Non-blocking I/O)와 이벤트 루프(Event Loop)를 활용해 비동기 방식으로 많은 양의 동시 요청을 처리합니다.
전환하며 느낀 점
- 비동기 처리(Async/Await)의 일상화: Node.js에서는 데이터베이스 쿼리를 날리거나 외부 API를 호출할 때 반드시 비동기 처리가 수반됩니다. 처음에는 JavaScript의
Promise체이닝이나async/await키워드를 모든 곳에 붙이는 코드가 낯설었지만, 코드의 실행 흐름을 완전히 제어하고 I/O 병목을 피하는 데 있어 자바의 멀티스레드 동기화 제어보다 어떤 면에서는 더 직관적이고 경량화되어 있다는 느낌을 받았습니다. - CPU 집약적 작업의 위험성: 싱글스레드 환경이기 때문에 대용량 이미지 처리나 복잡한 알고리즘 연산처럼 CPU를 많이 소모하는 로직이 이벤트 루프를 막아서(Blocking) 전체 서버의 요청 처리가 일시 중단되는 현상을 겪기도 했습니다. 이 점은 자바 스프링에 비해 Node.js가 가지는 뚜렷한 한계점이며, 이를 해결하기 위해 Worker Thread를 쓰거나 마이크로서비스로 분리해야 한다는 설계적 시야를 얻게 되었습니다.
2. Express에서 NestJS로: 스프링 개발자에게 찾아온 구원
처음 Express를 마주했을 때는 프레임워크 자체의 높은 자유도(자유로운 아키텍처 설정) 때문에 오히려 혼란스러웠습니다. 프로젝트가 커질수록 아키텍처 패턴이 정립되지 않아 스파게티 코드가 되기 십상이었습니다.
이러한 페인 포인트(Pain Point)를 완벽하게 해소해 준 것이 바로 NestJS였습니다. NestJS는 TypeScript를 기본으로 채택하고 있으며, Spring Framework의 구조와 철학을 거의 그대로 차용한 프레임워크입니다.
- 의존성 주입(DI / Dependency Injection):
@Injectable(),@Module()데코레이터를 이용해 스프링 빈 컨테이너처럼 의존성 주입을 완벽히 제어합니다. - 계층형 아키텍처: Controller -> Service -> Repository(or Provider)로 이어지는 계층 구조가 완벽하게 잡혀 있어, 스프링 개발자가 코드를 접했을 때 이질감 없이 단 하루 만에 프로젝트 구조를 파악하고 기능 개발을 시작할 수 있었습니다.
- 데코레이터 기반 선언:
@Get(),@Post(),@UseGuards(),@UseInterceptors()등 Spring의@GetMapping,@PostMapping, Spring Security Filter, Interceptor 등과 1:1 매핑되는 개념들이 존재하여 학습 곡선이 매우 완만했습니다.
3. 잔디 심기와 개발 라이프의 회고
회사용 깃허브 계정에 커밋을 쏟아붓느라 아래처럼 개인 깃허브의 잔디 관리에 한동안 소홀해졌던 것이 사실입니다. 회사 업무에 적응하고 기술 스택의 기반을 닦느라 에너지를 쏟았기 때문이라고 위안을 해보지만, 결국 꾸준함이야말로 개발자의 가장 큰 무기임을 다시금 상기하게 됩니다.
이제 Node.js 및 TypeScript 환경에서의 다양한 트러블슈팅 경험, NestJS와 Express 기반에서 겪었던 ORM(TypeORM, Prisma) 이슈 및 성능 개선 사례 등 유용한 지식들을 본 블로그에 적극적으로 풀어놓고자 합니다.
비록 주력 무기가 Java/Spring에서 JavaScript/Node.js로 확장되는 과도기를 거치고 있지만, 백엔드 개발자로서 “대용량 데이터를 안전하게 처리하고, 신뢰성 높은 아키텍처를 그린다”는 본질적인 목표는 변함이 없다는 것을 깨달은 뜻깊은 성장의 시간이었습니다. 앞으로도 더 알찬 기술 포스트로 찾아뵙겠습니다!
