📖 백엔드 디렉토리 추가 스터디
지난 번 스터디한 내용에 이어서. 디렉토리 구조라는 것이 구체적으로 와닿지 않아서 GPT에 서비스 도메인별로 예시를 들어달라고 해보았다. 근데 아무래도 직접 보는 등 실전 경험이 없으니 구체적으로 와닿지 않아서 한계가 느껴졌다. 아래 디렉토리 구조 예시도 N회 정독해야할듯..
하지만 이 과정에서 개발과 비즈니스의 관계에 대한 인사이트를 얻었기 때문에 의미 있는 스터디였다. 글의 마무리 부분에도 적었지만 디렉토리 구조는 곧 폴더 = 도메인 = 기능 단위로, 서비스의 실제 작동 방식(비즈니스 흐름)을 코드 구조에 반영한 것이다.
아래 표는 디렉토리 구조를 보고 나서 역으로 찾아본 내용이다. 디렉토리 구조를 보기 전에 개념을 쓱 보고 다시 읽었을 때 더 이해가 잘 되어서 앞쪽에 추가해뒀다.
백엔드 컴포넌트별 역할 정리
| 구분 | 하는 일 (역할) | 안에 들어있는 것 | 처리 가능한 요청 예시 |
|---|---|---|---|
| Controller | 클라이언트의 HTTP 요청을 받고 응답을 돌려줌 | 라우트 핸들러 (@Get(), @Post() 등) |
- GET /hotels → 호텔 목록 조회- GET /hotels/:id → 호텔 상세- POST /hotels → 새 호텔 등록- PUT /hotels/:id → 호텔 수정 |
| Service | 요청에 대한 실제 비즈니스 로직을 처리함 | 예약 가능 여부 확인, 가격 계산, 필터링 함수 등 | - getHotels() → 호텔 필터링, 정렬- bookHotel(id) → 예약 처리- checkAvailability() |
| Model | DB 구조 정의. 어떤 데이터를 저장하고 읽을지 명세함 | ORM Entity 클래스 (Hotel, Room 등) |
- Hotel 모델: name, location, price, rating 등의 필드로 구성 |
| Adapter | 제휴사 API 또는 외부 시스템과의 연동 담당 | API 요청/응답 핸들링 (fetchFromAgoda() 등) |
- GET /agoda/hotels/:id 호출- 아고다 API 호출 후 JSON → 내부 형식 변환 |
| Repository | DB에 실제로 접근해서 읽고/쓰기 수행 | findById(), save(), delete() 등 DB 쿼리 메서드 |
- findHotelById(id)- saveNewHotel(data)- updateHotel(id, data) |
| Gateway | 웹소켓이나 실시간 이벤트를 처리 | 소켓 연결 관리, 알림 push | - @SubscribeMessage('joinRoom')- emit('newReservation') 등 |
역할이 나눠진 이유
코드의 복잡도를 줄이고 유지보수 효율성 및 확장성을 높이기 위함. 각 부분에 책임을 명확히 부여하는 것이 핵심이다.
참고: Model은 데이터의 구조(스키마)를 정의하고 Repository는 그 구조를 바탕으로 데이터를 조회/저장/수정/삭제하는 역할
1. 작업 분리
- 한 파일 또는 한 클래스가 너무 많은 일을 하지 않도록 분리
- 예: Controller는 요청을 받고, Service는 비즈니스 로직, Repository는 DB 처리만 담당
2. 유지보수성 향상
- 특정 로직을 수정할 때 다른 모듈에 영향을 주지 않고 변경 가능
- 예: DB 구조가 바뀌면 Model만 수정하면 되고, Controller나 Service는 그대로
3. 재사용성 증가
- Service나 Repository는 여러 Controller에서 재사용
- 예: 호텔 검색 기능은 여러 API 엔드포인트에서 공통으로 호출
4. 테스트 용이성
- 각각의 컴포넌트가 독립되어 있어 단위 테스트를 쉽게 작성
- 예: Service 로직만 mock을 사용해서 테스트 가능
- Mock: 진짜 객체처럼 동작하지만 실제 동작하지 않고 가짜 값을 반환하는 테스트 전용 도구
5. 협업에 유리
- 여러 명이 동시에 작업할 때 충돌 감소
- 예: A는 Controller, B는 Service나 DB 쿼리 작성 등으로 역할 분담
도메인별 디렉토리 구조 예시
예시로 가져온 내용. 실제 서비스들의 운영 상황에 맞춰 구성하면 더 다양하고 복잡할 것이다.
1. 여행 서비스 예시
- 항공, 호텔, 티켓 등 다양한 예약 도메인이 존재
- 외부 제휴사 API(GDS, OTA 등) 연동이 빈번하며 실시간성 요구
- 결제, 예약 확정, 발권 등 비동기 이벤트 처리 구조 필요
- 예약 변경·취소·환불에 따른 상태 전이와 예외 처리 중요
┣ 📂users ← 사용자 계정, 인증, 포인트, 즐겨찾기
┣ 📂auth ← 로그인, OAuth, 토큰 관리
┣ 📂hotels
┃ ┣ 📄hotel.controller.ts
┃ ┣ 📄hotel.service.ts
┃ ┣ 📄hotel.repository.ts
┃ ┣ 📄room.service.ts
┃ ┣ 📄room.repository.ts
┃ ┣ 📄hotel.model.ts
┃ ┗ 📄agoda.adapter.ts ← 제휴사 API 연동
┣ 📂flights
┃ ┣ 📄flight.controller.ts
┃ ┣ 📄flight.service.ts
┃ ┣ 📄flight.model.ts
┃ ┗ 📄gds.adapter.ts ← GDS 연동
┣ 📂tickets
┃ ┣ 📄ticket.controller.ts
┃ ┣ 📄ticket.service.ts
┃ ┗ 📄partner-api.ts ← 티켓 파트너 API 연동
┣ 📂bookings
┃ ┣ 📄booking.controller.ts
┃ ┣ 📄booking.service.ts ← 예약 공통 로직
┃ ┣ 📄booking.model.ts
┃ ┣ 📄cancellation.service.ts
┃ ┗ 📄reschedule.service.ts
┣ 📂payments
┃ ┣ 📄payment.controller.ts
┃ ┣ 📄payment.service.ts
┃ ┣ 📄refund.service.ts
┃ ┗ 📄toss.adapter.ts ← 토스페이먼츠 연동
┣ 📂reviews
┃ ┣ 📄review.controller.ts
┃ ┣ 📄review.service.ts
┣ 📂notifications ← 예약 알림, 결제 알림 (Email, Kakao, SMS)
┣ 📂promo ← 쿠폰, 프로모션 코드, 제휴 혜택
┣ 📂admin ← 운영자/파트너사 CMS 관리 기능
┣ 📂cs ← 고객센터, 문의, 1:1상담, 환불 사유 기록 등
┣ 📂common ← DTO, 에러, 로거, 헬퍼 유틸
┣ 📂config ← 환경변수, 결제 키 등 설정
┣ 📂middlewares ← 인증, 로깅, 요청 제한 등
┣ 📂jobs ← 정기 스케줄링 (예약 확인, 결제 마감 등)
┣ 📂external-apis ← 호텔/항공/티켓 제휴사 API 모음
┣ 📂audit-logs ← 운영, 결제 변경 로그 감사 용도
┗ 📄main.ts
시나리오: 사용자가 항공권 예약을 완료하는 경우
1단계: 항공편 검색
[사용자] 왕복 여정 선택 → GET /flights/search
→ flights.controller.ts
→ flights.service.ts → external-apis/gds.adapter.ts 호출
→ 여정 옵션, 운임조건, 환불/수하물 정책 포함 응답
2단계: 예약 가능 여부 확인
[사용자] 항공편 선택 및 탑승객 정보 입력 → POST /bookings/precheck
→ bookings.controller.ts
→ precheck.service.ts → GDS API에 예약 가능 여부 실시간 조회
→ 좌석 수, 예약 가능 여부 확인 후 응답
3단계: 결제 진행
[사용자] 결제 진행 → POST /bookings/confirm
→ bookings.service.ts: 예약 생성 + 결제 준비
→ payments.service.ts → PG사 API 호출 (external-apis/toss.adapter.ts)
4단계: 결제 완료 처리
[PG사 결제 성공 콜백 도착] → POST /webhooks/payments
→ 결제 상태 변경 → 예약 확정 처리
5단계: 티켓 발권
[GDS 티켓 발권 요청] → bookings/ticketing.service.ts
→ external-apis/gds.adapter.ts 호출 → 발권 처리 (e-ticket 발급)
→ 발권 성공 시: ticket.number 저장
6단계: 알림 발송
[알림 발송] → notifications.service.ts
→ 이메일, 문자로 여정 정보 및 e-ticket 전송
7단계: 이력 저장
[이력 저장] → audit-logs.service.ts
→ 예약 생성, 결제 완료, 발권 이벤트 로깅
예외 처리 (중간에 실패 시)
→ 예약 롤백, 결제 취소, 알림 발송, 사용자에게 실패 사유 응답
연관 디렉토리: /flights, /bookings, /bookings/ticketing, /payments, /external-apis, /webhooks, /users, /notifications, /audit-logs, /cancellation, /refunds, /compliance
2. 커머스 서비스 예시
- 상품 등록/관리, 장바구니, 주문, 결제, 배송, 프로모션, 정산 등 존재
- 비즈니스 로직이 복잡하고, 이벤트 기반 처리(주문 완료 후 배송 처리 등) 많음
- 도메인 단위로 모듈화된 구조로 구성하는 경우가 많음
┣ 📂products ← 상품 도메인
┃ ┣ 📄product.controller.ts
┃ ┣ 📄product.service.ts
┃ ┣ 📄product.model.ts
┃ ┗ 📄product.repository.ts
┣ 📂orders ← 주문 도메인
┃ ┣ 📄order.controller.ts
┃ ┣ 📄order.service.ts
┃ ┣ 📄order.model.ts
┃ ┣ 📄order.status.ts
┃ ┗ 📄order.repository.ts
┣ 📂payments ← 결제 도메인
┃ ┣ 📄payment.controller.ts
┃ ┣ 📄payment.service.ts
┃ ┣ 📄payment.model.ts
┃ ┗ 📄payment.gateway.ts ← PG사 연동 처리
┣ 📂users ← 사용자 도메인
┃ ┣ 📄user.controller.ts
┃ ┣ 📄user.service.ts
┃ ┗ 📄user.model.ts
┣ 📂cart ← 장바구니 도메인
┣ 📂promotions ← 쿠폰, 할인 등 마케팅 기능
┣ 📂settlement ← 판매자 정산 관련
┣ 📂notification ← 알림 발송 (이메일, SMS 등)
┣ 📂middlewares
┣ 📂config
┣ 📂common ← 공통 유틸 / 에러 / DTO
┣ 📂jobs ← 배치 / 스케줄러
┗ 📄main.ts
시나리오: 사용자가 상품을 장바구니에 담고, 결제까지 완료하는 경우
1단계: 장바구니 담기
[사용자] 상품 상세 페이지에서 "장바구니 담기" 클릭
→ POST /cart/items
→ cart.controller.ts
→ cart.service.ts: 장바구니에 상품 추가
→ cart.model.ts: DB 저장
2단계: 주문 생성
[사용자] 장바구니 페이지에서 "결제하기" 클릭
→ POST /orders
→ order.controller.ts
→ order.service.ts: 주문 정보 생성, 결제 전 상태로 저장
→ payment.service.ts 호출: 결제 요청 생성
→ toss.adapter.ts 호출: PG사 API로 결제 요청
3단계: 결제 완료 처리
[PG사 응답 콜백] 결제 완료 이벤트 수신
→ /webhooks/payments
→ webhook.controller.ts → payment.service.ts
→ order.service.ts에서 결제 상태 → '완료'로 업데이트
4단계: 결제 완료 확인
[사용자] 결제 완료 페이지 노출
→ order 정보 조회 → 배송 준비 상태 확인
연관 디렉토리: cart/, orders/, payments/, webhooks/, users/, notifications/
3. 핀테크 서비스 예시
- 인증, 계좌 연결, 송금, 결제, 투자, 보험 등 민감하고 보안이 중요한 기능이 많음
- 사용자 기반 금융 계좌 연결 및 외부 금융 API 연동 빈번
- 트랜잭션 안정성과 감사 로그, 이중 인증 등의 보안 구조 고려
┣ 📂auth ← 인증/인가 (JWT, OAuth, MFA 등)
┃ ┣ 📄auth.controller.ts
┃ ┣ 📄auth.service.ts
┃ ┣ 📄jwt.strategy.ts
┃ ┗ 📄mfa.service.ts
┣ 📂accounts ← 유저 계좌 (연결된 금융기관 정보 등)
┃ ┣ 📄account.controller.ts
┃ ┣ 📄account.service.ts
┃ ┗ 📄account.model.ts
┣ 📂transactions ← 송금/결제/거래 내역
┃ ┣ 📄transfer.service.ts
┃ ┣ 📄deposit.service.ts
┃ ┣ 📄transaction.model.ts
┃ ┗ 📄webhook.handler.ts ← 외부 금융기관 이벤트 처리
┣ 📂investments ← 투자 관련 모듈
┣ 📂insurances ← 보험 비교 및 연동
┣ 📂users
┣ 📂notifications ← 알림 (push/SMS/email)
┣ 📂compliance ← 법적 감사 및 KYC/AML 관련 모듈
┣ 📂audit-logs ← 감사 기록 (Audit Trail 저장)
┣ 📂middlewares
┣ 📂common
┃ ┣ 📄exceptions/ ← 비즈니스 예외 정의
┃ ┃ ┣ 📄payment.exception.ts
┃ ┃ ┗ 📄booking.exception.ts
┃ ┗ 📄error-handlers/ ← 전역 에러 처리
┣ 📂config
┣ 📂external-apis ← 외부 금융기관/PG사/신용정보사 등 API 연동
┗ 📄main.ts
시나리오: 사용자가 새 계좌를 연결하고, 해당 계좌로 송금하는 경우
1단계: 계좌 연결
[사용자] 마이페이지에서 "새 계좌 연결" 버튼 클릭
→ POST /accounts/connect
→ account.controller.ts
→ account.service.ts
→ external-apis/bank-api.adapter.ts → 은행 오픈 API 호출 (계좌 인증, 이름 확인)
→ account.model.ts에 계좌 정보 저장
2단계: 송금 요청
[사용자] 연결된 계좌에서 다른 사용자에게 송금 요청
→ POST /transactions/transfer
→ transaction.controller.ts
→ transfer.service.ts
→ 계좌 잔고 확인
→ external-apis/bank-api.adapter.ts → 실시간 이체 API 호출
→ transaction.model.ts에 거래 기록 저장
3단계: 송금 완료 처리
[은행 API 콜백] 송금 성공 응답 수신
→ webhook.handler.ts
→ transaction 상태 → '성공'으로 갱신
4단계: 거래 내역 확인
[사용자] 거래 내역 조회
→ GET /transactions
→ transaction.service.ts → 거래 목록 반환
연관 디렉토리: accounts/, transactions/, external-apis/, webhooks/, users/, audit-logs/
결국 디렉토리는 "비즈니스 흐름"
폴더 = 도메인 = 기능 단위
- 폴더 하나가 하나의 기능 또는 도메인을 대표
- 예시:
/payments디렉토리는 '결제'라는 비즈니스 기능을 담당 - 실제 서비스에서도 '상품 구매 → 결제 → 결제 승인 → 결제 내역 조회'라는 비즈니스 흐름이 존재
- 이 흐름의 핵심 단계마다 디렉토리 대응: 비즈니스 플로우 차트와 거의 동일한 정보
마무리하며
- "이 기능이 왜 필요한가?"
- "이 코드 왜 짜야 하지?"
- "이 API는 어떤 사용자 행동에서 시작되나? 어떻게 연결되어야 하나?"
- "문제가 생기면 전체 비즈니스에 어떤 영향을 줄까?"
이런 질문들을 습관처럼 던지면 단순히 기능을 구현하는 수준을 넘어 비즈니스 맥락을 고려한 설계와 개발이 가능할 것 같다.
문제를 예방하고 장애가 발생했을 때도 빠르게 전체 흐름 속에서 원인을 추론할 수 있는 힘이 생길 텐데.. 아마 실전에서는 당면한 문제를 풀어내는 것만으로도 벅차겠지..?
이렇게 스터디를 해보니 좋은 개발자는 코드만 잘 짜는 사람이 아니라, 코드가 연결된 세상을 함께 이해하는 사람이겠구나 싶다.
디렉토리 구조를 통해 비즈니스 도메인을 이해하고 각 컴포넌트의 역할과 책임을 명확히 하며, 전체적인 서비스 흐름을 파악하는 것. 이것이 백엔드 개발의 핵심이 아닐까 생각이 든다.