Repository navigation
[volume-8] Redis 기반 주문 대기열 구현 - #349
Conversation
Claude Code 로컬 skill 파일을 원격에 올리지 않도록 ignore.
Working Principles 5개 원칙 명시, Git Workflow (브랜치/커밋 컨벤션) 추가, 영역별 상세 룰은 외부 skill (/coding-style, /design, /refactor, /tdd) 로 분리.
수동 getter/protected 기본 생성자 제거 후 @Getter/@NoArgsConstructor/@builder 로 통일. 사용처(ProductService, 관련 테스트 3종)도 빌더 호출로 정렬.
POST /api/v1/users — 로그인 ID 중복/비밀번호 정책/이메일 형식 검증 후 BCrypt 인코딩하여 저장. domain/application/interfaces 레이어와 단위/통합/E2E 테스트 포함.
X-Loopers-LoginId/Pw 헤더 기반 인증 — Spring Security 필터 체인에 커스텀 LoopersAuthenticationFilter 를 추가하고, 인증 실패는 LoopersAuthenticationEntryPoint 가 401 UNAUTHORIZED ApiResponse 로 변환. UserRepository 에 단건 조회 메서드(findById, findByLoginId) 도 함께 추가. findByLoginId 는 필터가 사용하고, findById 는 후속 내 정보 조회 API 에서 사용.
GET /api/v1/users/me — 인증된 사용자의 회원 정보를 반환. 이름은 Response DTO 변환 시점에 마지막 한 글자만 * 로 마스킹 (응답 필드: loginId/name/birthDate/email). UserService.getById, UserFacade.getMyInfo, UserV1Dto.MyInfoResponse(마스킹), 컨트롤러 GET /me 핸들러까지 단위(마스킹)/통합(Service/Facade)/E2E(정상 + 인증 실패 3종) 테스트와 함께 추가.
- anyRequest() 를 denyAll 로 바꾸고 허용 경로(POST /api/v1/users, Swagger, /error) 만 명시적 permitAll - /api/** 를 일괄 authenticated 로 묶어 인증 누락 방지 - PasswordEncoder 빈을 SecurityConfig 로 통합 (PasswordEncoderConfig 제거) - 변경된 인증 정책에 맞춰 ExampleV1ApiE2ETest 에 헤더 인증 추가
- PUT /api/v1/users/me/password — 인증된 사용자 본인의 비밀번호 변경 - 현재 비밀번호 검증 실패 시 401, 정책 위반/동일 비밀번호 시 400 - 정책 검증은 기존 UserPasswordPolicy 재사용 (8~16자, 생년월일 미포함) - 도메인/서비스/E2E 레이어 테스트 추가
- UserV1ApiSpec 인터페이스 신규: @tag, 엔드포인트별 @operation(summary/description), 인증 헤더 @parameter, @ApiResponses 응답 코드 매핑 - 각 에러 코드에 @content + @ExampleObject 로 실제 응답 바디(ApiResponse.fail) 예시 노출 - UserV1Controller 가 UserV1ApiSpec 을 implements, 각 메서드에 @OverRide 추가
원본 com.querydsl 가 사실상 미관리 상태(마지막 릴리즈 2021)라 record 기반 @embeddable 등 Java 신기능 APT 처리가 막힘. io.github.openfeign.querydsl 6.10.1 (소스 호환, jakarta 네이티브) 로 교체하여 record VO 도입 기반을 마련.
LoginId/Email/EncodedPassword/PlainPassword/UserName/BirthDate 를 도메인 VO 로 분리해 invariant 를 한 군데로 모으고, JPA @embeddable 로 박아 UserModel 이 VO 타입을 그대로 보존하도록 한다.
비밀번호 정책 검증은 PlainPassword VO 의 record 생성자에서 처리하므로 별도 정책 클래스를 둘 필요가 없다.
application 측 SignUpCommand 는 controller 가 facade 로 넘기는 raw 입력, 도메인 측 SignUpUserCommand 는 VO 로 묶인 도메인 입력으로 layer 책임을 분리.
LoginId 파싱과 비밀번호 매칭 책임을 UserAuthenticator 로 빼고, LoopersAuthenticationFilter 는 헤더 추출과 SecurityContext 설정만 담당한다.
primitive 필드 + 자체 validation 을 걷어내고 도메인 VO 를 @Embedded 로 박는다. 회원 가입 사건이 빌더 체인에 묻히지 않도록 UserModel.signUp 정적 팩토리를 노출. JpaRepository.findByLoginId 시그니처도 LoginId 타입으로 맞춘다.
signUp 은 SignUpUserCommand 를 받아 EncodedPassword 까지 만든 뒤 UserModel.signUp 으로 위임하고, changePassword 는 현재 비번 매칭과 raw 동일 여부 검증을 직접 수행한 뒤 PlainPassword 로 정책만 검사. getUser 도 LoginId 가 아닌 PK 기반으로 표준화한다.
Facade 는 raw 입력을 VO 로 감싸 SignUpUserCommand 를 만들어 도메인에 넘기고, UserInfo 는 UserModel 의 VO 를 unwrap 해 외부에 노출.
controller 는 UserInfo 를 받아 V1Dto.UserResponse/MyInfoResponse 로만 매핑하고, SignUpRequest.toCommand 와 ChangePasswordRequest 시그니처를 facade 의 새 API 와 맞춰 ApiSpec 도 가독성 위주로 단순화.
|
Important Review skippedToo many files! This PR contains 503 files, which is 353 over the limit of 150. To get a review, narrow the scope: Upgrade to a paid plan to raise the limit. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (503)
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🧭 Context & Decision
문제 정의
선택지와 결정
Rate limiter로 초과분을 거절하는 방식은 순번을 줄 수 없고 서버가 느려져도 계속 밀어넣어서, 줄을 세우는 대기열 방식을 선택했습니다.
flowchart LR U[유저] -->|① 줄서기| Q[대기열<br/>Redis Sorted Set] Q -->|② 스케줄러가 순차 입장| T[입장 토큰] U -->|③ 토큰으로 주문| B[벌크헤드<br/>동시 실행 제한] --> G[게이트<br/>토큰 확인] --> O[주문 API]설계하며 고민한 포인트
수치는 대부분 감으로 잡았다가 측정으로 다시 정했습니다. 핵심 고민은 아래와 같습니다.
flowchart LR R([대기열 설계 고민]) R --> A[입장 속도] R --> B[동시 주문 상한] R --> C[토큰 쏠림 herd] R --> D[Redis 장애] R --> E[순번 조회 부하] A --> A1[공식 → 실측으로 재산정<br/>병목이 DB 아닌 인증] B --> B1[어림값이 과해 재보고 낮춤] C --> C1[발급 속도가 도착 속도 상한<br/>구조적 예방, 완화 미도입] D --> D1[거절과 판정불가 구분<br/>fail-open 후 재시도 안내] E --> E1[순번 비례 폴링<br/>대기 토큰으로 경량 식별] classDef cat fill:#EEEDFE,stroke:#534AB7,color:#26215C classDef leaf fill:#E1F5EE,stroke:#0F6E56,color:#04342C classDef root fill:#F1EFE8,stroke:#5F5E5A,color:#2C2C2A class R root class A,B,C,D,E cat class A1,B1,C1,D1,E1 leaf🤔 고민한 점 / 막혔던 부분
🙋 기타
주문 이후 이벤트 흐름은 R7 구조를 그대로 재사용했고, 대기열은 주문 앞단 관문만 새로 얹었습니다.