Skip to content

fix: [ALT-307] 업장 신청 취소·승인 경합 직렬화 - #118

Open
ysw789 wants to merge 12 commits into
fix/alt-297-qa-followupfrom
fix/alt-307
Open

ysw789 wants to merge 12 commits into
fix/alt-297-qa-followupfrom
fix/alt-307

Conversation

@ysw789

@ysw789 ysw789 commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

요약

업장 등록 신청을 취소하는 요청과 관리자가 승인·반려하는 요청이 동시에 들어오면, 두 요청이 모두 PENDING을 읽고 각자 저장해서 "취소됐는데 업장이 만들어진" 상태가 남을 수 있었습니다. 또 이미 취소된 신청을 관리자가 승인하면 ACTIVATED로 되살아나 업장이 만들어졌고, 반려하면 REVOKED로 바뀌었습니다.

이제 취소·승인·반려 모두 신청 행을 잠근 뒤 최신 상태로 검사하므로 한 번에 하나씩 처리됩니다. 취소된 신청은 승인도 반려도 할 수 없고 409를 돌려줍니다. API 경로와 요청·응답 필드는 바뀌지 않았고, 새 ErrorCode와 DB 마이그레이션은 없습니다.

변경 내용

PATCH /app/workspace-requests/{workspaceRequestId}/cancel, PATCH /manager/workspace-requests/{workspaceRequestId}/cancel (CancelWorkspaceRequest)

  • 신청을 조회할 때 신청 ID와 요청한 사용자 ID를 함께 조건으로 걸고 행을 잠급니다(findByIdAndUserIdWithPessimisticLock). 전에는 findByIdWithUser로 조회한 뒤 UseCase에서 소유자를 비교했습니다.
  • 남의 신청이면 행을 잠그지 않고 바로 404를 돌려줍니다. 그래서 다른 사용자가 취소 요청을 보내 남의 신청 처리를 늦출 수 없습니다. 응답은 없는 신청과 같은 404 B019 등록 신청한 업장을 찾을 수 없습니다.입니다.
  • 같은 신청을 다른 요청이 처리 중이면 최대 5초 기다린 뒤 최신 상태로 다시 검사합니다. PENDING이 아니면 기존처럼 409 B020 취소할 수 없는 상태입니다.를 돌려줍니다.
  • 5초 안에 잠금을 얻지 못하면 429 E001 요청이 너무 많습니다. 잠시 후 다시 시도해주세요.를 돌려줍니다(신규).

PATCH /admin/workspace-requests/{workspaceRequestId}/status (UpdateWorkspaceRequestStatus)

  • 신청을 findByIdWithPessimisticLock으로 잠가서 조회합니다. 잠금 대기 방식은 취소 API와 같습니다.
  • 취소된 신청은 승인·반려할 수 없습니다(WorkspaceRequest#approve, #reject에 CANCELLED 검사 추가).
    • 승인: 409 B020 취소된 요청은 승인할 수 없습니다.
    • 반려: 409 B020 취소된 요청은 반려할 수 없습니다.
  • 상태별로 허용하는 동작은 아래와 같습니다. 이번에 바뀐 것은 CANCELLED 줄입니다.
현재 상태 취소 승인 반려
PENDING 가능 가능 가능
REVOKED 409 가능 (재검토 승인, 기존 정책 유지) 200, 상태 변화 없음
ACTIVATED 409 409 이미 승인된 요청입니다. 409 이미 승인된 요청입니다.
CANCELLED 409 409 (신규) 409 (신규)
  • 승인할 때는 신청 행을 잠근 다음 신청자의 User 행도 잠그고(UserQueryRepository#lockByIdWithPessimisticLock), 그 뒤에 ManagerUser를 찾거나 만듭니다. manager_users.user_id에 유니크 제약이 없어서, 같은 사용자의 서로 다른 신청 두 건을 동시에 승인하면 ManagerUser가 두 개 생길 수 있었기 때문입니다. 이제 두 번째 승인은 첫 번째가 끝날 때까지 기다렸다가 이미 만들어진 ManagerUser를 다시 씁니다. 반려는 User 행을 잠그지 않습니다.
  • 잠금 시간 초과 시 429 E001(신규)입니다. 그 밖의 응답(400 B001 ACTIVATED 또는 REVOKED 만 설정 가능합니다., 404 B019 해당 업장 등록 신청을 찾을 수 없습니다.)은 기존과 같습니다.

잠금

  • WorkspaceRequestQueryRepository에 행 잠금 조회 두 개를 추가했습니다. QueryDSL PESSIMISTIC_WRITE를 씁니다.
    • findByIdWithPessimisticLock(workspaceRequestId) — 관리자 승인·반려
    • findByIdAndUserIdWithPessimisticLock(workspaceRequestId, userId) — 신청 취소
  • 두 조회 모두 잠금 전에 SET LOCAL lock_timeout = '5s'를 실행합니다(LOCK_TIMEOUT_STATEMENT 상수). SET LOCAL은 트랜잭션 전체에 적용되므로, 승인 중 이어지는 User 행 잠금 대기도 5초로 제한됩니다.
  • 5초를 넘기면 PostgreSQL이 잠금 대기를 끊고, 기존 GlobalExceptionHandler가 PessimisticLockingFailureException을 429 E001로 바꿉니다. 트랜잭션은 롤백되므로 아무것도 바뀌지 않습니다.
  • 새 설정, 분산 락, 버전 컬럼 같은 별도 잠금 장치는 추가하지 않았습니다. 로그 정책, 연락처 정규화(PR fix: [alt-297] 업장 신청·승인 연락처 정규화 및 기존 신청 호환 #108), 신분증 파일 삭제, 대표 이미지 이전 처리도 그대로입니다.

Swagger

  • AdminWorkspaceRequestControllerSpec 상태 변경: 409(취소된 요청의 승인·반려, 이미 승인된 요청의 재처리)와 429 설명을 추가했습니다. 관리자 신청 상세 조회 GET /admin/workspace-requests/{workspaceRequestId}는 200/404 그대로입니다.
  • UserWorkspaceRequestControllerSpec, ManagerWorkspaceRequestControllerSpec 신청 취소: 429 설명을 추가했습니다.

프론트 영향

  • 응답 필드와 경로는 바뀌지 않았습니다. 기존 앱은 그대로 동작합니다.
  • 관리자 화면: 취소된 신청의 승인·반려 버튼을 누르면 이제 409 B020과 위 문구가 옵니다. 버튼을 숨기거나, 응답의 message를 그대로 보여 주면 됩니다.
  • 알바 앱·사장 앱 신청 취소, 관리자 승인·반려: 같은 신청을 동시에 처리하다 5초를 넘기면 429 E001이 올 수 있습니다. 거의 일어나지 않지만, 오면 "잠시 후 다시 시도해 주세요"로 안내하면 됩니다.
  • 취소와 승인이 동시에 들어오면 먼저 끝난 쪽이 반영되고 나중 쪽은 409를 받습니다. 409를 받은 화면은 목록·상세를 다시 불러와 최신 상태를 보여 주면 됩니다.

테스트

  • WorkspaceRequestTests(엔티티) — CANCELLED 승인·반려 시 409와 상태 유지, REVOKED 재반려 시 상태 유지, 기존 취소·승인 상태 검사
  • CancelWorkspaceRequestTests — 소유자 조건을 포함한 잠금 조회 사용, 남의 신청이면 잠금 조회 결과가 비어 NOT_FOUND이고 취소하지 않음
  • UpdateWorkspaceRequestStatusTests — 잠금 조회 사용, 승인은 User 행을 잠근 뒤에 ManagerUser를 조회·생성(호출 순서 검증), 반려는 User 행을 잠그지 않음
  • 단위 테스트만 둔다는 팀 규칙에 따라, 리뷰 반영 중 PostgreSQL 경합 테스트(Testcontainers, WorkspaceRequestConcurrencyTests)와 base 브랜치에 있던 H2 승인 통합 테스트(WorkspaceRequestApprovalIntegrationTests, DataJpaTest)를 삭제했습니다.
  • 최종 head 4d675ee6에서 ./gradlew test --rerun 전체 680건 통과(실패·오류·skip 0).

리뷰 반영 전 기준(head 7612c0a)으로는 실제 PostgreSQL과 로컬 서버에서 아래를 확인했습니다.

  • PostgreSQL 테스트로 "취소 완료 후 승인", "취소 먼저", "승인 먼저" 세 경우에 최종 상태가 뒤집히는 것을 먼저 재현했고, 수정 후 통과했습니다.
  • 실제 HTTP로 같은 신청을 동시에 처리했을 때, 취소가 먼저면 취소 200·승인 409이고 업장·ManagerUser가 생기지 않았습니다. 승인이 먼저면 승인 200·취소 409이고 업장·ManagerUser가 하나씩 생겼습니다.
  • 잠금을 8초 잡아 둔 상태에서 알바 앱 취소·사장 앱 취소·관리자 승인이 각각 5초 뒤 429 E001을 받았고, DB는 바뀌지 않았습니다.

이후 바뀐 세 가지(취소된 신청의 반려 차단, 승인 시 User 행 잠금, 취소 잠금 조회의 소유자 조건)는 단위 테스트로만 확인했습니다. 실제 DB 동시 요청과 HTTP 측정은 다시 하지 않았습니다.

남긴 것

  • manager_users.user_id 유니크 제약은 추가하지 않았습니다. 이번에는 승인 경로에서 User 행 잠금으로만 중복 생성을 막습니다. 제약을 넣으려면 기존 중복 데이터 확인과 마이그레이션이 필요해 범위에서 뺐습니다.
  • 잠금 시간 초과에 기존 TOO_MANY_REQUESTS(429 E001)를 재사용했습니다. 문구가 "요청이 너무 많습니다"라 상황과 딱 맞지는 않지만, 새 ErrorCode를 만들지 않는 팀 규칙을 따랐습니다.
  • QA B-06의 처음 실패 요청(배포 버전, 요청·응답)은 기록이 없어 재현하지 못했습니다. 취소 후 목록·상세의 캐시 갱신과 버튼 상태는 FE·QA 확인이 필요합니다.
  • 운영 S3에 영향을 줄 수 있어 실제 신분증·대표 이미지 첨부로 취소·승인 시 파일 삭제를 검증하지 않았습니다. 첨부 없는 신청으로만 확인했습니다.

병합 순서

관련 문서

@coderabbitai

coderabbitai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review: 2
  • main
  • dev

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@hodoon hodoon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리뷰했습니다. 인라인 코멘트 확인 부탁드립니다.

@ysw789 ysw789 self-assigned this Oct 21, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants