락과 트랜잭션 경계 분리: 좌석 예약을 구현하며 배운 실무 패턴
락과 트랜잭션 경계 분리: 직접 겪어보며 정리한 실무 패턴 좌석 예약 기능을 만들면서 락과 트랜잭션의 경계를 어디에 둬야 하는지 한참 고민했다. 처음에는 @Transactional 메서드 안에서 분산 락을 잡고 푸는 식으로 짰다. 그런데 코드 리뷰를 받고 구조를 다시 뜯어보니, 이 방식이 왜 문제가 되는지 그리고 보통은 어떤 패턴을 쓰는지가 눈에 ...
락과 트랜잭션 경계 분리: 직접 겪어보며 정리한 실무 패턴 좌석 예약 기능을 만들면서 락과 트랜잭션의 경계를 어디에 둬야 하는지 한참 고민했다. 처음에는 @Transactional 메서드 안에서 분산 락을 잡고 푸는 식으로 짰다. 그런데 코드 리뷰를 받고 구조를 다시 뜯어보니, 이 방식이 왜 문제가 되는지 그리고 보통은 어떤 패턴을 쓰는지가 눈에 ...
Redis 캐시에 객체를 저장할 때 직렬화가 필요한 이유 Spring에서 @Cacheable로 Redis 캐시를 붙이다 보면 이런 예외를 만나기 마련이다. org.springframework.data.redis.serializer.SerializationException: Cannot serialize 처음 이 에러를 보면 로직이 잘못된 것처럼...
들어가며 개인 프로젝트에서 통합 테스트 환경을 꾸리면서 Testcontainers로 Docker 컨테이너를 띄우는 방식을 다시 시도했다. 예전에는 docker-compose.yml로 테스트 환경을 구성해 큰 탈 없이 써 왔다. 이번에는 좀 더 테스트 친화적인 구조를 만들고 싶었고, 그래서 Testcontainers를 골랐다. 결과부터 말하면 잘 ...
들어가며 좌석 예약 동시성 테스트를 구현하면서 unit / integration 테스트를 명확히 나누고 싶다는 생각이 들었다. 패키지만 갈라놓고 끝내는 대신, Gradle의 sourceSet으로 테스트 자체를 물리적으로 떼어내고, 통합 테스트에서는 Testcontainers(MySQL) 로 실제와 유사한 환경을 만들어보려 했다. 해보고 내린 결론은...
setTempHoldExpiresAt()을 둘러싼 선택 테스트를 돌리려고 엔티티에 setTempHoldExpiresAt()을 추가하는 건 권하지 않는다. 만료 시각을 조작할 일이 있다면 Repository 레벨에서 테스트 전용으로 열어두는 편이 낫다. 지금 구조에서는 setter가 없는 쪽이 오히려 설계상 옳은 상태다. 왜 setTempHoldEx...
들어가며 예약 시스템을 만들면서 대부분의 유스케이스는 이런 구조로 작성해왔다. MakeReservationUseCase ConfirmReservationUseCase DeductPointUseCase Port-In 인터페이스를 두고 Application Service가 이를 구현하는 식이다. 이 구조는 외부 요청(API, 메시지, ...
문제 상황 동시성 테스트를 작성하던 중 람다에서 컴파일 에러를 만났다. Local variable must be final or effectively final 처음에는 람다 문법이나 멀티 스레드 문제를 의심했지만, 실제 원인은 테스트 코드에서 자주 쓰는 엔티티 저장 패턴에 있었다. 원인이 되는 코드 문제의 핵심은 아래 코드다. Co...
문제 상황 Gradle Kotlin DSL로 커스텀 sourceSet을 추가하다 보면 IDE가 이런 제안을 내미는 순간이 온다. “create()에 import가 필요합니다” 그러면서 후보를 몇 개 친절하게 추천한다. kotlin.collections.create java.util.create 기타 전혀 관계없는 create...
좌석 예약 시스템의 동시성 이슈를 검증하려고 Pessimistic Lock 기반의 임시 배정(TEMP_HOLD) 로직을 구현하고, 이를 멀티 스레드 통합 테스트로 확인하던 중에 예외를 만났다. No EntityManager with actual transaction available for current thread cannot reliably pr...
들어가며 동시성 제어를 검증하려고 Hibernate의 낙관적 락(Optimistic Lock) 테스트를 작성했다. 의도는 단순했다. 여러 요청이 동시에 포인트를 차감하면 단 1건만 성공하고 나머지는 실패해야 한다. 그런데 테스트 결과는 계속 엇나갔다. expected: 1 actual : 3 왜 테스트가 계속 실패했고, 무엇을 바꾸자 정확히 성공...