Files

6.1 KiB

라이브 예약 LazyInitializationException 수정 Plan/TASK

For agentic workers: REQUIRED SUB-SKILL: superpowers:executing-plans를 사용해 각 task를 순서대로 실행한다. 각 단계는 체크박스 상태와 검증 기록을 즉시 갱신한다.

Goal: OSIV off 환경에서 라이브 예약 생성이 LiveRoom.reservations lazy 컬렉션 초기화 예외 없이 완료되고, 결제와 예약 저장이 하나의 트랜잭션에 참여하게 한다.

Architecture: 기존 API와 엔티티 매핑은 유지한다. LiveReservationService.makeReservation()을 서비스 계층의 쓰기 트랜잭션 경계로 만들고, 트랜잭션이 없는 통합 테스트에서 실제 Spring 프록시를 호출해 detached LiveRoom의 lazy 컬렉션 접근 오류를 재현하고 수정한다.

Tech Stack: Kotlin, Java 17, Spring Boot 2.7.14, Spring Data JPA, Hibernate, JUnit 5, H2, Gradle Wrapper

Global Constraints

  • 공개 API URL, 요청 및 응답 스키마를 변경하지 않는다.
  • spring.jpa.open-in-view=false를 유지한다.
  • LiveRoom.reservations의 fetch 전략과 LiveReservation.room setter를 변경하지 않는다.
  • 예약 중복 방지, 결제 정책, 응답 포맷을 변경하지 않는다.
  • 변경은 PRD, Plan/TASK, LiveReservationService.makeReservation(), 해당 통합 테스트로 제한한다.

파일 구조 계획

  • Create: src/test/kotlin/kr/co/vividnext/sodalive/live/reservation/LiveReservationServiceIntegrationTest.kt
    • 실제 Spring 서비스 프록시와 JPA 엔티티로 OSIV off 예약 생성 경로를 검증한다.
  • Modify: src/main/kotlin/kr/co/vividnext/sodalive/live/reservation/LiveReservationService.kt
    • makeReservation()에 쓰기 @Transactional을 추가한다.
  • Modify: docs/20260715_라이브_예약_LazyInitializationException_수정/plan-task.md
    • RED/GREEN/회귀 검증 결과를 누적 기록한다.

Phase 1: LazyInitializationException 재현

  • Task 1.1: 라이브 예약 서비스 통합 실패 테스트 작성
    • Create: src/test/kotlin/kr/co/vividnext/sodalive/live/reservation/LiveReservationServiceIntegrationTest.kt
    • RED: @SpringBootTest@Transactional(propagation = Propagation.NOT_SUPPORTED)를 사용해 테스트 자체 트랜잭션이 서비스 경계를 가리지 않게 한다.
    • RED: EmbeddedRedisInitializer를 명시적으로 적용하고 클래스 종료 후 Context를 정리한다.
    • RED: TransactionTemplate 안에서 예약자, 크리에이터, 가격이 0인 예약 라이브방을 저장하고 EntityManager.flush(), EntityManager.clear()를 실행한다.
    • RED: MockHttpServletRequestRequestContextHolder에 등록해 request-scoped LangContext를 사용할 수 있게 한 뒤 실제 Spring 빈 service.makeReservation(...)을 호출한다.
    • RED 코드의 핵심 검증은 다음과 같다.
val response = service.makeReservation(
    request = MakeLiveReservationRequest(
        roomId = fixture.roomId,
        container = "web",
        timezone = "Asia/Seoul"
    ),
    memberId = fixture.memberId
)

val reservation = transactionTemplate.execute {
    repository.findById(response.reservationId).orElseThrow()
}!!

assertEquals(fixture.roomId, reservation.room!!.id)
assertEquals(fixture.memberId, reservation.member!!.id)
  • 실패 확인: ./gradlew --no-daemon test --tests kr.co.vividnext.sodalive.live.reservation.LiveReservationServiceIntegrationTest
  • 기대 결과: production code 수정 전 reservation.room = room에서 LiveRoom.reservations를 초기화하려다 LazyInitializationException으로 실패한다.
  • GREEN: 이 task에서는 production code를 변경하지 않는다.
  • REFACTOR: fixture와 결과 검증용 타입은 테스트 파일 내부 private data class로 제한하고, request context는 @AfterEach에서 해제한다.
  • 검증 기록: 구현 중 누적한다.

Phase 2: 서비스 쓰기 트랜잭션 적용

  • Task 2.1: makeReservation() 트랜잭션 경계 추가
    • Modify: src/main/kotlin/kr/co/vividnext/sodalive/live/reservation/LiveReservationService.kt
    • Consumes: LiveReservationService.makeReservation(request: MakeLiveReservationRequest, memberId: Long): MakeLiveReservationResponse
    • Produces: 같은 메서드 시그니처와 응답을 유지하는 transactional 예약 생성 흐름
    • GREEN: 기존 import인 org.springframework.transaction.annotation.Transactional을 사용해 다음 한 줄만 추가한다.
@Transactional
fun makeReservation(request: MakeLiveReservationRequest, memberId: Long): MakeLiveReservationResponse {
  • 통과 확인: ./gradlew --no-daemon test --tests kr.co.vividnext.sodalive.live.reservation.LiveReservationServiceIntegrationTest
  • 기대 결과: BUILD SUCCESSFUL이며 저장된 예약의 방 ID와 회원 ID가 fixture와 일치한다.
  • REFACTOR: 불필요한 fetch 전략, setter, 응답 로직 변경이 없는지 git diff로 확인한다.
  • 검증 기록: 구현 중 누적한다.

Phase 3: 회귀 및 문서 검증

  • Task 3.1: 관련 테스트와 저장소 규칙 검증
    • Verify: ./gradlew --no-daemon test --tests kr.co.vividnext.sodalive.live.reservation.LiveReservationServiceIntegrationTest
    • Verify: ./gradlew --no-daemon ktlintCheck
    • Verify: ./gradlew --no-daemon tasks --all
    • Verify: git diff --check
    • 기대 결과: 모든 Gradle 명령은 BUILD SUCCESSFUL, git diff --check는 출력 없이 exit code 0이다.
    • RED/GREEN: Phase 1과 Phase 2의 실패 및 통과 결과를 다시 확인한다.
    • REFACTOR: 이번 요청과 무관한 코드 및 문서 변경이 없는지 확인한다.
    • 검증 기록: 구현 중 누적한다.

검증 기록

  • 계획 작성 시점에는 production code와 테스트를 변경하지 않았다.
  • 2026-07-15: 문서 변경 후 ./gradlew --no-daemon tasks --all 명령 유효성을 확인했다.
    • sandbox 실행은 Gradle wrapper lock 파일 접근 제한으로 실패했다.
    • 승인 실행은 BUILD SUCCESSFUL in 11s로 통과했다.
  • 2026-07-15: git diff --check가 출력 없이 통과해 문서 공백 오류가 없음을 확인했다.