Conversation
Walkthrough사용자 간 팔로우 기능이 추가되었습니다: Follow 엔티티, FollowService, FollowRepository, 컨트롤러 엔드포인트 및 관련 예외와 에러코드가 도입되었고, JWT 토큰 유틸리티의 일부 반환 타입이 변경되었습니다. Changes
Sequence DiagramsequenceDiagram
participant Client as Client
participant Controller as UserController
participant Service as FollowService
participant UserRepo as UserRepository
participant FollowRepo as FollowRepository
participant DB as Database
Client->>Controller: POST /api/v1/users/follow/{userId} (JWT)
Controller->>Service: follow(followId, followerId)
alt followerId == followId
Service-->>Controller: throw SameFollowException (400)
Controller-->>Client: 400 Bad Request
else existsByFollowerIdAndFolloweeId == true
Service->>FollowRepo: existsByFollowerIdAndFolloweeId(followerId, followId)
FollowRepo->>DB: SELECT ...
DB-->>FollowRepo: count > 0
Service-->>Controller: throw ExistFollowException (400)
Controller-->>Client: 400 Bad Request
else 정상 흐름
Service->>UserRepo: findById(followerId)
UserRepo->>DB: SELECT user
DB-->>UserRepo: follower User
Service->>UserRepo: findById(followId)
UserRepo->>DB: SELECT user
DB-->>UserRepo: followee User
Service->>FollowRepo: save(new Follow(follower, followee))
FollowRepo->>DB: INSERT INTO follows
DB-->>FollowRepo: OK
Service-->>Controller: 성공
Controller-->>Client: 201 Created
end
Estimated code review effort🎯 3 (보통) | ⏱️ ~30분
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (8)
src/main/java/team/wego/wegobackend/user/presentation/UserControllerDocs.java (1)
46-50: 팔로우 API 문서 시그니처는 적절하나@Valid는 불필요해 보입니다
- 인증 주체 +
userId조합으로 팔로우 요청을 받는 시그니처는 자연스럽습니다.- 다만
Long타입@PathVariable에 붙은@Valid는 별도의 제약 어노테이션이 없으면 동작하지 않으므로 제거해도 될 것 같습니다.- @AuthenticationPrincipal CustomUserDetails userDetails, - @Valid @PathVariable("userId") Long userId + @AuthenticationPrincipal CustomUserDetails userDetails, + @PathVariable("userId") Long userIdsrc/main/java/team/wego/wegobackend/user/domain/User.java (1)
72-77: User–Follow 양방향 매핑은 적절하지만 사용 패턴을 명확히 해두는 것이 좋습니다
mappedBy = "follower" / "followee"설정과 컬렉션 초기화까지 JPA 매핑 관점에서는 무난합니다.- 다만 이후에 컬렉션을 직접 조작할 계획이라면
addFollowing(Follow follow)같은 편의 메서드로 양쪽 연관관계 일관성을 보장하는 패턴을 두는 것을 고려해 볼 수 있습니다.- 또, 엔티티를 직접 JSON 응답에 노출할 경우 팔로우 컬렉션 때문에 순환 참조가 생길 수 있으므로, 지금처럼 DTO를 통해서만 외부에 노출하는 방향을 계속 유지하는 것이 안전합니다.
src/main/java/team/wego/wegobackend/user/repository/FollowRepository.java (1)
6-9: existsByFollowerIdAndFolloweeId 파라미터 이름 정합성 제안메서드 시그니처 자체는 Spring Data JPA 파생 쿼리 규칙에 맞지만, 두 번째 파라미터 이름이
followingId라서 메서드명(...FolloweeId)과 살짝 어긋납니다. 가독성을 위해 아래처럼 맞춰 두는 것을 추천합니다.- boolean existsByFollowerIdAndFolloweeId(Long followerId, Long followingId); + boolean existsByFollowerIdAndFolloweeId(Long followerId, Long followeeId);src/main/java/team/wego/wegobackend/common/exception/AppErrorCode.java (1)
29-30: 팔로우 관련 에러 코드 추가는 적절하지만 상수명 통일을 고려해볼 만합니다
- HTTP 상태와 메시지는 도메인 요구사항과 잘 맞습니다.
- 다만 이미
ALREADY_EXISTS_USER가 있는 상태에서ALREADY_EXIST_FOLLOW는 철자 패턴이 달라 약간 혼란을 줄 수 있습니다. 아직 사용처가 많지 않다면, 아래처럼 정리해 두면 나중에 읽기 더 수월할 것 같습니다.- NOT_SAME_FOLLOW(HttpStatus.BAD_REQUEST, "회원 : 자기 자신을 팔로우할 수 없습니다."), - ALREADY_EXIST_FOLLOW(HttpStatus.BAD_REQUEST, "회원 : 이미 팔로우 중입니다."), + SELF_FOLLOW_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "회원 : 자기 자신을 팔로우할 수 없습니다."), + ALREADY_EXISTS_FOLLOW(HttpStatus.BAD_REQUEST, "회원 : 이미 팔로우 중입니다."),(당연히 관련 예외 클래스와 사용처 상수명도 함께 변경해야 합니다.)
src/main/java/team/wego/wegobackend/user/presentation/UserController.java (1)
106-106: 불필요한 @Valid 애노테이션을 제거하세요.
@PathVariable Long userId에@Valid를 사용하는 것은 불필요합니다.@Valid는 복잡한 객체에 대한 Bean Validation을 트리거하는데, 단순한 Long 타입에는 효과가 없습니다.- @Valid @PathVariable("userId") Long userId + @PathVariable("userId") Long userIdsrc/main/java/team/wego/wegobackend/user/application/FollowService.java (1)
3-3: 사용하지 않는 import를 제거하세요.
import java.util.List;는 코드에서 사용되지 않습니다.-import java.util.List;src/main/java/team/wego/wegobackend/user/domain/Follow.java (2)
17-17: 사용하지 않는 import를 제거하세요.
import team.wego.wegobackend.common.security.Role;는 이 엔티티 클래스에서 사용되지 않습니다.-import team.wego.wegobackend.common.security.Role;
40-44: 생성자 파라미터 이름을 필드명과 일치시키세요.Line 41에서 두 번째 파라미터가
follow로 명명되어 있지만, Line 43에서는followee필드에 할당됩니다. 이러한 불일치는 코드 가독성과 유지보수성을 저해합니다.@Builder - public Follow(User follower, User follow) { + public Follow(User follower, User followee) { this.follower = follower; - this.followee = follow; + this.followee = followee; }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
src/main/java/team/wego/wegobackend/common/exception/AppErrorCode.java(1 hunks)src/main/java/team/wego/wegobackend/common/security/jwt/JwtTokenProvider.java(1 hunks)src/main/java/team/wego/wegobackend/user/application/FollowService.java(1 hunks)src/main/java/team/wego/wegobackend/user/domain/Follow.java(1 hunks)src/main/java/team/wego/wegobackend/user/domain/User.java(2 hunks)src/main/java/team/wego/wegobackend/user/exception/ExistFollowException.java(1 hunks)src/main/java/team/wego/wegobackend/user/exception/SameFollowException.java(1 hunks)src/main/java/team/wego/wegobackend/user/presentation/UserController.java(4 hunks)src/main/java/team/wego/wegobackend/user/presentation/UserControllerDocs.java(1 hunks)src/main/java/team/wego/wegobackend/user/repository/FollowRepository.java(1 hunks)src/test/http/user/user-api.http(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/team/wego/wegobackend/user/application/FollowService.java (4)
src/main/java/team/wego/wegobackend/user/presentation/UserController.java (1)
Slf4j(29-121)src/main/java/team/wego/wegobackend/common/security/CustomUserDetailsService.java (1)
Service(15-28)src/main/java/team/wego/wegobackend/user/exception/ExistFollowException.java (1)
ExistFollowException(6-11)src/main/java/team/wego/wegobackend/user/exception/SameFollowException.java (1)
SameFollowException(7-12)
🔇 Additional comments (5)
src/main/java/team/wego/wegobackend/common/security/jwt/JwtTokenProvider.java (1)
75-76: JWT 클레임 타입 변경 방향 괜찮습니다
userId를Long,role을String으로 직접 꺼내도록 통일된 점이 토큰 생성 로직과도 일관적이라 좋습니다. 호출부에서 컴파일 타임에 타입이 드러나서 이후 사용도 더 명확해질 것 같습니다.Also applies to: 83-85
src/test/http/user/user-api.http (1)
56-68: 팔로우 플로우 HTTP 시나리오 구성이 자연스럽습니다기존에 로그인한 유저(1번)가 새로 가입한 유저(2번)를
/users/follow/2로 팔로우하는 흐름이 잘 드러납니다. 다만 DB 초기 상태에서 1, 2번 ID가 순서대로 생성된다는 전제이므로, 반복 실행 시에는 테스트용 DB 리셋이 필요하다는 점만 인지하고 사용하면 될 것 같습니다.src/main/java/team/wego/wegobackend/user/exception/SameFollowException.java (1)
1-12: 자기 자신 팔로우 방지 예외 매핑이 명확합니다
SameFollowException이AppErrorCode.NOT_SAME_FOLLOW로 직접 매핑되어 있어 서비스 레이어에서 의도가 잘 드러납니다. 별다른 추가 로직 없이도 사용하기에 충분해 보입니다.src/main/java/team/wego/wegobackend/user/exception/ExistFollowException.java (1)
1-11: 중복 팔로우 예외 정의가 간결하고 일관적입니다
ExistFollowException이AppErrorCode.ALREADY_EXIST_FOLLOW에만 의존하도록 잘 묶여 있어서 서비스 레이어에서 의미를 파악하기 쉽습니다.SameFollowException과 패턴도 맞아 일관성이 좋습니다.src/main/java/team/wego/wegobackend/user/domain/Follow.java (1)
19-23: LGTM: Unique constraint가 적절하게 구성되었습니다.
follower_id와followee_id의 조합에 대한 unique constraint는 중복 팔로우를 데이터베이스 레벨에서 방지하여 데이터 무결성을 보장합니다. 애플리케이션 레벨의 검증(FollowService의existsByFollowerIdAndFolloweeId체크)과 함께 이중 안전장치를 제공합니다.
|
|
||
| private final UserRepository userRepository; | ||
|
|
||
| public void follow(Long followId, Long followerId) { |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
파라미터 이름이 혼란스럽습니다.
메서드 시그니처 follow(Long followId, Long followerId)에서 파라미터 순서와 이름이 직관적이지 않습니다. Line 31의 사용을 보면 followerId가 팔로우하는 사용자이고 followId가 팔로우 대상인데, 일반적인 명명 규칙에서는 행위자(follower)가 먼저 오는 것이 자연스럽습니다.
다음과 같이 시그니처를 변경하여 가독성을 개선하세요:
- public void follow(Long followId, Long followerId) {
+ public void follow(Long followerId, Long followeeId) {
- if(followerId.equals(followId)) {
+ if(followerId.equals(followeeId)) {
throw new SameFollowException();
}
- if (followRepository.existsByFollowerIdAndFolloweeId(followerId, followId)) {
+ if (followRepository.existsByFollowerIdAndFolloweeId(followerId, followeeId)) {
throw new ExistFollowException();
}
User follower = userRepository.findById(followerId)
.orElseThrow(UserNotFoundException::new);
- User follow = userRepository.findById(followId)
+ User followee = userRepository.findById(followeeId)
.orElseThrow(UserNotFoundException::new);
followRepository.save(Follow.builder()
.follower(follower)
- .follow(follow)
+ .follow(followee)
.build());
}참고: 이 변경을 적용하면 UserController의 호출부도 함께 수정해야 합니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public void follow(Long followId, Long followerId) { | |
| public void follow(Long followerId, Long followeeId) { | |
| if(followerId.equals(followeeId)) { | |
| throw new SameFollowException(); | |
| } | |
| if (followRepository.existsByFollowerIdAndFolloweeId(followerId, followeeId)) { | |
| throw new ExistFollowException(); | |
| } | |
| User follower = userRepository.findById(followerId) | |
| .orElseThrow(UserNotFoundException::new); | |
| User followee = userRepository.findById(followeeId) | |
| .orElseThrow(UserNotFoundException::new); | |
| followRepository.save(Follow.builder() | |
| .follower(follower) | |
| .follow(followee) | |
| .build()); | |
| } |
🤖 Prompt for AI Agents
In src/main/java/team/wego/wegobackend/user/application/FollowService.java
around line 26, the method signature follow(Long followId, Long followerId) is
confusing because the actor (follower) should appear first; change the signature
to place the follower parameter first and rename parameters to be explicit (for
example follow(Long followerId, Long followeeId) or follow(Long followerId, Long
targetUserId)), update all usages inside the service accordingly, and then
update the UserController call site(s) and any other callers to use the new
parameter order and names so behavior remains correct.
src/main/java/team/wego/wegobackend/user/presentation/UserController.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
src/main/java/team/wego/wegobackend/user/presentation/UserController.java (3)
10-10: 사용하지 않는 import를 제거하는 것을 고려하세요.
DeleteMapping이 import되었지만 현재 TODO 주석에서만 참조되고 실제로 사용되지 않습니다. 삭제 API를 구현할 때 추가하는 것이 좋습니다.
116-120: 향후 기능 구현을 위한 TODO 주석팔로우 삭제, 팔로우 목록 조회, 팔로워 목록 조회를 위한 TODO 주석이 있습니다. 이러한 기능들은 Issue #60의 범위를 넘어서지만 팔로우 시스템을 완성하는 데 필요합니다.
이러한 기능들의 구현을 도와드리거나 추적을 위한 새 이슈를 생성하시겠습니까?
103-114: 이전 리뷰의 치명적 이슈가 수정되었습니다!Line 109의 인자 순서가 올바르게 수정되었습니다.
followService.follow(userId, userDetails.getId())는 서비스 메서드 시그니처와 일치합니다 (팔로우 대상이 첫 번째, 팔로우하는 사용자가 두 번째).서비스 계층의 에러 처리도 완벽하게 구현되었습니다:
- 자기 자신을 팔로우하는 경우:
SameFollowException처리 ✓- 이미 팔로우한 사용자를 다시 팔로우하는 경우:
ExistFollowException처리 ✓- 존재하지 않는 팔로우 대상/팔로우자:
UserNotFoundException처리 ✓추가 개선 사항:
Line 106의
@Valid어노테이션은 제거하세요.@Valid는 제약 조건 어노테이션이 있는 복잡한 DTO 객체를 검증하는 데 사용되며, 단순 타입인Long에는 불필요합니다:- @Valid @PathVariable("userId") Long userId + @PathVariable("userId") Long userId
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/main/java/team/wego/wegobackend/user/presentation/UserController.java(4 hunks)
🔇 Additional comments (1)
src/main/java/team/wego/wegobackend/user/presentation/UserController.java (1)
24-24: 잘 구현되었습니다!
FollowServiceimport와 필드 주입이 올바르게 구현되었습니다.Also applies to: 36-36
📝 Pull Request
📌 PR 종류
해당하는 항목에 체크해주세요.
✨ 변경 내용
팔로우 등록을 위한 요청 API 추가
🔍 관련 이슈
Closes #60
🧪 테스트
변경된 기능에 대한 테스트 범위 또는 테스트 결과를 작성해주세요.
🚨 확인해야 할 사항 (Checklist)
PR을 제출하기 전에 아래 항목들을 확인해주세요.
🙋 기타 참고 사항
리뷰어가 참고하면 좋을 만한 추가 설명이 있다면 적어주세요.
Summary by CodeRabbit
새 기능
개선 사항
테스트
✏️ Tip: You can customize this high-level summary in your review settings.