데이터를 불러오는 중입니다...
호텔 객실 조회부터 예약, 결제, 관리자 운영 시스템까지 아우르는 통합 예약 플랫폼을 1인 백엔드로 기획·개발 중인 프로젝트입니다. 고객사 요구사항 정의부터 Core 아키텍처 설계, 동시성 제어, 인프라 트러블슈팅까지 전 과정을 주도했으며, FE 3명과의 협업 속에서 PM·PL도 겸임했습니다. 비즈니스 확장성을 고려한 데이터 모델링과 결제·재고의 정합성 보장이 핵심 목표였습니다.
이전 '화훼 도소매 B2B 주문 플랫폼' 프로젝트에서 상위 Product 개념 없이 상품 테이블이 독립적으로 파편화되어 있던 구조를 경험했습니다. 결제 정보를 저장하는 하나의 테이블에서 2개 이상의 상품 테이블을 참조해야 했으나 FK로 묶을 수 없어, reference_id(테이블 ID)와 reference_type(테이블명)을 컬럼으로 저장한 뒤 row 데이터를 기반으로 동적 JOIN을 수행해야 하는 구조적 한계가 있었습니다.
| 대안 | 접근 | 기각 사유 |
|---|---|---|
| 현행 유지 | 문자열 참조 구조 그대로 사용 | 분기 로직 증가로 신규 상품 추가 시 수정 범위 통제 불가 |
| 완전 정규화 | 모든 관계를 FK로 연결, JOIN으로 조회 | JOIN Depth 5단계 이상 → 조회 쿼리 복잡도·성능 저하 |
| Core Product 다형성 + 전략적 비정규화 | 공통 부모 테이블을 두고 하위 테이블이 FK 참조, 재고는 객실을 직접 참조 | — |
이 경험을 바탕으로, Core Product 테이블을 공통 뿌리로 두고 type 컬럼으로 상품 종류를 식별하는 다형성 구조를 채택했습니다. 하위에 관광, 객실 등의 카테고리 테이블을 두어 관리하되, 결제·주문 등 타 테이블은 상위 Product ID만으로 관계를 맺도록 설계했습니다. 완전 정규화 대신 JOIN Depth를 최대 3단계로 제한하는 전략적 비정규화를 병행해 조회 성능을 방어했습니다.
실시간으로 감소해야 하는 재고와 외부 의존성이 높은 결제 API가 맞물리는 구조였습니다. 다수 사용자가 동시에 동일 객실을 예약 시도하거나, 결제 API 호출 이후 내부 트랜잭션이 실패하면 재고는 차감되었으나 예약은 미생성되는 데이터 불일치 상태가 발생할 수 있었습니다.
| 대안 | 접근 | 기각 사유 |
|---|---|---|
| 비관적 락(Pessimistic Lock) | SELECT FOR UPDATE로 재고 행 잠금 | 트래픽 집중 시 DB 락 대기 누적 → 처리량 저하 |
| 애플리케이션 레벨 큐 | 예약 요청을 순차 처리 | 외부 결제 API 실패 시 롤백 복잡도 급증, 추가 인프라 필요 |
| 낙관적 락(Optimistic Lock) + 원자적 트랜잭션 | version 컬럼으로 충돌 감지, 예약·결제·재고를 단일 트랜잭션으로 묶음 | — |
락 대기 없이 충돌을 감지하는 낙관적 락이 예약 시스템의 쓰기 빈도와 성능 요건에 더 적합하다고 판단했습니다. 예약·재고 차감·결제 검증을 단일 트랜잭션으로 묶어 원자성을 보장했습니다.
두 가지 이슈가 동시에 발생했습니다. 첫째, 도메인이 분리된 배포 환경에서 httpOnly secure 쿠키가 클라이언트에 전달되지 않아 로그인 세션이 유지되지 않는 현상이 있었습니다. 둘째, 관리자 API 경로가 늘어날수록 각 컨트롤러마다 인증 Guard를 선언해야 하는 구조로, 누락 가능성과 유지보수 비용이 높았습니다.
| 대안 | 접근 | 기각 사유 |
|---|---|---|
| 쿠키 → Authorization 헤더 전환 | JWT를 헤더로 전달 | httpOnly 쿠키 포기 → XSS 취약점 노출 위험 |
| Cloudflare 비활성화 | 리버스 프록시 우회 | CDN·DDoS 방어 포기, 운영 환경에서 불가 |
| Cloudflare 봇 감지 설정 조정 + MiddlewareConsumer 일괄 적용 | 서버 간 통신을 봇으로 오인한 룰 수정, 인증을 라우팅 레벨에서 일괄 처리 | — |
쿠키 보안 속성을 포기하지 않고 인프라 설정을 수정하는 방향을 선택했습니다. 인증 중앙화는 Guard 개별 선언 방식보다 MiddlewareConsumer를 통한 경로 기반 일괄 적용이 유지보수에 유리하다고 판단했습니다.
Middleware와 Guard 중 어떤 방식을 선택할지에 대한 트레이드오프 분석은 인사이트 [NestJS] Guard가 정답일까? 에서 다루고 있습니다.
Next.js와 NestJS 간 도메인 분리 환경의 아키텍처 구성에 대해서는 인사이트 [Next.js x NestJS] 프론트엔드와 백엔드의 도메인 분리, 깨부순 아키텍처 오해들 에서 다루고 있습니다.