Skip to content

LEEDONGH00N/form-pass-server

Repository files navigation

Form-PASS Server

스크린샷 2026-03-05 오후 6 34 35

호스트를 위한 간편 예약 관리 솔루션의 백엔드 서버입니다.

이벤트 생성 → 예약 폼 공유 → 선착순 예약 → QR 체크인까지 하나의 플로우로 처리합니다.

서비스 링크 : https://www.form-pass.life

프론트엔드 : https://github.com/LEEDONGH00N/form-pass-client


기술 스택

분류 기술
Language Java 17
Framework Spring Boot 3.4.12
ORM Spring Data JPA (Hibernate)
Database MySQL 8.0 (RDS), H2 (테스트)
Auth Spring Security + JWT (jjwt 0.11.5)
Infra AWS EC2, RDS, S3
CI/CD GitHub Actions
Monitoring Spring Actuator + Micrometer Prometheus
API Docs Springdoc OpenAPI 2.7.0 (Swagger UI)
Mail Spring Mail (Google SMTP)
Cache Caffeine (이메일 인증코드 TTL 관리)
Build Gradle

인프라 구조

[Client - Vercel]
        │
        ▼ HTTPS
[EC2 Instance]
  ├── Nginx (Reverse Proxy, SSL)
  └── Spring Boot (ticket-form.jar)
        ├── RDS MySQL (데이터 저장)
        ├── S3 (이미지 저장)
        └── Google SMTP (메일 발송)

ERD

┌──────────┐       ┌──────────────┐       ┌─────────────────┐
│  hosts   │       │   events     │       │ event_schedules  │
├──────────┤       ├──────────────┤       ├─────────────────┤
│ id (PK)  │──1:N─▶│ id (PK)      │──1:N─▶│ id (PK)         │
│ email    │       │ host_id (FK) │       │ event_id (FK)   │
│ password │       │ title        │       │ startTime       │
│ name     │       │ location     │       │ endTime         │
│ role     │       │ description  │       │ maxCapacity     │
└──────────┘       │ eventCode    │       │ reservedCount   │
                   │ isPublic     │       └────────┬────────┘
                   └──────┬───────┘                │
                          │                        │ 1:N
                          │ 1:N                    ▼
                   ┌──────┴───────┐       ┌─────────────────┐
                   │form_questions│       │  reservations   │
                   ├──────────────┤       ├─────────────────┤
                   │ id (PK)      │       │ id (PK)         │
                   │ event_id(FK) │       │ schedule_id(FK) │
                   │ questionText │       │ guestName       │
                   │ questionType │       │ guestPhone(AES) │
                   │ isRequired   │       │ ticketCount     │
                   └──────┬───────┘       │ qrToken (UUID)  │
                          │               │ isCheckedIn     │
                          │               │ status          │
                          │               └────────┬────────┘
                          │                        │ 1:N
                          │               ┌────────┴────────┐
                          │               │  form_answers   │
                          └──────────────▶├─────────────────┤
                               N:1        │ id (PK)         │
                                          │ reservation_id  │
                                          │ question_id(FK) │
                                          │ answerText      │
                                          └─────────────────┘

┌──────────────┐
│ event_images │
├──────────────┤
│ id (PK)      │
│ event_id(FK) │  ◀── events 1:N
│ imageUrl     │
│ orderIndex   │
└──────────────┘

Enum 타입:

  • Role : HOST
  • ReservationStatus : CONFIRMED, CANCELLED
  • QuestionType : TEXT, CHECKBOX, RADIO

핵심 기능 상세

동시성 제어 (선착순 예약)

요청 A ──▶ ReservationFacade
           └─ LockExecutor.executeWithLock("schedule:1")
              └─ ReservationService (@Transactional 내부)
                 └─ reservedCount 검증 → 증가 → 예약 생성
요청 B ──▶ Lock 대기 후 순차 처리
  • InMemoryLockExecutor (ReentrantLock) + Facade 패턴으로 락과 트랜잭션 분리
  • ConcurrentHashMap<String, ReentrantLock> — 키별 공정 락 (FIFO), tryLock(5, SECONDS)
  • 락은 @Transactional 바깥에서 획득하여 DB 커넥션 점유 시간 최소화
  • LockExecutor 인터페이스 기반 설계로 추후 분산 락(Redis 등) 전환 가능
  • reservedCount >= maxCapacityIllegalStateException (409)
  • 동일 스케줄 + 전화번호 중복 예약 거부

QR 체크인 플로우

예약 확정 → UUID 기반 qrToken 발급
         → 게스트가 QR 코드 제시
         → 호스트가 QR 스캔 (POST /api/host/checkin)
         → 또는 수동 체크인 (PATCH /api/host/reservations/{id}/checkin)
         → isCheckedIn = true 업데이트

이미지 업로드 (Presigned URL)

1. 클라이언트 → POST /api/host/s3/presigned-url (fileName, contentType)
2. 서버 → S3 Presigned PUT URL + 최종 fileUrl 반환
3. 클라이언트 → S3에 직접 PUT 업로드
4. 클라이언트 → 이벤트 생성 시 fileUrl을 images 목록에 포함

아키텍처 진화 과정

버전 동시성 제어 방식 문제점 / 개선 이유
v1 @Lock(PESSIMISTIC_WRITE) 비관적 락 DB row-level 락으로 트랜잭션 전체 구간 동안 커넥션 점유 → 고부하 시 커넥션 풀 고갈 위험
v2 Redis + Redisson 분산 락 단일 인스턴스 환경에 Redis 인프라 운영 비용이 과도, 장애 포인트 증가
v3 (현재) 인메모리 ReentrantLock + Facade 패턴 락과 트랜잭션 분리로 커넥션 점유 최소화, 인프라 의존성 제거, LockExecutor 인터페이스로 확장성 유지

각 의사결정의 상세 근거: docs/adr/


테스트

계층 전략 테스트 수
Service (단위) @ExtendWith(MockitoExtension.class) + Mock 46개
Repository (통합) @DataJpaTest + H2 + QueryDSL 8개
Controller (슬라이스) @WebMvcTest + @MockBean 10개
Context Load @SpringBootTest 1개
합계 65개
./gradlew clean test    # 전체 테스트 실행

실행 방법

사전 조건

  • Java 17+
  • MySQL 8.0 (local 프로필) 또는 H2 (test 프로필)
  • Gradle 8+

CI/CD

CI (Pull Request → main)

PR 생성 → GitHub Actions → ./gradlew clean test → 테스트 결과 PR 코멘트

CD (Push → main)

main 병합 → GitHub Actions → JAR 빌드 → SCP로 EC2 전송 → deploy 스크립트 실행

모니터링

엔드포인트 용도
/actuator/health 서버 상태 (DB, Disk 포함)
/actuator/prometheus Prometheus 메트릭 수집

수집 메트릭: HTTP 요청 응답시간 분포 (P95, P99), JVM 메모리/GC, Tomcat 스레드 풀


부하 테스트

k6를 사용하여 예약 생성 API의 동시성 안정성을 검증합니다.

# Spike 테스트 (50 → 100 VU)
k6 run k6/reservation-test.js

# Constant 테스트 (50 VU 고정)
k6 run k6/reservation-constant-test.js

테스트 결과 요약

시나리오 VU 총 요청 평균 응답 p95 에러율 초과 예약
Spike (100 VU 피크) 0→100 11,200건 706ms 1,000ms 0% 0건
Constant (50 VU) 50 8,152건 236ms 567ms 0% 0건

상세 결과: docs/reports/load-test-report.md, docs/reports/constant-test-report.md

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors