성숙한 생태계 현대화: Clean Architecture와 읽기 마이크로서비스의 Performance
Source: Dev.to
어떻게 데이터 읽기 전용 아키텍처를 확장하여 인프라 비용을 절감하고 레거시 모놀리스를 탈결합했는지.
🇺🇸 Read the English version here
최근에 나는 대규모 기업 환경에서 흔히 마주치는 도전 과제에 직면했다: 기존에 통합된 데이터베이스에서 데이터를 소비해야 하는 고성능·확장 가능한 새로운 기능을 만들면서, 핵심 시스템의 안정성을 해치지 않아야 하는 상황이었다.
목표는 Read‑Only 마이크로서비스(읽기 전용) 를 설계해 데이터 시각화 인터페이스와 내보내기 루틴에 데이터를 공급하는 것이었다. 비즈니스 요구는 빠른 전달이었고, 내가 스스로에게 부과한 기술 요구는 우수한 엔지니어링과 장기적인 유지보수성이었다.
이 글에서는 Node.js, Clean Architecture, Prisma, Docker 를 활용해 견고하고 탈결합된 솔루션을 보장하기 위해 내린 아키텍처 결정들을 공유한다.
1. 엔지니어링 과제
시나리오는 수년간 유기적으로 성장한 시스템의 전형적인 복잡성을 포함했다:
- 방대한 데이터와 복잡한 구조를 가진 관계형(SQL) 데이터베이스.
- Node.js 이벤트 루프를 차단하지 않으면서 다수의 클라이언트를 동시에 서비스할 수 있는 고가용성.
- 엄격한 보안 요구와 테스트·프로덕션 환경 간 완전한 격리.
- 비용 효율성을 위해 최적화가 필요한 AWS 인프라(Compute/Storage).
2. Clean Architecture
프로젝트의 장수성을 보장하기 위해 Clean Architecture 를 도입했다. 주요 목표는 비즈니스 규칙을 웹 프레임워크나 데이터베이스 드라이버 같은 구현 세부 사항으로부터 격리하는 것이었다.
구조는 책임이 명확히 정의된 레이어로 구성했다:
- Domain: 애플리케이션의 순수 인터페이스와 모델.
- Data: 유스케이스와 비즈니스 규칙.
- Infra: 외부 구현(리포지토리, 암호화, 통합).
- Main: 의존성을 주입하고 서비스를 초기화하는 조합 레이어.
이러한 분리는 예를 들어 HTTP 프레임워크(예: Express → Fastify)나 ORM 을 교체하더라도 도메인 로직에 영향을 주지 않게 만든다.
3. 기존 데이터베이스와의 통합 (Prisma ORM)
기술적인 도전 과제 중 하나는 기존 데이터 구조를 안전하고 타입‑안전하게 매핑하는 것이었다. 수동 쿼리는 향후 유지보수 시 깨지기 쉬웠다.
해결책은 Prisma ORM 의 Introspection 기능을 활용하는 것이었다. 이 서비스는 마이그레이션을 관리하지 않으므로(위성 서비스의 범위가 아니었음) Prisma 를 기존 스키마를 읽어 자동으로 TypeScript 타입을 생성하도록 설정했다.
복합 키를 사용하는 오래된 관계 테이블을 다루기 위해 스키마에 네이티브 매핑을 사용했다:
model ExampleRelation {
userId Int
itemId Int
// 컬럼 조합으로 고유 식별자를 정의
@@id([userId, itemId])
}
이 덕분에 타입 안전성이 확보돼 런타임 오류를 방지하고 개발 속도가 빨라졌다.
4. 성능 및 효율성: Docker Multi‑stage 와 PM2 Cluster
인프라 최적화는 메모리 사용량을 줄이고 CPU 활용도를 극대화하는 데 초점을 맞췄다.
Docker Multi‑stage Build
불필요한 개발 파일이 포함된 무거운 이미지를 방지하기 위해 Alpine Linux 기반의 다중 단계 빌드를 구현했다:
- Builder Stage: 모든 의존성을 설치하고 TypeScript 를 컴파일해 아티팩트를 생성.
- Runner Stage: 트랜스파일된 코드(
dist)와 프로덕션 의존성만 복사.
결과: 최종 이미지 크기가 약 150 MB 로 압축돼 배포 속도가 빨라지고 레지스트리(ECR) 저장 비용이 감소했다.
PM2 Cluster Mode
Node.js 애플리케이션은 single‑threaded 이기 때문에 기본적으로 하나의 CPU 코어만 사용한다. 이는 멀티코어 클라우드 인스턴스에서 자원을 충분히 활용하지 못한다는 뜻이다.
PM2 를 Cluster 모드 로 실행하면 컨테이너 내부에서 수직 확장이 가능해진다. 관리자는 CPU 가용량에 따라 여러 워커 프로세스를 띄워 처리량을 최적화한다. 성능 향상 외에도 이 전략은 복원력에도 기여한다: 워커가 재시작해야 할 경우 내부 로드밸런서가 트래픽을 재분배해 서비스 가용성을 유지한다.
제안된 아키텍처의 개념도:
5. 불변성 및 보안
환경 간 일관성을 보장하기 위해 불변 아티팩트 기반 CI/CD 파이프라인을 구축했다:
- 모든 환경(Dev, Homol, Prod)에서 동일한
Dockerfile사용. - 민감한 환경 변수는 컨테이너 실행 시점에만 주입.
- 레지스트리 태그를 활용한 환경 구분.
애플리케이션 레이어에서는 엄격한 CORS 미들웨어(허가된 도메인만 허용)와 모든 라우트에 대한 인증 검증을 추가해 보안을 강화했다.
결론
이 사례 연구는 성숙한 생태계를 현대화하기 위해 최신 소프트웨어 엔지니어링 패턴을 적용할 수 있음을 보여준다.
Clean Architecture 로 구조를 잡고, Prisma 로 데이터 안전성을 확보하며, Docker/PM2 로 운영 효율성을 높인 결과, 유지보수가 쉽고 확장 가능한 견고한 백엔드를 만들 수 있었다.
