-
Notifications
You must be signed in to change notification settings - Fork 0
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
골 참여자 방출 기능 구현 #99
골 참여자 방출 기능 구현 #99
Conversation
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.
기능 구현하시느라 고생 많으셨습니다!
몇 가지 질문들이 있어 일단 rc로 처리해 두었습니다.
논의 사항에 대한 답변은 각 TODO 코드 쪽에 코멘트 달아두었습니다.
의견을 정리하자면 현재 상태가 좋다입니다!
private void validUserToDeleteUser(final Long userId) { | ||
if (!isManager(userId)) { | ||
throw new ForbiddenGoalToDeleteException.ForbiddenUserToDeleteUser(); | ||
} | ||
} |
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.
해당 부분은 생각하지 못했는데 좋네요! 👍
final List<GoalTeam> updatedGoalTeams = this.goalTeams.stream() | ||
.filter(GoalTeam::isDeletedFalse) | ||
.toList(); | ||
updateGoalTeams(updatedGoalTeams); | ||
} | ||
|
||
private void updateGoalTeams(final List<GoalTeam> goalTeams) { | ||
this.goalTeams = goalTeams; | ||
} |
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.
이렇게 하더라도 goalTeam 테이블에서 삭제된 사용자가 제거되거나 goalId를 null로 만드는 등의 행위는 하지 않을 것으로 보이는데, 삭제 후 다시 조회했을 때 삭제된 사용자는 불러와지지 않나요? 어떤 의미가 있는 것인지 궁금합니다.
또한, 팀원을 softDelete하는 것과 hardDelete하는 것 중 뭐가 더 적절할지도 고민이네요!
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.
이렇게 하면 삭제된 사용자는 불러와지지 않습니다. updateGoalTeams를 하지 않을 경우, 삭제된 사용자가 그대로 goalTeams에 남아있기 때문에 삭제를 하지 않은 사용자들만 다시 goalTeams에 할당했습니다. 그런데 너무 어렵게 생각한 것 같네요.. 정수님이 밑에서 피드백 주신대로 for문 돌면서 if문 걸리면 delete하고 goalTeams에서도 remove 하는 방식으로 수정했습니다!
softDelete vs hardDelete 관련해서는 골 팀은 hardDelete를 해도 문제가 없을 것 같아요. 정말 단지 골 참여자 목록에 추가하기 위해 만들어진 테이블이기 때문에 소속 되어있던 골에서 방출되면 더 이상 테이블의 존재 의미가 없어지긴 합니다. 추후 분석용 데이터로서는 어느정도 의미가 있을까요? 딱히 생각나는 활용방안이 없어서 저는 hardDelete를 해도 좋을 것 같습니다.
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.
hardDelete를 해도 괜찮을 것 같습니다.
원래 해당 리뷰를 작성할 때 팀원 정보가 스탬프에 영향을 주지 않을까 했었는데, user를 fk로 사용하고 있어 큰 문제가 없을 것 같습니다.
그래서 결론적으로는 hardDelete로 하면 좋을 것 같네요!
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.
그런데 Teams
의 deleteUser
에서는 현재는 soft delete로 처리하고 있는 것 같은데 맞을까요?
코멘트와 로직이 서로 달라 여쭤봅니다.
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.
hardDelete로 구현하기로 결정된 게 아니라고 판단해 로직을 추가하지 않았습니다. 로직 추가해서 리뷰 재요청하도록 하겠습니다.
// TO DO: api 형식에서 /user는 불필요할까요? | ||
@DeleteMapping(value = "/{goalId}/user/{userId}", headers = "X-API-VERSION=1") |
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.
저는 필요하다고 생각합니다!
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.
TO DO: 골 아이디와 사용자 아이디를 받아서 모든 스탬프를 삭제하는 로직을 스탬프 서비스에 구현했는데, | ||
골 서비스에서 한꺼번에 구현하는 것이 나을까요? | ||
*/ | ||
public void deleteAllByGoalIdAndUserId(final Long goalId, final Long userId) { | ||
final List<Stamp> stamps = stampRepository.findAllByGoalIdAndUserId(goalId, userId); | ||
stamps.forEach(Stamp::delete); | ||
} |
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.
해당 부분은 좀 고민이 되네요....
일당 당장의 생각은 Goal 패키지에서 stamp가 어떻게 삭제되는지는 알 필요가 없다고 생각하긴 합니다. 그리고 StampService와 GoalService에서 순환참조가 일어나지 않고 있으므로 문제는 없다고 생각합니다. 그래서 일단은 현재처럼 진행하고 나중에 전체적으로 의존성과 관련해 리팩터링에 대한 논의를 하면 좋을 것 같긴 합니다!
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.
좋습니다! 그러면 지금은 일단 이렇게 두고, 의존성 관련 리팩터링에 대한 논의 후에 정해진 대로 리팩터링 하는 것으로 해요!
final List<GoalTeam> goalTeam = goal.getTeams() | ||
.stream() | ||
.filter(GoalTeam::isDeletedFalse) | ||
.toList(); |
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.
방출에 대한 테스트를 할 때 해당 부분은 적절하지 않다고 생각합니다.
이렇게 되면 골 조회 등과 같은 상황에 항상 해당 로직을 서비스에서 수행해야 한다는 생각이 드네요.
그래서 goal의 teams를 가져와 진행하는 것이 의도하신 바에 더 적합한 테스트가 아닌가 하는 생각이 들어서요.
방출과 관련해 의도하신 것이 무엇일까요?
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.
delete 메서드의 반환값이 없어서 트랜잭션 전이가 안되어서 goal의 teams를 가져와도 delete를 수행한 값대로 받아와지지 않더라구요... 이 부분은 아예 잘못된 부분인 것 같습니다.
맨 밑에 피드백 주신대로 원래 골을 수정했을 때 ReadGoalDetaildto로 수정된 내용을 클라이언트로 넘겨주었는데 골 참여자 방출을 delete로 생각하다보니 반환값이 없어도 될 것이라 생각했던 것 같습니다. 그런데 이렇게 되면 수정된 내용을 넘겨주지 못하니 테스트 코드도 부자연스럽게 억지로 끼워맞출 수 밖에 없었던 것 같네요😅 ReadGoalDetailDto로 수정된 내용을 클라이언트로 넘겨주도록 메서드 수정해서 테스트 코드도 그에 맞게 수정했습니다.
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.
테스트 클래스에는 @Transactional
을 붙이지 않기에 전이가 되지 않는 것이 맞습니다.
그래서 만약 해당 테스트를 수행했을 것이라면, deleteUser
수행 후 goalRepository
를 통해 다시 조회해 가져오는 것이 적절헀을 것이라 생각합니다!
이와 관련해 해당 클래스 157번째 라인인 요청받은_골의_아이디에_해당하는_골을_삭제한다
에 대해 예외가 일어나지 않는다가 아니라, 해당 서비스 메서드 수행 후 goalRepository
에서 해당 골을 조회할 때 optional
의 isEmpty()
로 테스트를 수행하는 것은 어떨까요?
테스트하고자 하는 바를 명확히 하는 것이 좋을 것 같아 제안드립니다!
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.
말씀주신 방식이 테스트 목적을 더 잘 반영할 것 같네요! 이렇게 수정하도록 하겠습니다!
@@ -171,4 +173,11 @@ public void acceptGoalRequest(final Long userId, final Long goalId) { | |||
final Goal goal = getGoal(goalId); | |||
goal.updateAccepted(user); | |||
} | |||
|
|||
public void deleteUser(final Long userId, final Long goalId, final Long managerId) { |
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.
managerId에 들어오는 값은 항상 매니저가 아니라고 생각합니다.
그래서 해당 변수를 userId로 하고, userId deleteUserId라고 하는 건 어떨까요?
이건 그냥 제안사항이기에 효선님께서 판단해 주시고 더 적절하다고 생각되는 것으로 수정해 주시면 됩니다!
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.
좋습니다! managerId를 userId로 바꾸고 현 userId를 deleteUserId로 바꾸는 게 좋은 것 같네요!
public void deleteUser(final User user, final Long managerId) { | ||
validUserToDelete(user); | ||
validUserToDeleteUser(managerId); | ||
teams.deleteUser(user.getId()); |
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.
굳이 user
가 아닌 user.getId()
를 전달하는 이유가 있을까요?
user에 id는 어차피 들어있고 user.equals(user)
라고 하면 더 편할 것 같은데 해당 로직을 user.getId.equals(userId)
로 되어 있어 여쭤봅니다!
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.
여기까진 미처 생각을 못한 것 같습니다..! user 전체 넘겨주는 것으로 수정했습니다!
@@ -57,4 +57,12 @@ private void processDefaultManager(final User user, final Goal goal) { | |||
public void updateAccepted() { | |||
this.accepted = true; | |||
} | |||
|
|||
public void updateDeleted() { |
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.
그냥 delete
라고 하는 것이 더 적절하다고 생각합니다.
왜냐하면 해당 메서드는 deleted
의 값을 매개변수로 주입해 바꾸는 것이 아니라 무조건 삭제의 의미인 true
로 바꾸는 것이기에 그냥 delete()
라는 메서드명으로 삭제한다는 의미를 명확하게 전달하는 것이 더 좋다고 생각합니다.
this.deleted = true; | ||
} | ||
|
||
public boolean isDeletedFalse() { |
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.
해당 메서드를 만들어 사용하는 이유는 무엇일까요?
lombok을 통해 만들어지는 !isDeleted()
를 사용하지 않은 이유가 있을까요?
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.
final List<User> usersBeforeUpdate = this.goalTeams.stream()
.filter(GoalTeam::isDeletedFalse)
.map(GoalTeam::getUser)
.toList();
위 같은 로직에서 filter를 걸 때 !isDeleted()를 사용하게 되면 goalTeam -> !goalTeam.isDeleted()로 사용하게 됩니다. 이것이 반복되니 아예 따로 deleted가 false인지를 true로 반환하는 메서드를 만들어서 간결하게 표현하는 것이 낫지 않나 생각해 따로 메서드를 만들었습니다.
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.
저는 오히려 동일한 로직이 추가되어 코드의 중복이 발생하는 것이 아닌가 하는 의문이 있습니다.
또한, 해당 메서드를 사용할 것이라면 isNotDeleted()
는 어떨까요?
GoalTeams
객체 외부에서는 deleted가 boolean 타입인지 다른 타입인지 알 필요가 없다고 생각합니다.
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.
고민해봤는데 isDeletedFalse 자체가 굳이 필요하지 않은 메서드를 만든 것 같아 삭제하고 isDeleted의 True/False 여부 확인으로 수정했습니다!
this.goalTeams.forEach(goalTeam -> { | ||
if (goalTeam.getUser().getId().equals(userId)) { | ||
goalTeam.updateDeleted(); | ||
} | ||
}); |
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.
개인적으로 아래와 같은 for문을 통해 break를 추가하면 어떨까 하는 생각이 있습니다.
현재는 물론 최대 5명이기에 상관없긴 하지만, 팀원이 많아질 수 있다면 결국 성능상 아주 조금은 낮아지지 않을까 해서요!
물론 정말 완벽한 성능 개선을 생각한다면 좀 더 고민해 봐야 하긴 하지만요...ㅎㅎ
this.goalTeams.forEach(goalTeam -> { | |
if (goalTeam.getUser().getId().equals(userId)) { | |
goalTeam.updateDeleted(); | |
} | |
}); | |
for (GoalTeam goalTeam : goalTeams) { | |
if (goalTeam.getUser().getId().equals(userId)) { | |
goalTeam.updateDeleted(); | |
break; | |
} | |
} |
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.
위에서 코멘트 남긴대로 for문 돌다가 if문에서 걸리면 goalTeam delete 해주고 Teams에서는 remove해주는 식으로 구현했습니다. 감사합니다!
갑자기 생각이 나 코멘트 추가합니다. |
* chore: flyway 의존성 추가 및 설정 * docs: flyway를 위한 테이블 초기 스크립트 추가 * ci: 서브모듈 최신화 * docs: 스탬프 컬럼 최신화 적용 * docs: flyway 스크립트 실패 시 자동 제거 스크립트 추가 * docs: 실패 자동 제거 테스트를 위한 스크립트 추가 * ci: flyway 스크립트 검증 workflow 추가 * fix: 테스트에서 flyway는 수행하지 않도록 수정 * fix: flyway 검증이 안 되는 문제 해결 * ci: 서브모듈 최신화 * ci: 서브모듈 최신화 * ci: 서브모듈 최신화 * docs: 콜백 스크립트 명 수정 * docs: 콜백 스크립트 명 수정 * docs: 콜백 스크립트 명 수정 * docs: 테스트를 위한 flyway 스크립트 제거 * chore: flyway 잘못된 버전 수정 * refactor: 메시지의 컬럼 타입 변경 text -> varchar * docs: fk 변경사항 수정 fk_goal_team_user <-> fk_goal_team_goal
브랜치 최신화 관련해서 질문이 있습니다...! |
rebase가 아닌 merge를 해야 합니다! |
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.
수정하신 사항들 모두 확인 완료했습니다!
수정하시느라 고생 많으셨습니다.
질문 사항 및 기록용 코멘트 남겨두었습니다. 머지 전에 한 번 확인해 주시면 감사하겠습니다!
@@ -171,4 +176,17 @@ public void acceptGoalRequest(final Long userId, final Long goalId) { | |||
final Goal goal = getGoal(goalId); | |||
goal.updateAccepted(user); | |||
} | |||
|
|||
public ReadGoalDetailDto deleteUser(final Long deleteUserId, final Long goalId, final Long userId) { |
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.
당장 좋은 아이디어가 떠오르지 않지만, 추후 리팩터링 진행 시 어떻게 하면 쿼리를 줄일 수 있을지 함께 고민해 보면 좋을 것 같습니다.
(정말 줄일 수 없는 것이라면 그러한 결론도 괜찮고요!)
기록 겸 코멘트 달아둡니다.
@@ -78,4 +78,8 @@ public boolean isTeamAndAccepted(final User user) { | |||
return goalTeams.stream() | |||
.anyMatch(goalTeam -> goalTeam.getUser().equals(user) && goalTeam.isAccepted()); | |||
} | |||
|
|||
public void deleteUser(final User user) { | |||
goalTeams.removeIf(goalTeam -> goalTeam.getUser().equals(user)); |
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.
👍👍👍
@Query(""" | ||
SELECT s | ||
FROM Stamp s | ||
WHERE s.goal.id = :goalId AND s.user.id = :userId | ||
""") | ||
List<Stamp> findAllByGoalIdAndUserId(final Long goalId, final Long userId); |
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.
jpa 네이밍만으로 안 되기에 쿼리문을 사용한 것이 맞을까요?
궁금해서 여쭤봅니다!
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.
네 맞습니다! 네이밍만으로 조회가 불가하다고 판단해 쿼리문 사용했습니다!
final List<User> goalUsers = goal.getTeams() | ||
.stream() | ||
.map(GoalTeam::getUser) | ||
.toList(); |
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.
해당 부분은 assertSofly
밖에 작성해줘도 좋을 것 같네요!
오히려 테스트하고자 하는 부분이 잘 안 보여서요.
@Test | ||
void 골_참여자를_방출한다() { | ||
// when | ||
final ReadGoalDetailDto result = goalService.deleteUser(방출할_사용자_아이디, 유효한_골_아이디, 유효한_사용자_아이디); |
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.
저희 테스트하고자 하는 객체에 대한 필드명은 acutal
로 하기로 했던 것으로 기억합니다!
Quality Gate passedIssues Measures |
3/30
골 참여자 방출 기능 구현 완료했습니다.
두가지 논의사항이 있어 질문 드립니다. 논의가 필요한 코드 윗줄에 TO DO로 주석 남겨놓아서 쉽게 찾아보실 수 있을 것 같습니다.
[논의사항]
[작업 내용]
p.s. 이슈번호 잘못 보고 브랜치 파는 바람에 브랜치를 다시 팠습니다.. 다음엔 확실히 확인하도록 하겠습니다..