Project Portfolio

Project Portfolio

제품 완성도를 높이기 위한 구조적 문제 해결 경험을 정리한 포트폴리오입니다.
각 프로젝트는 문제 원인 → 해결 과정 → 해결 순으로 정리되어 있습니다.

Project 01

Blaybus MVP 개발 해커톤 TypeScript Rewrite

AI 기반 3D 학습 플랫폼 백엔드 프로젝트

Blaybus MVP 개발 해커톤 TypeScript Rewrite Case 01 · auth / authorization boundary

1. Fastify Auth Plugin 기반 인증/인가 구조 설계

라우트마다 흩어질 수 있는 인증 검증을 Fastify auth plugin으로 공통 처리하고, Repository 계층에서 사용자 리소스 접근 조건을 강제했습니다.

Fig. 1 — Fastify auth plugin 으로 인증 경계 격리
Fig. 1 — Fastify auth plugin 으로 인증 경계 격리

문제

  • 라우트별 인증 검증이 흩어지면 중복 코드와 누락 위험이 발생
  • 사용자별 리소스 접근 제어가 일관되지 않으면 타 사용자 데이터 노출 가능성 존재

해결

  • Public route와 Protected route를 분리
  • JWT 검증을 Fastify plugin으로 공통 처리
  • 검증된 사용자 정보를 request.user에 주입
  • Repository 계층에서 user_id 조건을 강제 적용

결과

  • 인증 로직 중복 감소
  • 사용자 소유 리소스 접근 정책 일관화
  • 보호 API의 보안 경계 명확화
Blaybus MVP 개발 해커톤 TypeScript Rewrite Case 02 · route-service-repository-driver

2. 기능별 API 정책을 Route-Service-Repository-Driver 구조로 분리

사용자 기능별로 달라지는 API 정책을 Route Handler, Service Layer, Repository Layer, Postgres Driver로 분리하여 라우트 비대화와 정책 누락 위험을 줄였습니다.

Fig. 2 — 사용자 흐름과 API 계약 · 정책 매핑
Fig. 2 — 사용자 흐름과 API 계약 · 정책 매핑

문제

  • Route Handler에 정책과 DB 접근이 섞이면 라우트가 비대해지고 정책 누락 위험 발생
  • ownership check, answer hidden 같은 보안성 정책은 일관된 위치에서 관리할 필요가 있었음

해결

  • API 서버를 Route Handler, Service Layer, Repository Layer, Postgres Driver의 4계층으로 분리
  • 사용자 흐름별 정책을 API 계층에 매핑하고 소유권 검사와 정답 은닉 정책을 일관된 위치에서 관리

결과

  • 기능별 정책의 위치가 명확해짐
  • Memo 소유권 검사와 Exam 정답 은닉 정책을 일관되게 적용
  • 기능 추가 시 변경 영향 범위 감소
Blaybus MVP 개발 해커톤 TypeScript Rewrite Case 03 · migration / delete simplification

3. Strangler Fig 마이그레이션과 ON DELETE CASCADE

v1 API와 v2 rewrite를 병행 운영하면서 기존 다운로드 URL 호환성을 유지하고, 삭제 정합성 문제는 DB cascade로 단순화했습니다.

Fig. 3 — Strangler Fig 이전과 ON DELETE CASCADE 기반 단순화
Fig. 3 — Strangler Fig 이전과 ON DELETE CASCADE 기반 단순화

문제

  • 기존 v1 API는 X-User-ID header 기반 인증을 사용
  • 기존 클라이언트와 다운로드 URL 호환성을 유지해야 했음
  • 노드 삭제 시 연결 파일, 연결 관계, 노드 row를 애플리케이션에서 순차 삭제해야 했음
  • 삭제 순서 오류 또는 중간 실패 시 일부 데이터가 남는 정합성 문제 발생 가능

해결

  • Strangler Fig 패턴을 적용해 v1과 v2를 병행 운영
  • GET 응답의 download URL 규격을 유지하여 클라이언트 변경 범위 최소화
  • 인증 방식은 v2에서 JWT Authorization Bearer token으로 전환
  • 하위 테이블에 ON DELETE CASCADE 적용

결과

  • 기존 API를 즉시 제거하지 않고 안정적으로 v2 전환 가능
  • 클라이언트 호환성 유지
  • 인증 정책 개선
  • 삭제 로직 단계 감소
  • 데이터 정합성과 유지보수성 개선
Project 02

PeakPass 예약·결제 API 서버

예약, 결제 정산, 티켓 발급의 정합성과 멱등성을 시각화한 백엔드 프로젝트

PeakPass 예약·결제 API 서버 Case 01 · command / query separation

1. 상태 변경 API와 조회 API를 분리한 예약·결제 서버 구조

쓰기 API와 조회 API의 책임을 나누고, PostgreSQL은 source of truth로, Redis는 운영 보조 상태로 분리했습니다.

Fig. 1 — 쓰기/읽기 API 분리 및 Source of Truth 배치
Fig. 1 — 쓰기/읽기 API 분리 및 Source of Truth 배치

문제

  • 예약 생성, 결제 요청, 정산 웹훅은 주문·결제·티켓 상태를 변경하는 작업이라 정합성 관리가 중요했음
  • 쓰기와 읽기 요청이 섞이면 상태 변경 흐름을 추적하기 어렵고 API 책임이 모호해질 수 있었음

해결

  • PostgreSQL은 주문·티켓·결제 기록의 source of truth로 사용
  • Redis는 hold TTL, rate-limit, idempotency cache 용도로 분리
  • 상태 변경 기능은 REST 명령 API로 구성, 조회 기능은 GraphQL 조회 API로 분리

결과

  • 멱등성, rate-limit, 트랜잭션 처리를 쓰기 API에 집중할 수 있었음
  • 조회 API는 화면 요구사항에 맞게 필요한 데이터만 선택적으로 제공 가능
PeakPass 예약·결제 API 서버 Case 02 · reserve / checkout / settlement / issue

2. 미결제 티켓 발급을 방지하기 위한 예약·결제 상태 분리

미결제 주문으로 티켓이 생성되는 문제를 방지하고자, 결제 요청이 생성되더라도 정산완료 전까지 티켓을 발급하지 않도록 설계했습니다.

Fig. 2 — 예약 → 체크아웃 → 정산 완료 후 티켓 발급
Fig. 2 — 예약 → 체크아웃 → 정산 완료 후 티켓 발급

문제

  • 결제 요청 생성만으로 티켓을 발급하면 미결제 주문에 티켓이 생성될 수 있음
  • 클라이언트 응답만으로 결제를 확정하면 실제 상태와 서버 상태가 불일치할 수 있음
  • 예약, 결제 대기, 결제 확정, 티켓 발급 상태가 섞이면 주문 추적이 어려움

해결

  • 예약 생성 시 좌석을 임시 확보하고 reserved 상태로 관리
  • 체크아웃 요청 시 order를 payment_pending 상태로 생성
  • Payment Provider의 settlement webhook 수신 후 order를 paid로 변경
  • paid 상태가 된 주문에 대해서만 ticket issuance 실행

결과

  • 결제 확정 기준을 settlement webhook으로 일원화
  • 주문 상태 추적이 명확해짐
  • 예약 → 결제 → 정산 → 티켓 발급 흐름의 데이터 정합성 개선
PeakPass 예약·결제 API 서버 Case 03 · concurrency / row-level lock

3. 동시 체크아웃과 row-level lock

동일 이벤트에 대한 동시 체크아웃 요청에서도 좌석 수가 중복 차감되지 않도록 PostgreSQL row-level lock으로 재고 변경을 직렬화했습니다.

Fig. 3 — 동시 체크아웃에서 row-level lock 으로 oversell 방지
Fig. 3 — 동시 체크아웃에서 row-level lock 으로 oversell 방지

문제

  • 동일 이벤트에 대해 여러 사용자가 동시에 체크아웃할 경우 available_seats가 중복 차감될 수 있음
  • 단순 조회 후 업데이트 방식은 oversell 위험이 있음

해결

  • checkout transaction을 SERIALIZABLE하게 처리
  • SELECT ... FOR UPDATE로 이벤트 row에 lock을 획득
  • lock을 획득한 트랜잭션만 좌석 수를 검사하고 차감
  • 좌석 부족 시 주문 생성 없이 실패 처리

결과

  • 동시에 요청이 들어와도 좌석 수가 음수가 되는 상황 방지
  • PostgreSQL을 source of truth로 두고 정합성을 보장
PeakPass 예약·결제 API 서버 Case 04 · idempotent settlement webhook

4. settlement webhook 멱등성 처리

Payment Provider의 settlement webhook이 중복 전송되어도 같은 결제에 대해 티켓이 한 번만 발급되도록 멱등성 방어선을 구성했습니다.

Fig. 4 — settlement webhook 멱등성 구조와 중복 발급 방지
Fig. 4 — settlement webhook 멱등성 구조와 중복 발급 방지

문제

  • Payment Provider의 settlement webhook은 네트워크 문제로 중복 전송될 수 있음
  • 동일 결제에 대해 티켓이 중복 발급되면 치명적인 데이터 정합성 문제가 발생

해결

  • 1차 방어: Redis idempotency lock으로 동일 key 요청 중복 처리
  • 2차 방어: pg_advisory_xact_lock으로 같은 결제 키의 DB 처리 직렬화
  • 3차 방어: payment_records.provider_transaction_id UNIQUE 제약 적용
  • 4차 방어: 이미 paid order 또는 existing ticket 여부 확인

결과

  • 동일 settlement webhook이 반복 호출되어도 티켓은 한 번만 발급
  • 중복 요청은 결과 캐시 또는 no-op 처리