Skip to content

[feat] 매칭 요청 api V3 마이그레이션#240

Merged
hyeeum merged 13 commits intodevfrom
feat/#239-create-request-v3-migration
Mar 22, 2026
Merged

[feat] 매칭 요청 api V3 마이그레이션#240
hyeeum merged 13 commits intodevfrom
feat/#239-create-request-v3-migration

Conversation

@hyeeum
Copy link
Collaborator

@hyeeum hyeeum commented Mar 17, 2026

📌 이슈 번호


closed #239


✅ 어떻게 이슈를 해결했나요?


  • 로직 흐름: 매칭 요청 api 호출 -> groupMember 테이블에 요청자 row 생성 > 매칭생성자의 memberStatus를 "새요청"으로 업데이트 > 매칭생성자에 "새요청" 알림 생성
  • 검증: 내가 생성한 매칭인지 > 동일한 매칭에 요청을 보낸 전적이 있는지 > 매칭에 현재 승인대기중인 요청자가 있는지 > 이미 완료된 매칭인지
  • 검증에 사용되는 data 는 queryDSL 을 활용하여 한 번에 조회(isDuplicatedRequest,hasPendingRequest)

❤️ To 다진 / To 헤음


  • 뚱땅뚱땅... 비록 내 코드들은 날아갔지만 로직이 단순해져서 좋아요 ㅎㅎㅎ

Summary by CodeRabbit

  • 새로운 기능

    • 사용자가 매칭에 참여 요청을 보낼 수 있는 POST 엔드포인트 및 요청 생성 흐름 추가
    • 요청 검증 결과에 중복 요청 여부 및 대기 요청 여부 반환
    • 그룹/매칭 유효성 검사용 데이터 조회 기능 추가
  • 예외 처리 / 개선

    • 요청 불가 사유 추가: 이미 종료된 매칭, 자신의 매칭 요청 금지, 중복 요청 감지 및 관련 오류 응답
  • 데이터 무결성

    • 동일 사용자-매칭 중복 저장 방지(중복 제약 및 중복요청 처리)
  • 알림

    • 요청 생성 시 대기 상태 반영 및 알림 전송 흐름 추가

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

매칭 요청 API(v3) 추가: POST 엔드포인트 및 서비스/밸리데이션/레포지토리/DTO/에러 코드 추가, 그룹멤버 생성 및 리더 알람 생성 흐름 도입, 알람 타입에 PENDING 추가.

Changes

Cohort / File(s) Summary
Alarm Type
src/main/java/at/mateball/domain/alarm/common/AlarmType.java
PENDING enum 상수 추가.
Controller & Service API
src/main/java/at/mateball/domain/group/api/controller/GroupV3Controller.java, src/main/java/at/mateball/domain/group/core/service/GroupV3Service.java
POST /match-request/{matchId} 핸들러 추가 및 GroupV3Service.createRequest(Long, Long) 위임 메서드 추가.
Service & Validation
src/main/java/at/mateball/domain/groupRequest/GroupRequestService.java, src/main/java/at/mateball/domain/groupRequest/GroupRequestValidator.java
새 서비스 및 검증 컴포넌트 추가: 락 조회 → 검증 → 멤버 생성 → 리더 상태 변경 → 알람 생성 흐름과 DataIntegrityViolation 예외 변환 처리.
DTOs for Validation
src/main/java/at/mateball/domain/group/api/dto/RequestValidationRes.java, src/main/java/at/mateball/domain/group/api/dto/GroupValidationRes.java
요청/그룹 검증 결과용 record 추가(정적 팩토리 포함).
Validation Repository Contract & Impl
src/main/java/at/mateball/domain/group/infrastructure/repository/GroupV3RepositoryCustom.java, .../GroupV3RepositoryImpl.java
getValidation(Long userId, Long groupId) 선언·구현 추가(QueryDSL로 중복/보류 여부 계산, RequestValidationRes 반환).
Group Repository Custom & Impl
src/main/java/at/mateball/domain/group/core/repository/querydsl/GroupRepositoryCustom.java, .../GroupRepositoryImpl.java
findValidateGroupData(Long) 선언·구현 추가(QueryDSL로 leaderId, gameDate, status 조회, GroupValidationRes 반환).
GroupMember 엔티티 및 레포지토리
src/main/java/at/mateball/domain/groupmember/core/GroupMember.java, .../GroupMemberRepositoryCustom.java, .../GroupMemberRepositoryImpl.java
(user_id, group_id) 유니크 제약 추가, isLeader 초기화 생성자·정적 팩토리 추가, createGroupMemberV3 구현 추가(엔티티 생성·persist).
Repository: Pessimistic Lock
src/main/java/at/mateball/domain/group/core/repository/GroupRepository.java
findGroupWithLock(Long) 메서드 추가(@Lock(LockModeType.PESSIMISTIC_WRITE)로 그룹·리더·gameInformation을 fetch).
Business Error Codes
src/main/java/at/mateball/exception/code/BusinessErrorCode.java
새 에러 코드 3개 추가: ALREADY_FINISHED_MATCH, CANNOT_REQUEST_OWN_MATCH, DUPLICATED_MATCH_REQUEST.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Controller as GroupV3Controller
    participant Service as GroupRequestService
    participant RepoG as GroupRepository
    participant RepoV as GroupV3Repository
    participant RepoM as GroupMemberRepository
    participant Alarm as AlarmService
    participant DB as Database

    Client->>Controller: POST /v3/users/match-request/{matchId}
    Controller->>Service: createRequest(userId, matchId)
    Service->>RepoG: findGroupWithLock(matchId)
    RepoG-->>Service: GroupValidationRes
    Service->>RepoV: getValidation(userId, matchId)
    RepoV->>DB: exists 쿼리 (중복/보류)
    DB-->>RepoV: RequestValidationRes
    RepoV-->>Service: RequestValidationRes
    Service->>Service: validateCreateRequest(...)
    alt validation ok
      Service->>RepoM: createGroupMemberV3(userId, matchId)
      RepoM->>DB: INSERT group_member
      Service->>DB: UPDATE leader member 상태 -> NEW_REQUEST
      Service->>Alarm: createAlarm(leaderId, AlarmType.NEW_REQUEST or PENDING)
      Alarm->>DB: INSERT alarm
      Service-->>Controller: success response
      Controller-->>Client: 200 OK
    else validation fail
      Service-->>Controller: throw BusinessException
      Controller-->>Client: Error response
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • YangDaJin0104
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 V3 마이그레이션이라는 주요 변경사항을 명확하게 설명하며, PR의 핵심 목표와 완벽하게 관련이 있습니다.
Linked Issues check ✅ Passed PR은 #239의 모든 주요 요구사항을 충족합니다: V3 마이그레이션 구현, 매칭 생성자에게만 새요청 표시, 예외 케이스 처리(자신의 매칭 요청 불가, 중복 요청, 대기 중인 요청 감지, 종료된 매칭).
Out of Scope Changes check ✅ Passed 모든 변경사항은 V3 마이그레이션 범위 내입니다: 열거형 상수 추가, 엔드포인트 구현, DTO 및 검증 로직, 저장소 메서드, 비즈니스 로직 서비스 추가.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#239-create-request-v3-migration

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/main/java/at/mateball/domain/group/api/dto/RequestValidationRes.java (1)

2-7: 이 타입은 api.dto보다 내부 계층 패키지에 두는 편이 좋겠습니다.

컨트롤러 응답이 아니라 repository projection/서비스 내부 검증용이라서, 현재 위치는 infrastructure가 API 패키지에 의존하게 만듭니다. infrastructure.dtoquery 쪽으로 옮기고 Res suffix도 내부 용도에 맞게 정리하는 편이 경계가 더 명확합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/at/mateball/domain/group/api/dto/RequestValidationRes.java`
around lines 2 - 7, 이 타입 RequestValidationRes(isDuplicatedRequest,
hasPendingRequest)은 API 응답용이 아니라 내부 검증/프로젝션용이므로 패키지를 API 계층에서 분리하세요: 파일을
at.mateball.domain.group.api.dto에서 내부 계층 패키지(예:
at.mateball.domain.group.infrastructure.dto 또는 at.mateball.domain.group.query)로
이동하고 타입명을 Res 접미사 대신 RequestValidationResult(또는 RequestValidationInfo 등 내부 용도에
맞는 명칭)로 변경한 뒤, 관련된 import/참조(Repository, Service, Query projection 등)를 모두 수정하여
API 계층이 내부 패키지에 의존하지 않도록 정리하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/at/mateball/domain/group/api/controller/GroupV3Controller.java`:
- Around line 62-72: The createRequest method in GroupV3Controller is returning
ResponseEntity.ok(...) which yields HTTP 200 despite using SuccessCode.CREATED;
change the response to return a 201 status by replacing ResponseEntity.ok(...)
with
ResponseEntity.status(HttpStatus.CREATED).body(MateballResponse.successWithNoData(SuccessCode.CREATED))
(and add the HttpStatus import) so the HTTP status matches SuccessCode.CREATED.

In `@src/main/java/at/mateball/domain/group/core/service/GroupV3Service.java`:
- Around line 217-227: The createRequest method currently checks invariants
(validateRequest) before inserting, which allows a race where two concurrent
requests both pass validation and create duplicate pending members; fix by
performing the validation-and-insert atomically inside a transaction with a
proper DB-level lock or constraint: load the target Group with a pessimistic
lock (e.g., findByIdForUpdate / repository method that does SELECT ... FOR
UPDATE) or add a unique DB constraint on (group_id, user_id, status=pending) and
catch the DB exception, then perform validateRequest and
groupMemberRepository.createGroupMember within the same transactional boundary
(same method), and handle duplicate-key/lock failures to return the appropriate
BusinessException; update usages of groupRepository.findById, validateRequest,
groupMemberRepository.createGroupMember, and
groupMemberRepository.updateMemberStatus accordingly.
- Around line 230-250: In validateRequest, move validations that can be decided
from the provided group before calling groupV3RepositoryCustom.getValidation:
first call validate(group.getGameInformation().getGameDate()), then check if
group.getLeader().getId().equals(userId) and throw
BusinessException(CANNOT_REQUEST_OWN_MATCH), then check if group.getStatus() ==
GroupStatus.COMPLETED.getValue() and throw
BusinessException(ALREADY_FINISHED_MATCH); only after these group-only checks
call groupV3RepositoryCustom.getValidation(userId, group.getId()) and then
handle RequestValidationRes.isDuplicatedRequest()/hasPendingRequest() as before.

---

Nitpick comments:
In `@src/main/java/at/mateball/domain/group/api/dto/RequestValidationRes.java`:
- Around line 2-7: 이 타입 RequestValidationRes(isDuplicatedRequest,
hasPendingRequest)은 API 응답용이 아니라 내부 검증/프로젝션용이므로 패키지를 API 계층에서 분리하세요: 파일을
at.mateball.domain.group.api.dto에서 내부 계층 패키지(예:
at.mateball.domain.group.infrastructure.dto 또는 at.mateball.domain.group.query)로
이동하고 타입명을 Res 접미사 대신 RequestValidationResult(또는 RequestValidationInfo 등 내부 용도에
맞는 명칭)로 변경한 뒤, 관련된 import/참조(Repository, Service, Query projection 등)를 모두 수정하여
API 계층이 내부 패키지에 의존하지 않도록 정리하세요.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 873e778f-96e5-4e88-a041-131bbae1bd8b

📥 Commits

Reviewing files that changed from the base of the PR and between 88ebbe6 and 6d296eb.

📒 Files selected for processing (7)
  • src/main/java/at/mateball/domain/alarm/common/AlarmType.java
  • src/main/java/at/mateball/domain/group/api/controller/GroupV3Controller.java
  • src/main/java/at/mateball/domain/group/api/dto/RequestValidationRes.java
  • src/main/java/at/mateball/domain/group/core/service/GroupV3Service.java
  • src/main/java/at/mateball/domain/group/infrastructure/repository/GroupV3RepositoryCustom.java
  • src/main/java/at/mateball/domain/group/infrastructure/repository/GroupV3RepositoryImpl.java
  • src/main/java/at/mateball/exception/code/BusinessErrorCode.java

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/main/java/at/mateball/domain/group/core/service/GroupV3Service.java (1)

216-225: ⚠️ Potential issue | 🔴 Critical

동시 요청에서 중복 requester가 생성될 수 있습니다.

Line 222~225는 “검증 후 삽입”이 분리되어 있어, 동시 요청 두 건이 동시에 통과하면 둘 다 생성될 수 있습니다. 이 제약은 서비스 로직만으로는 부족하고 DB 레벨 원자성(락/유니크 제약)으로 보장해야 합니다.

🔧 제안 수정
 `@Transactional`
 public void createRequest(Long userId, Long groupId) {
-    Group group = groupRepository.findById(groupId)
+    Group group = groupRepository.findByIdForUpdate(groupId)
             .orElseThrow(() -> new BusinessException(BusinessErrorCode.GROUP_NOT_FOUND));

     validateRequest(userId, group);

-    groupMemberRepository.createGroupMember(userId, groupId);
+    try {
+        groupMemberRepository.createGroupMember(userId, groupId);
+    } catch (DataIntegrityViolationException e) {
+        throw new BusinessException(BusinessErrorCode.DUPLICATED_MATCH_REQUEST);
+    }
     groupMemberRepository.updateMemberStatus(group.getLeader().getId(), group.getId(), GroupMemberStatus.NEW_REQUEST.getValue());
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/at/mateball/domain/group/core/service/GroupV3Service.java`
around lines 216 - 225, createRequest currently does "validateRequest" then
"groupMemberRepository.createGroupMember", which allows duplicate requesters
under concurrent calls; enforce atomicity by adding a DB-level constraint
(unique on group_id + user_id in the group member table) and make createRequest
resilient to constraint violations: keep the validateRequest call but ensure the
persistence layer or createGroupMember handles unique constraint errors (e.g.,
catch DataIntegrityViolationException / ConstraintViolationException) and
translate them to BusinessException(GROUP_MEMBER_ALREADY_EXISTS) or similar;
alternatively acquire a row lock on the Group (SELECT ... FOR UPDATE) before
calling groupMemberRepository.createGroupMember to serialize concurrent requests
— update createRequest and groupMemberRepository.createGroupMember to implement
one of these DB-level solutions and handle the resulting exception/path
accordingly (also adjust groupMemberRepository.updateMemberStatus call flow to
only run after successful insert).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/main/java/at/mateball/domain/group/core/service/GroupV3Service.java`:
- Around line 216-225: createRequest currently does "validateRequest" then
"groupMemberRepository.createGroupMember", which allows duplicate requesters
under concurrent calls; enforce atomicity by adding a DB-level constraint
(unique on group_id + user_id in the group member table) and make createRequest
resilient to constraint violations: keep the validateRequest call but ensure the
persistence layer or createGroupMember handles unique constraint errors (e.g.,
catch DataIntegrityViolationException / ConstraintViolationException) and
translate them to BusinessException(GROUP_MEMBER_ALREADY_EXISTS) or similar;
alternatively acquire a row lock on the Group (SELECT ... FOR UPDATE) before
calling groupMemberRepository.createGroupMember to serialize concurrent requests
— update createRequest and groupMemberRepository.createGroupMember to implement
one of these DB-level solutions and handle the resulting exception/path
accordingly (also adjust groupMemberRepository.updateMemberStatus call flow to
only run after successful insert).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a48dec59-c507-4fb9-aa5b-5a1497b89a75

📥 Commits

Reviewing files that changed from the base of the PR and between 6d296eb and a0333ed.

📒 Files selected for processing (1)
  • src/main/java/at/mateball/domain/group/core/service/GroupV3Service.java

Copy link
Contributor

@YangDaJin0104 YangDaJin0104 left a comment

Choose a reason for hiding this comment

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

확실히 의도와 코드가 간결해 져서 보기 편하내요,,,ㅎ

} No newline at end of file

@Transactional
public void createRequest(Long userId, Long groupId) {
Copy link
Contributor

Choose a reason for hiding this comment

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

isDuplicatedRequest == false -> hasPendingRequest == false를 본 뒤에 createGroupMemeber() 실행이라 여기도 동시성 이슈가 생길수있으니 이젠 락을 걸어야할 차례가 왔소

alarmService.createAlarm(group.getLeader().getId(), AlarmType.NEW_REQUEST, group.getId());
}

private void validateRequest(Long userId, Group group) {
Copy link
Contributor

Choose a reason for hiding this comment

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

이 부분이 조회 시점 기준 검증이라 원자성이 부족해서 아래 insert/update까지 한 트랜잭션 안에서 불변식을 보장하지 않으니, DB 수준에서 보호해줄 필요가있을거같습니당

groupMemberRepository.createGroupMember(userId, groupId);
groupMemberRepository.updateMemberStatus(group.getLeader().getId(), group.getId(), GroupMemberStatus.NEW_REQUEST.getValue());

alarmService.createAlarm(group.getLeader().getId(), AlarmType.NEW_REQUEST, group.getId());
Copy link
Contributor

Choose a reason for hiding this comment

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

알림 생성 실패 시 매칭 요청 생성도 록백이 되는건지? 아니면 핵심은 요청 생성이고, 알림은 부가 기능인지. 만약 이렇게 된다면 알림 저장 실패도 전체 록백으로 될 수 있는데 괜찮은지 확인 부탁드립니둉

Copy link
Contributor

Choose a reason for hiding this comment

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

createRequest()는 검증, 요청 row 생성, 리더 상태 변경, 알림 생성까지 한 번에 오케스트레이션하고 있어서 흐름은 잘 보입니다. 다만 이후 승인/거절/실패 같은 상태 전이가 더 늘어나면 서비스가 비대해질 수 있어소 장기적으로는 request 도메인 정책을 조금 더 캡슐화해도 좋을 거 같습니당

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

매칭 생성/요청/수락/거절 api 만들고 매칭 관련 상위 서비스를 만들어도 좋을 것 같네요.///// -> 이 뜻으로 말씀한거 맞지요??? 말이 고급져서 약간 헷갈려요

Copy link
Contributor

Choose a reason for hiding this comment

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

넵 맞습니다아
지금 createRequest()는 검증, 요청 row 생성, 리더 상태 변경, 알림 생성까지 한 메서드에서 오케스트레이션하고 있어서 현재는 흐름이 잘 보이고 좋습니다만,,,
이후에 수락/거절/실패 처리까지 비슷한 패턴으로 계속 붙기 시작하면 GroupV3Service가 점점 커질 수 있어서. 그 시점에 매칭 요청/수락/거절 관련 흐름만 따로 묶는 상위 서비스나 도메인 정책 클래스로 분리해도 좋겠다는 의미였슴다

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

확인이요~! 이 리뷰를 반영해서 GroupRequestService 를 새롭게 추가했습니다, 일단 #239 에서 먼저 반영하고, 코드 합친다음에 이사시킬게요!!

Copy link
Contributor

Choose a reason for hiding this comment

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

넵 확인했습니당!

@hyeeum hyeeum force-pushed the feat/#239-create-request-v3-migration branch from a0333ed to 2e304cb Compare March 19, 2026 18:41
@hyeeum hyeeum requested a review from YangDaJin0104 March 19, 2026 19:29
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/at/mateball/domain/groupmember/core/GroupMember.java (1)

50-69: ⚠️ Potential issue | 🟡 Minor

4-파라미터 생성자에서 isLeader 필드가 설정되지 않아 데이터 불일치가 발생합니다.

기존 4-파라미터 생성자(50-56줄)는 isLeader를 설정하지 않아 null로 남으며, 이를 통해 생성된 리더(GroupExecutor, 39줄)의 isLeader 값이 null이 됩니다. 반면 새로운 5-파라미터 생성자(58-65줄)와 member() 팩토리 메서드(67-69줄)는 isLeader를 명시적으로 설정합니다.

GroupV3RepositoryImpl의 쿼리에서 .isLeader.isTrue().isLeader.isFalse()로 필터링하므로, null 값을 가진 레코드와 불일치할 수 있습니다.

4-파라미터 생성자 사용처를 모두 5-파라미터 생성자로 변경하거나, 4-파라미터 생성자를 제거하여 일관성을 유지하기 바랍니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/at/mateball/domain/groupmember/core/GroupMember.java` around
lines 50 - 69, The 4-parameter GroupMember constructor leaves isLeader null
causing mismatches with queries in GroupV3RepositoryImpl; update the 4-arg
constructor in class GroupMember (the one taking User, Group, Boolean
isParticipant, int status) to explicitly set this.isLeader = false (or remove
the 4-arg ctor and replace its call sites to use the 5-arg constructor),
ensuring all places that create GroupMember (including where GroupExecutor is
constructed and the member() factory) produce non-null boolean values so
.isLeader.isTrue()/isFalse() filters work correctly.
🧹 Nitpick comments (3)
src/main/java/at/mateball/domain/group/core/repository/querydsl/GroupRepositoryImpl.java (1)

294-307: @Override 어노테이션이 누락되었습니다.

이 클래스의 다른 메서드들과 일관성을 위해 @Override 어노테이션을 추가해주세요.

🔧 수정 제안
+    `@Override`
     public GroupValidationRes findValidateGroupData(Long groupId) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/at/mateball/domain/group/core/repository/querydsl/GroupRepositoryImpl.java`
around lines 294 - 307, Add the missing `@Override` annotation to the
findValidateGroupData method in GroupRepositoryImpl: locate the public
GroupValidationRes findValidateGroupData(Long groupId) method (uses QGroup,
queryFactory and constructs GroupValidationRes) and place `@Override` immediately
above its signature to match other overridden methods in the class.
src/main/java/at/mateball/domain/groupmember/core/repository/querydsl/GroupMemberRepositoryImpl.java (1)

844-854: @Override 어노테이션이 누락되었습니다.

인터페이스 GroupMemberRepositoryCustom에 선언된 메서드를 구현하므로 @Override 어노테이션을 추가해주세요.

🔧 수정 제안
-
-
     `@Override`
     public void createGroupMemberV3(Long userId, Long matchId) {
         User user = entityManager.getReference(User.class, userId);
         Group group = entityManager.getReference(Group.class, matchId);

-        GroupMember groupMember = GroupMember.member(user,group,GroupMemberStatus.AWAITING_APPROVAL.getValue());
+        GroupMember groupMember = GroupMember.member(user, group, GroupMemberStatus.AWAITING_APPROVAL.getValue());

         entityManager.persist(groupMember);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/at/mateball/domain/groupmember/core/repository/querydsl/GroupMemberRepositoryImpl.java`
around lines 844 - 854, The implementation of createGroupMemberV3 in
GroupMemberRepositoryImpl is missing the `@Override` annotation for the method
declared in GroupMemberRepositoryCustom; add the `@Override` annotation above the
createGroupMemberV3 method in GroupMemberRepositoryImpl to indicate it
implements the interface method (locate the createGroupMemberV3 method in
GroupMemberRepositoryImpl and annotate it with `@Override`).
src/main/java/at/mateball/domain/group/infrastructure/repository/GroupV3RepositoryImpl.java (1)

328-356: @Override 어노테이션 누락 및 null 안전성 검토 필요.

  1. 인터페이스 메서드 구현에 @Override 어노테이션을 추가해주세요.

  2. 이 메서드는 fetchOne()을 사용하여 그룹이 존재하지 않으면 null을 반환합니다. GroupV3Service.validateRequest에서 findValidateGroupData로 그룹 존재를 먼저 확인하지만, 두 쿼리 사이에 그룹이 삭제되는 경쟁 조건이 발생하면 NPE가 발생할 수 있습니다.

🔧 수정 제안
+    `@Override`
     public RequestValidationRes getValidation(Long userId, Long groupId) {

GroupV3Service.validateRequest에서 null 체크를 추가하거나, 반환 타입을 Optional<RequestValidationRes>로 변경하는 것을 고려해주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/at/mateball/domain/group/infrastructure/repository/GroupV3RepositoryImpl.java`
around lines 328 - 356, Add the missing `@Override` on getValidation in
GroupV3RepositoryImpl and make the method null-safe: either change its signature
to return Optional<RequestValidationRes> (so getValidation uses fetchOne() and
wraps result with Optional.ofNullable) or keep the current signature but ensure
callers (e.g., GroupV3Service.validateRequest and its findValidateGroupData
flow) perform a null check after calling getValidation to avoid NPE in the race
where the group was deleted; adjust all usages accordingly (references:
getValidation, GroupV3RepositoryImpl, RequestValidationRes,
GroupV3Service.validateRequest, findValidateGroupData, fetchOne).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/at/mateball/domain/group/core/service/GroupV3Service.java`:
- Around line 331-339: getValidation() can return null if the group was deleted
between findValidateGroupData() and the validation query, causing a potential
NPE when calling data.isDuplicatedRequest(); fix by null-checking the
RequestValidationRes returned from groupV3RepositoryCustom.getValidation(userId,
groupId) in GroupV3Service and if null throw a BusinessException (e.g.,
BusinessErrorCode.GROUP_NOT_FOUND) or re-run group existence validation, then
proceed to check data.isDuplicatedRequest() / data.hasPendingRequest().

---

Outside diff comments:
In `@src/main/java/at/mateball/domain/groupmember/core/GroupMember.java`:
- Around line 50-69: The 4-parameter GroupMember constructor leaves isLeader
null causing mismatches with queries in GroupV3RepositoryImpl; update the 4-arg
constructor in class GroupMember (the one taking User, Group, Boolean
isParticipant, int status) to explicitly set this.isLeader = false (or remove
the 4-arg ctor and replace its call sites to use the 5-arg constructor),
ensuring all places that create GroupMember (including where GroupExecutor is
constructed and the member() factory) produce non-null boolean values so
.isLeader.isTrue()/isFalse() filters work correctly.

---

Nitpick comments:
In
`@src/main/java/at/mateball/domain/group/core/repository/querydsl/GroupRepositoryImpl.java`:
- Around line 294-307: Add the missing `@Override` annotation to the
findValidateGroupData method in GroupRepositoryImpl: locate the public
GroupValidationRes findValidateGroupData(Long groupId) method (uses QGroup,
queryFactory and constructs GroupValidationRes) and place `@Override` immediately
above its signature to match other overridden methods in the class.

In
`@src/main/java/at/mateball/domain/group/infrastructure/repository/GroupV3RepositoryImpl.java`:
- Around line 328-356: Add the missing `@Override` on getValidation in
GroupV3RepositoryImpl and make the method null-safe: either change its signature
to return Optional<RequestValidationRes> (so getValidation uses fetchOne() and
wraps result with Optional.ofNullable) or keep the current signature but ensure
callers (e.g., GroupV3Service.validateRequest and its findValidateGroupData
flow) perform a null check after calling getValidation to avoid NPE in the race
where the group was deleted; adjust all usages accordingly (references:
getValidation, GroupV3RepositoryImpl, RequestValidationRes,
GroupV3Service.validateRequest, findValidateGroupData, fetchOne).

In
`@src/main/java/at/mateball/domain/groupmember/core/repository/querydsl/GroupMemberRepositoryImpl.java`:
- Around line 844-854: The implementation of createGroupMemberV3 in
GroupMemberRepositoryImpl is missing the `@Override` annotation for the method
declared in GroupMemberRepositoryCustom; add the `@Override` annotation above the
createGroupMemberV3 method in GroupMemberRepositoryImpl to indicate it
implements the interface method (locate the createGroupMemberV3 method in
GroupMemberRepositoryImpl and annotate it with `@Override`).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0e4607c7-3bb8-43bb-adf0-78514ad1206c

📥 Commits

Reviewing files that changed from the base of the PR and between a0333ed and 7eb3d44.

📒 Files selected for processing (13)
  • src/main/java/at/mateball/domain/alarm/common/AlarmType.java
  • src/main/java/at/mateball/domain/group/api/controller/GroupV3Controller.java
  • src/main/java/at/mateball/domain/group/api/dto/GroupValidationRes.java
  • src/main/java/at/mateball/domain/group/api/dto/RequestValidationRes.java
  • src/main/java/at/mateball/domain/group/core/repository/querydsl/GroupRepositoryCustom.java
  • src/main/java/at/mateball/domain/group/core/repository/querydsl/GroupRepositoryImpl.java
  • src/main/java/at/mateball/domain/group/core/service/GroupV3Service.java
  • src/main/java/at/mateball/domain/group/infrastructure/repository/GroupV3RepositoryCustom.java
  • src/main/java/at/mateball/domain/group/infrastructure/repository/GroupV3RepositoryImpl.java
  • src/main/java/at/mateball/domain/groupmember/core/GroupMember.java
  • src/main/java/at/mateball/domain/groupmember/core/repository/querydsl/GroupMemberRepositoryCustom.java
  • src/main/java/at/mateball/domain/groupmember/core/repository/querydsl/GroupMemberRepositoryImpl.java
  • src/main/java/at/mateball/exception/code/BusinessErrorCode.java
✅ Files skipped from review due to trivial changes (3)
  • src/main/java/at/mateball/domain/alarm/common/AlarmType.java
  • src/main/java/at/mateball/domain/group/api/dto/GroupValidationRes.java
  • src/main/java/at/mateball/domain/group/api/dto/RequestValidationRes.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/at/mateball/domain/group/infrastructure/repository/GroupV3RepositoryCustom.java
  • src/main/java/at/mateball/exception/code/BusinessErrorCode.java
  • src/main/java/at/mateball/domain/group/api/controller/GroupV3Controller.java

Copy link
Contributor

@YangDaJin0104 YangDaJin0104 left a comment

Choose a reason for hiding this comment

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

답글 달아 두었습니당!!!

Copy link
Contributor

Choose a reason for hiding this comment

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

넵 맞습니다아
지금 createRequest()는 검증, 요청 row 생성, 리더 상태 변경, 알림 생성까지 한 메서드에서 오케스트레이션하고 있어서 현재는 흐름이 잘 보이고 좋습니다만,,,
이후에 수락/거절/실패 처리까지 비슷한 패턴으로 계속 붙기 시작하면 GroupV3Service가 점점 커질 수 있어서. 그 시점에 매칭 요청/수락/거절 관련 흐름만 따로 묶는 상위 서비스나 도메인 정책 클래스로 분리해도 좋겠다는 의미였슴다

Comment on lines +337 to +339
if (data.hasPendingRequest()) {
throw new BusinessException(BusinessErrorCode.ALREADY_HAS_PENDING_REQUEST);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

unique constraint로 같은 사용자의 중복 요청은 막았는데, 이 메서드는 조회 기반 검증이라 서로 다른 두 사용자가 동시에 같은 match에 요청하는 경우까지는 완전하게 못 막을거같아서 보완하면 더 좋을거같아용!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/main/java/at/mateball/domain/group/core/repository/GroupRepository.java (1)

25-27: LGTM! 비관적 락 구현이 적절합니다.

동시 요청 시 race condition을 방지하기 위한 PESSIMISTIC_WRITE 락이 올바르게 적용되었습니다. 이는 과거 리뷰에서 지적된 동시성 이슈를 해결합니다.

운영 환경에서 락 대기 시간이 길어질 경우를 대비하여 @QueryHints로 타임아웃 설정을 고려해볼 수 있습니다.

♻️ 선택적 개선안: 락 타임아웃 설정
+import jakarta.persistence.QueryHint;
+import org.springframework.data.jpa.repository.QueryHints;

 `@Lock`(LockModeType.PESSIMISTIC_WRITE)
+@QueryHints({`@QueryHint`(name = "jakarta.persistence.lock.timeout", value = "3000")})
 `@Query`("select g from Group g where g.id = :groupId")
 Optional<Group> findGroupWithLock(Long groupId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/at/mateball/domain/group/core/repository/GroupRepository.java`
around lines 25 - 27, Add a pessimistic lock timeout hint to the existing
findGroupWithLock method to avoid indefinite waits under contention: update the
repository method (findGroupWithLock in GroupRepository) to include `@QueryHints`
with a javax.persistence.lock.timeout or "javax.persistence.query.timeout" value
(depending on JPA provider) and a suitable millisecond timeout value so that the
PESSIMISTIC_WRITE lock fails fast instead of blocking indefinitely; ensure the
hint is applied alongside `@Lock` and `@Query` annotations and adjust tests/handlers
to expect a lock timeout exception where appropriate.
src/main/java/at/mateball/domain/group/api/dto/GroupValidationRes.java (1)

12-18: 지연 로딩 연관관계 접근에 대한 고려사항

from() 메서드에서 group.getLeader().getId()group.getGameInformation().getGameDate()를 호출하는데, 두 연관관계 모두 FetchType.LAZY로 설정되어 있습니다 (src/main/java/at/mateball/domain/group/core/Group.java:20-26 참조).

현재 GroupRequestService.createRequest()@Transactional 내에서 호출하므로 LazyInitializationException은 발생하지 않습니다. 다만 요청당 2개의 추가 쿼리가 발생합니다.

단일 요청 처리에서는 큰 문제가 아니지만, 향후 성능 최적화가 필요할 경우 fetch join을 사용하는 findGroupWithLockFetchJoin() 메서드로 대체할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/at/mateball/domain/group/api/dto/GroupValidationRes.java`
around lines 12 - 18, GroupValidationRes.from에서 group.getLeader().getId()와
group.getGameInformation().getGameDate()가 LAZY 연관관계를 직접 접근해 요청당 추가 쿼리가 발생하므로, 성능
최적화 시 GroupRequestService.createRequest에서 호출하는 코드 경로를 변경해 fetch join을 사용한 조회로
대체하세요; 구체적으로 현재 사용하는 엔티티 조회를 findGroupWithLockFetchJoin 같은 fetch-join 기반 메서드로 바꿔
필요한 연관(leader, gameInformation)을 한 번에 로드하거나, 호출 전 트랜잭션 내부에서 명시적으로 초기화해 추가 쿼리를
제거하도록 수정하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/at/mateball/domain/groupRequest/GroupRequestService.java`:
- Around line 35-36: The call to groupV3RepositoryCustom.getValidation(userId,
groupId) can return null and must be checked before calling
groupRequestValidator.validateCreateRequest; update GroupRequestService to
verify the returned RequestValidationRes (e.g., validationData) is not null and
handle the null case (throw a suitable exception or return an error) instead of
passing it to groupRequestValidator.validateCreateRequest(userId,
validatedGroup, validationData); ensure you reference getValidation,
RequestValidationRes, groupV3RepositoryCustom,
groupRequestValidator.validateCreateRequest, userId, groupId and validatedGroup
when locating where to add the null check and error handling.

---

Nitpick comments:
In `@src/main/java/at/mateball/domain/group/api/dto/GroupValidationRes.java`:
- Around line 12-18: GroupValidationRes.from에서 group.getLeader().getId()와
group.getGameInformation().getGameDate()가 LAZY 연관관계를 직접 접근해 요청당 추가 쿼리가 발생하므로, 성능
최적화 시 GroupRequestService.createRequest에서 호출하는 코드 경로를 변경해 fetch join을 사용한 조회로
대체하세요; 구체적으로 현재 사용하는 엔티티 조회를 findGroupWithLockFetchJoin 같은 fetch-join 기반 메서드로 바꿔
필요한 연관(leader, gameInformation)을 한 번에 로드하거나, 호출 전 트랜잭션 내부에서 명시적으로 초기화해 추가 쿼리를
제거하도록 수정하세요.

In `@src/main/java/at/mateball/domain/group/core/repository/GroupRepository.java`:
- Around line 25-27: Add a pessimistic lock timeout hint to the existing
findGroupWithLock method to avoid indefinite waits under contention: update the
repository method (findGroupWithLock in GroupRepository) to include `@QueryHints`
with a javax.persistence.lock.timeout or "javax.persistence.query.timeout" value
(depending on JPA provider) and a suitable millisecond timeout value so that the
PESSIMISTIC_WRITE lock fails fast instead of blocking indefinitely; ensure the
hint is applied alongside `@Lock` and `@Query` annotations and adjust tests/handlers
to expect a lock timeout exception where appropriate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 254d6112-6ff5-43bc-b9c1-1261cd77ace9

📥 Commits

Reviewing files that changed from the base of the PR and between 7eb3d44 and 3d09586.

📒 Files selected for processing (5)
  • src/main/java/at/mateball/domain/group/api/dto/GroupValidationRes.java
  • src/main/java/at/mateball/domain/group/core/repository/GroupRepository.java
  • src/main/java/at/mateball/domain/group/core/service/GroupV3Service.java
  • src/main/java/at/mateball/domain/groupRequest/GroupRequestService.java
  • src/main/java/at/mateball/domain/groupRequest/GroupRequestValidator.java

Comment on lines +35 to +36
RequestValidationRes validationData = groupV3RepositoryCustom.getValidation(userId, groupId);
groupRequestValidator.validateCreateRequest(userId, validatedGroup, validationData);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

getValidation() 결과에 대한 null 체크가 누락되었습니다.

groupV3RepositoryCustom.getValidation()은 그룹이 존재하지 않으면 null을 반환할 수 있습니다. 비관적 락으로 그룹을 조회한 후이지만, 극단적인 경우 null이 반환되면 Line 36에서 validateCreateRequest() 호출 시 NPE가 발생할 수 있습니다.

🛡️ 수정 제안
 GroupValidationRes validatedGroup = GroupValidationRes.from(group);
 RequestValidationRes validationData = groupV3RepositoryCustom.getValidation(userId, groupId);
+
+if (validationData == null) {
+    throw new BusinessException(BusinessErrorCode.GROUP_NOT_FOUND);
+}
+
 groupRequestValidator.validateCreateRequest(userId, validatedGroup, validationData);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/at/mateball/domain/groupRequest/GroupRequestService.java`
around lines 35 - 36, The call to groupV3RepositoryCustom.getValidation(userId,
groupId) can return null and must be checked before calling
groupRequestValidator.validateCreateRequest; update GroupRequestService to
verify the returned RequestValidationRes (e.g., validationData) is not null and
handle the null case (throw a suitable exception or return an error) instead of
passing it to groupRequestValidator.validateCreateRequest(userId,
validatedGroup, validationData); ensure you reference getValidation,
RequestValidationRes, groupV3RepositoryCustom,
groupRequestValidator.validateCreateRequest, userId, groupId and validatedGroup
when locating where to add the null check and error handling.

@hyeeum hyeeum requested a review from YangDaJin0104 March 22, 2026 06:48
Copy link
Contributor

@YangDaJin0104 YangDaJin0104 left a comment

Choose a reason for hiding this comment

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

동시성 문제 해결한 부분 확인했습니당! 생성 예외처리와 방어 로직은 보안하면 더 좋을거같습니당

Copy link
Contributor

Choose a reason for hiding this comment

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

넵 확인했습니당!


GroupMember groupMember = GroupMember.member(user,group,GroupMemberStatus.AWAITING_APPROVAL.getValue());

entityManager.persist(groupMember);
Copy link
Contributor

Choose a reason for hiding this comment

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

여기서 DB unique constraint 실패를 비지니스 예외로 바꿔줘야 서버 500 에러가 안터집니당!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

#238 에 새롭게 추가된 ConstraintExceptionTranslator 가 ok 싸인받고 병합된다면,translator 를 사용해서 예외처리를 하겠습니다!

Copy link
Contributor

Choose a reason for hiding this comment

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

넵 확인했습니당!!

@hyeeum hyeeum force-pushed the feat/#239-create-request-v3-migration branch from dade148 to 73d37a7 Compare March 22, 2026 16:21
@hyeeum hyeeum requested a review from YangDaJin0104 March 22, 2026 16:33
Copy link
Contributor

@YangDaJin0104 YangDaJin0104 left a comment

Choose a reason for hiding this comment

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

바느질 둠칫둠칫 시작해볼까용

@hyeeum hyeeum merged commit ce1912a into dev Mar 22, 2026
1 check passed
@hyeeum hyeeum deleted the feat/#239-create-request-v3-migration branch March 22, 2026 16:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat]매칭 요청 api v3 마이그레이션

2 participants