-
Notifications
You must be signed in to change notification settings - Fork 1
[feat] 방 참여하기/취소하기 api 개발 #87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4d810a1
171fe97
032a4de
e88c948
17dea36
73bf174
9b7b891
db8757c
82989e9
88fb7b0
8b0ad9c
c828d04
ef01f18
7fc67a3
bbfd174
539ced9
467af61
82f93ce
57587f2
7b134dc
fa96e99
15e1a92
4ade6e4
dc844b2
e4b5b01
46bf492
35d1baa
f4b4130
e30c759
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package konkuk.thip.room.adapter.in.web.request; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
| import konkuk.thip.room.application.port.in.dto.RoomJoinCommand; | ||
|
|
||
| public record RoomJoinRequest( | ||
| @NotBlank(message = "방 참여 유형 파라미터는 필수입니다..") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. .. 오타있어요! |
||
| String type | ||
| ) { | ||
| public RoomJoinCommand toCommand(Long userId, Long roomId) { | ||
| return new RoomJoinCommand(userId, roomId, type); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,10 +4,12 @@ | |
| import konkuk.thip.common.entity.BaseJpaEntity; | ||
| import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; | ||
| import lombok.*; | ||
| import org.hibernate.annotations.SQLDelete; | ||
|
|
||
| @Entity | ||
| @Table(name = "room_participants") | ||
| @Getter | ||
| @SQLDelete(sql = "UPDATE room_participants SET status = 'INACTIVE' WHERE room_participant_id = ?") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain소프트 삭제 구현이 올바르게 적용되었습니다
다음 스크립트로 기존 쿼리들이 status 필터링을 올바르게 처리하는지 확인해보겠습니다: 🏁 Script executed: #!/bin/bash
# 설명: RoomParticipant 관련 쿼리들이 status 필터링을 올바르게 처리하는지 확인
# RoomParticipant 관련 repository 메서드들 검색
rg -A 10 "findBy.*RoomParticipant|existBy.*RoomParticipant" --type java
# Native query에서 status 조건 처리 확인
rg -A 5 -B 5 "SELECT.*room_participants|FROM.*room_participants" --type javaLength of output: 2928 조회 메서드에 상태 필터 추가 필요 소프트 삭제(@SQLDelete)는 올바르게 적용되었으나, 아래 조회 로직에 – src/main/java/konkuk/thip/room/adapter/out/persistence/repository/roomparticipant/RoomParticipantJpaRepository.java • existByUserIdAndRoomId는 이미 🤖 Prompt for AI Agents |
||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @AllArgsConstructor | ||
| @Builder | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,7 @@ public enum RoomParticipantRole { | |
| HOST("호스트"), | ||
| MEMBER("팀원"); | ||
|
|
||
| private String type; | ||
| private final String type; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍🏻 |
||
|
|
||
| RoomParticipantRole(String type) { | ||
| this.type = type; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package konkuk.thip.room.adapter.out.persistence; | ||
|
|
||
| import konkuk.thip.room.adapter.out.persistence.repository.roomparticipant.RoomParticipantJpaRepository; | ||
| import konkuk.thip.room.application.port.out.RoomParticipantQueryPort; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| @Repository | ||
| @RequiredArgsConstructor | ||
| public class RoomParticipantQueryPersistenceAdapter implements RoomParticipantQueryPort { | ||
|
|
||
| private final RoomParticipantJpaRepository roomParticipantJpaRepository; | ||
|
|
||
| @Override | ||
| public boolean existByUserIdAndRoomId(Long userId, Long roomId) { | ||
| return roomParticipantJpaRepository.existByUserIdAndRoomId(userId, roomId); | ||
| } | ||
|
|
||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| package konkuk.thip.room.adapter.out.persistence.repository.roomparticipant; | ||
|
|
||
| public interface RoomParticipantQueryRepository { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package konkuk.thip.room.adapter.out.persistence.repository.roomparticipant; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| @Repository | ||
| @RequiredArgsConstructor | ||
| public class RoomParticipantQueryRepositoryImpl implements RoomParticipantQueryRepository{ | ||
|
|
||
| } | ||
|
Comment on lines
+1
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain빈 구현체 클래스의 필요성을 확인해주세요. 현재 다음 스크립트로 이 인터페이스가 어떻게 사용되는지 확인해보겠습니다: 🏁 Script executed: #!/bin/bash
# RoomParticipantQueryRepository 인터페이스 사용 패턴 확인
ast-grep --pattern $'interface RoomParticipantQueryRepository {
$$$
}'
# 구현체에서 실제로 구현해야 할 메서드가 있는지 확인
rg -A 5 "RoomParticipantQueryRepository"Length of output: 2020 RoomParticipantQueryRepositoryImpl 빈 구현체 제거 또는 커스텀 쿼리 정의 필요 RoomParticipantQueryRepository 인터페이스가 메서드를 하나도 선언하지 않아, 현재 빈 구현체인 확인 및 조치가 필요한 위치:
🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package konkuk.thip.room.application.port.in; | ||
|
|
||
| import konkuk.thip.room.application.port.in.dto.RoomJoinCommand; | ||
|
|
||
| public interface RoomJoinUsecase { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usecase -> UseCase |
||
|
|
||
| void changeJoinState(RoomJoinCommand roomJoinCommand); | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package konkuk.thip.room.application.port.in.dto; | ||
|
|
||
| public record RoomJoinCommand( | ||
| Long userId, | ||
| Long roomId, | ||
| String type | ||
| ) { | ||
| } | ||
|
Comment on lines
+3
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 타입 안전성을 위해 enum 사용을 고려해보세요.
다음과 같이 enum을 도입하는 것을 제안합니다: +public enum RoomJoinType {
+ JOIN, CANCEL
+}
+
public record RoomJoinCommand(
Long userId,
Long roomId,
- String type
+ RoomJoinType type
) {
}
🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,6 @@ | ||
| package konkuk.thip.room.application.port.out; | ||
|
|
||
|
|
||
| public interface RoomParticipantQueryPort { | ||
| boolean existByUserIdAndRoomId(Long userId, Long roomId); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,80 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| package konkuk.thip.room.application.service; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.common.exception.EntityNotFoundException; | ||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.common.exception.InvalidStateException; | ||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.common.exception.code.ErrorCode; | ||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.room.application.port.in.RoomJoinUsecase; | ||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.room.application.port.in.dto.RoomJoinCommand; | ||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.room.application.port.out.RoomCommandPort; | ||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.room.application.port.out.RoomParticipantCommandPort; | ||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.room.application.port.out.RoomParticipantQueryPort; | ||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.room.domain.Room; | ||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.room.domain.RoomJoinType; | ||||||||||||||||||||||||||||||||||||||||||||
| import konkuk.thip.room.domain.RoomParticipant; | ||||||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.transaction.annotation.Transactional; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import static konkuk.thip.room.adapter.out.jpa.RoomParticipantRole.MEMBER; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Service | ||||||||||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||
| public class RoomJoinService implements RoomJoinUsecase { | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private final RoomParticipantQueryPort roomParticipantQueryPort; | ||||||||||||||||||||||||||||||||||||||||||||
| private final RoomCommandPort roomCommandPort; | ||||||||||||||||||||||||||||||||||||||||||||
| private final RoomParticipantCommandPort roomParticipantCommandPort; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||||||||||||||||||||||||
| public void changeJoinState(RoomJoinCommand roomJoinCommand) { | ||||||||||||||||||||||||||||||||||||||||||||
| RoomJoinType type = RoomJoinType.from(roomJoinCommand.type()); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // 방이 존재하지 않거나 만료된 경우 | ||||||||||||||||||||||||||||||||||||||||||||
| Room room; | ||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||
| room = roomCommandPort.findById(roomJoinCommand.roomId()); | ||||||||||||||||||||||||||||||||||||||||||||
| } catch (EntityNotFoundException e) { | ||||||||||||||||||||||||||||||||||||||||||||
| throw new InvalidStateException(ErrorCode.USER_CANNOT_JOIN_OR_CANCEL); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| boolean isParticipate = roomParticipantQueryPort.existByUserIdAndRoomId(roomJoinCommand.userId(), roomJoinCommand.roomId()); | ||||||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p3 : 어차피 RoomParticipant 도메인이 필요하니 boolean 으로 roomParticipant의 존재여부를 조회하는 것보다는, 처음부터 RoomParticipant 도메인 자체를 조회하는건 어떤가요?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 초기에는 요청 타입에 따라 분기한 뒤 필요한 경우에만 도메인을 조회하는 방식이 도메인의 의도를 명확히 드러낸다고 판단하여 그렇게 구현하였는데, 리뷰 주신 내용을 바탕으로 다시 검토해보니, Optional로 한 번만 조회하고, 존재 여부에 따라 분기 처리하는 방식도 흐름이 명확하고 불필요한 중복 쿼리를 줄일 수 있다는 점에서 장점이 있을 것 같네요. 말씀 주신대로 findBy를 통해 Optional을 반환하도록 리팩토링해보겠습니다!
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 Port 에서 제공하는 findById 메서드가 Optional 이 아니라면(저희 도메인을 조회하는 메서드는 전부 도메인 객체가 반환타입 + NOT_FOUND exception throw 플로우였으니), try-catch를 활용하는 방법도 있을 것 같습니다! |
||||||||||||||||||||||||||||||||||||||||||||
| room.validateRoomExpired(); | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+34
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 검증 순서 조정 및 구체적인 에러 처리 필요 방의 만료 상태를 먼저 확인한 후 참여 상태를 확인하는 것이 더 논리적입니다: Room room;
try {
room = roomCommandPort.findById(roomJoinCommand.roomId());
+ room.validateRoomExpired(); // 만료 검증을 먼저 수행
} catch (EntityNotFoundException e) {
- throw new InvalidStateException(ErrorCode.USER_CANNOT_JOIN_OR_CANCEL);
+ throw new EntityNotFoundException(ErrorCode.ROOM_NOT_FOUND);
}
boolean isParticipate = roomParticipantQueryPort.existByUserIdAndRoomId(roomJoinCommand.userId(), roomJoinCommand.roomId());
-room.validateRoomExpired();📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p2: 코래 말처럼 검증순서를 또한, RoomParticipant 여부를 먼저 확인하면, 이미 참여 여부를 확인하는 DB 쿼리가 실행된 후에 방이 이미 만료된 상태임이 확인될 경우, 해당 RoomParticipant 쿼리는 필요 없는 호출이 될 것 같아요 DB 리소스 측면에서도 위와같은 방향으로 코드를 수정하는 것이 좋아 보입니다!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아하 수정하겠습니다! |
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // 참여하기 요청 | ||||||||||||||||||||||||||||||||||||||||||||
| if(type.isJoinType()) { | ||||||||||||||||||||||||||||||||||||||||||||
| // 이미 참여한 상태 | ||||||||||||||||||||||||||||||||||||||||||||
| if(isParticipate) { | ||||||||||||||||||||||||||||||||||||||||||||
| throw new InvalidStateException(ErrorCode.USER_ALREADY_PARTICIPATE); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| RoomParticipant roomParticipant = RoomParticipant.withoutId(roomJoinCommand.userId(), roomJoinCommand.roomId(), MEMBER.getType()); | ||||||||||||||||||||||||||||||||||||||||||||
| roomParticipantCommandPort.save(roomParticipant); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| //Room의 memberCount 업데이트 | ||||||||||||||||||||||||||||||||||||||||||||
| room.increaseMemberCount(); | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+54
to
+55
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p3 : 저희가 개발시에 빼먹을 수도 있는 부분이라고 생각 + RoomParticipant와는 다른 애그리거트인 Room의 상태변경이 이루어진다고 생각하는데, 추후에 어떻게 최적화를 할 수 있을지(ex. event driven) 고민해보면 좋을거 같습니다!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 다른 애그리거트이기 때문에 이벤트 처리 기반으로 상태 변경을 하자는 말씀이신가요..?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 나~~중에 얘기긴 합니다 하하
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 조금 덧붙이자면, 다른 애그리거트이기 때문에 분리해보자 라기보다는 해당 로직처럼 여러 애그리거트가 업데이트 되어야하는 플로우는 저희가 (특히 제가) 로직을 빼먹거나 놓칠 가능성이 있다고 생각하는데, 이걸 개선할 방법이 없을까? 라는 생각을 해보자 라는 말이었습니다 |
||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // 취소하기 요청 | ||||||||||||||||||||||||||||||||||||||||||||
| if(!type.isJoinType()) { | ||||||||||||||||||||||||||||||||||||||||||||
| // 참여하지 않은 상태 | ||||||||||||||||||||||||||||||||||||||||||||
| if(!isParticipate) { | ||||||||||||||||||||||||||||||||||||||||||||
| throw new InvalidStateException(ErrorCode.USER_NOT_PARTICIPATED); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // 방장이 참여 취소를 요청한 경우 | ||||||||||||||||||||||||||||||||||||||||||||
| RoomParticipant roomParticipant = roomParticipantCommandPort.findByUserIdAndRoomId(roomJoinCommand.userId(), roomJoinCommand.roomId()); | ||||||||||||||||||||||||||||||||||||||||||||
| roomParticipant.validateHostCancelRoom(); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| roomParticipantCommandPort.deleteByUserIdAndRoomId(roomJoinCommand.userId(), roomJoinCommand.roomId()); | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+65
to
+69
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p3 : RoomParticipant 도메인이 '방장이 참여 취소를 요청했는지를 검사한다' 라는 메서드를 제공하기 보다는, '해당 참여자의 참여 취소를 한다' 라는 메서드를 제공하고, 이 메서드 내부에서 '방장이면 exception throw' 라는 로직을 수행하는 플로우는 어떤가요? 이러면 서비스는
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵 그게 좀더 명확할 것 같네요! 수정하겠습니다 |
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| //Room의 memberCount 업데이트 | ||||||||||||||||||||||||||||||||||||||||||||
| room.decreaseMemberCount(); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // 방의 상태 업데이트 | ||||||||||||||||||||||||||||||||||||||||||||
| roomCommandPort.updateMemberCount(room); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM