코디잉

8주차 - WIL (Weekly I Learned) 본문

PROJECT/항해플러스 Lite 백엔드

8주차 - WIL (Weekly I Learned)

yong_ღ'ᴗ'ღ 2025. 7. 13. 20:35

🧐 8주차 과제 

1. Application Event: 이벤트를 활용하여 트랜잭션과 관심사를 분리하기

2. Asynchronous Design: 대기열 기능에 대해 Redis 기반의 설계를 진행

3. feedback - RedisReservationRankingManager 상수값 외부 설정으로 빼기

4. feedback - redis 분산락 TTL 충분히 길게 줘서 중복실행 확률 낮추기

🙋‍♀️ 고민했던 부분

① Event 활용하여 트랜잭션과 관심사 분리

◾ 기존에는 모든 로직이 ReservationService.pay() 내에 포함되어 있어, 핵심로직과 부가로직이 혼재된 구조였다.

     ◽ 핵심 로직: 대기열 토큰 검증, 포인트 사용, 좌석 상태 변경  
     ◽ 부가 로직: 예매율 랭킹 갱신, 대기열 토큰 완료 처리, 결제 내역 저장  

◾ 위 부가로직은 예외가 발생하더라도 핵심 로직에는 영향을 주지 않아야 했고, 성능과 유지보수 측면에서도 이벤트 기반 처리가 적절하다고 판단했다.

◾ 특히 결제 실패 이력 저장의 경우, 핵심 트랜잭션이 롤백되더라도 반드시 저장되어야 하므로 @Transactional(propagation = REQUIRES_NEW) 로 별도 트랜잭션으로 분리해 처리함.

 

② Redis로 대기열 기능 구현

◾  Redis ZSET 사용 결정. 무제한 입장을 막기 위해 allowedLimit(ex. 1000명) 설정

◾  스케줄러 처리 방식

     ◽  ALLOWED 상태에서 TTL이 지난 토큰은 TIMEOUT으로 변경

     ◽   WAITING 상태 중 상위 토큰들을 ALLOWED로 전환 (현재 허용 인원 기준)

➡️ 한 번에 너무 많은 대기열을 조회하지 않기 위해 Redis에서 limit × 2 정도만 추출 후 상태 체크

✅ Keep (잘한 점 / 유지할 점)

- @TransactionalEventListener 를 활용해 AFTER_COMMIT 시점에만 부가로직 수행

- 이벤트 리스너를 비동기 처리(@Async)하여 주 쓰레드의 성능 저하 방지

- 예외 발생 시 REQUIRES_NEW 로 실패 이력 저장 → 핵심 트랜잭션의 영향을 받지 않도록 설계

- 예매율 랭킹 / 토큰 상태 변경 / 결제 성공 내역 저장을 각각 이벤트 리스너로 분리하여 단일 책임 원칙 지킴

- 대기열 진입 로직을 별도의 Facade 서비스로 구성하여 토큰 발급 → 대기열 진입 흐름을 하나의 API로 처리

- 토큰 상태값은 RDB에서 관리하고, 큐 순번은 Redis에서 분리 관리하여 상태/순번 책임 분리

❗️Problem (문제점) &💡Try (해결을 위한 시도)

- JPA dirty checking 기반의 토큰 상태 변경(엔티티 변경만)도 이벤트로 분리하려 했지만, 트랜잭션 범위를 벗어나면 dirty checking이 적용되지 않아 다시 save 호출이 필요함 → 이를 고려해 save 호출을 명시적으로 추가함

- 처음에는 ReservationService가 QueueService를 호출하는 구조로 구현했으나 서비스 간 의존성 문제 발생 가능성 있어서 → Facade 패턴 도입

💬 이번 주 알게 된 것들

- @TransactionalEventListener 의 phase 종류 (BEFORE_COMMIT, AFTER_COMMIT, AFTER_ROLLBACK) 와 각각의 동작 시점

- 서비스 간 호출이 필요할 경우 Facade 계층 사용

Comments