[Hotfix] 댓글 생성시 댓글 조회와 같은 형식으로 response 반환 #238
Conversation
Walkthrough댓글 생성 API의 응답을 CommentIdResponse에서 CommentCreateResponse로 변경하고, 서비스/매퍼/포트/리포지토리에 단건 조회 및 매핑 로직을 추가하여 생성 직후 완전한 댓글 정보(루트/대댓글 포함)를 반환하도록 흐름을 확장했습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor Client
participant Controller as CommentCommandController
participant Service as CommentCreateService
participant CmdPort as CommentCommandPort
participant QPort as CommentQueryPort
participant LikePort as CommentLikeQueryPort
participant Mapper as CommentQueryMapper
Client->>Controller: POST /comments
Controller->>Service: createComment(command)
alt Reply comment
Service->>CmdPort: save(reply)
Service->>QPort: findRootCommentById(rootId)
Service->>LikePort: existsLike(userId, rootId)
Service->>QPort: findChildCommentById(rootId, savedReplyId)
Service->>Mapper: toRootCommentResponseWithChildren(parentDto, childDto, isLiked, userId)
Mapper-->>Service: CommentCreateResponse
else Root comment
Service->>CmdPort: save(root)
Service->>QPort: findRootCommentById(savedRootId)
Service->>Mapper: toRoot(rootDto, isLike=false, userId)
Mapper-->>Service: CommentCreateResponse
end
Service-->>Controller: CommentCreateResponse
Controller-->>Client: BaseResponse<CommentCreateResponse>
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes(해당 없음) Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 4
🔭 Outside diff range comments (1)
src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java (1)
82-87: 부모 댓글 상태(ACTIVE) 및 루트 댓글 여부 검증 로직 추가 필요현재
commentCommandPort.findById()는 삭제(INACTIVE)된 댓글도 조회하며,Comment.validateReplyCommentCreate()에는 상태 및 깊이 검증이 빠져 있어
- 비활성(삭제)된 댓글에도 답글 생성 가능
- 루트 댓글이 아닌(대댓글에 대한 답글) 경우 서비스/쿼리 로직 오류 발생
아래 위치에 검증 로직을 추가해주세요.
• src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java#createCommentDomain
findById()직후에parentComment.getStatus() == StatusType.ACTIVE검증- 루트 댓글만 답글 가능하도록
parentComment.getParentCommentId() == null검증• src/main/java/konkuk/thip/comment/domain/Comment.java#validateReplyCommentCreate
- 상태 및 깊이 검증을 도메인 레벨로 이관하여 재사용성 확보
예시 (서비스 레벨):
// 3-1. (답글일 경우) 부모 댓글 조회 Comment parent = commentCommandPort.findById(command.parentId()) .orElseThrow(...); if (parent.getStatus() != StatusType.ACTIVE) { throw new InvalidStateException(..., new IllegalArgumentException("비활성된 댓글에는 답글을 달 수 없습니다.")); } if (parent.getParentCommentId() != null) { throw new InvalidStateException(..., new IllegalArgumentException("루트 댓글에만 답글을 달 수 있습니다.")); }위 두 검증을 추가하면 비활성 댓글 방지 및 허용 깊이를 일관되게 관리할 수 있습니다.
🧹 Nitpick comments (11)
src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentCreateResponse.java (3)
16-18: reply DTO에 isDeleted 누락 가능성 — 조회 응답 포맷과의 정합성 확인 필요PR 목표가 “댓글 조회와 동일한 형식”인 만큼, 루트에는
isDeleted가 있으나ReplyCommentCreateDto에는 없는 점이 프론트에서 공통 렌더러를 사용할 때 차이를 유발할 수 있습니다. 조회 응답의 reply 항목에isDeleted가 존재한다면 여기도 포함해 주세요.필요 시 아래와 같이 추가를 제안합니다(매퍼/서비스 매핑도 함께 보완 필요):
public record ReplyCommentCreateDto( Long commentId, String parentCommentCreatorNickname, Long creatorId, String creatorProfileImageUrl, String creatorNickname, String aliasName, String aliasColor, String postDate, // 댓글 작성 시각 (~ 전 형식) String content, int likeCount, boolean isLike, + boolean isDeleted, boolean isWriter ) {}Also applies to: 20-33
18-18: replyList null 방지(빈 리스트 보장) 요청레코드 자체로는 문제없지만, 매퍼/서비스에서
replyList를 null이 아닌 빈 리스트로 반환하도록 보장해 주세요. 공통 렌더러에서 NPE 또는 분기 처리를 줄일 수 있습니다.
14-15: likeCount 타입 검토(선택)좋아요 수가 커질 가능성이 있거나 기존 조회 응답이
long/Integer를 사용한다면 타입 정합성 확인을 권장합니다. 현재로도 기능상 문제는 없으며 선택사항입니다.src/main/java/konkuk/thip/comment/application/port/out/CommentQueryPort.java (2)
19-21: 단건 조회용 Port 추가는 목적에 부합하나, NotFound 처리 방식 명확화 필요반환 타입이
CommentQueryDto(nullable)인지, 조회 실패 시 예외를 던지는지 규약을 명시해 주세요. NPE/500 방지 차원에서 Optional 반환 또는 @nullable 주석, 혹은 Javadoc으로 계약을 분명히 하는 것을 권장합니다.
21-21: 사소한 스타일: 불필요한 공백 제거콤마 앞의 공백을 제거하면 코드 스타일 일관성이 좋아집니다.
- CommentQueryDto findChildCommentById(Long rootCommentId , Long replyCommentId); + CommentQueryDto findChildCommentById(Long rootCommentId, Long replyCommentId);src/main/java/konkuk/thip/comment/adapter/in/web/CommentCommandController.java (1)
37-50: REST 관점의 상태코드/스웨거 문서 검토(선택)생성 API인 만큼 201 Created와 Location 헤더 노출을 고려할 수 있습니다. 프로젝트에서 일괄 200 OK를 사용한다면 현재도 무방하나, 스웨거 응답 스키마가 CommentCreateResponse로 반영되었는지(예: @apiresponse 등)만 확인 부탁드립니다.
src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepository.java (2)
18-21: 메서드 명명 일관성 정리 제안(Port와 ById 접미사 정렬)Port는
findRootCommentById/findChildCommentById인데, Repository는findRootCommentId/findChildCommentId로 ById가 빠져 있어 반환값이 ID인지 DTO인지 혼동을 줄 수 있습니다. 또한 파라미터 명도 루트/자식 의미가 드러나도록 정리되면 가독성이 좋아집니다.다음과 같이 변경을 제안드립니다(구현/어댑터도 함께 수정 필요):
- CommentQueryDto findRootCommentId(Long commentId); - CommentQueryDto findChildCommentId(Long rootCommentId, Long commentId); + CommentQueryDto findRootCommentById(Long rootCommentId); + CommentQueryDto findChildCommentById(Long rootCommentId, Long childCommentId);
18-21: 조회 실패 시 처리 계약 명확화(Optional 또는 예외 규약)Port와 동일하게, 존재하지 않을 때 null 반환인지, 예외인지가 명확하면 상위 서비스에서 방어 로직을 통일할 수 있습니다. Optional 사용 또는 Javadoc으로 계약을 문서화하는 것을 권장합니다.
src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentQueryPersistenceAdapter.java (1)
47-55: ID 단건 조회 추가 자체는 적절. 다만 null 반환 시 상위 레이어 NPE 위험이 있습니다.두 메서드가 JPA/Querydsl 쿼리의 fetchOne 결과를 그대로 반환하므로, 데이터가 없거나 조인 조건으로 누락될 경우 null이 전달됩니다. 현재 create 흐름(서비스)에서 즉시 DTO 필드를 접근하므로 NPE 가능성이 있습니다. 최소한 서비스 레이어에서 null 체크 후 도메인 예외를 던지거나, 포트/어댑터에서 Optional 반환 또는 NotFound 예외로 변환하는 방식을 고려해주세요. 또한 메서드명/위임 대상의 네이밍이
findRootCommentById↔findRootCommentId로 어긋납니다(의미상 root 제약이 없다면findCommentById류로 통일 권장).src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepositoryImpl.java (1)
190-216: 메서드 명/의미 재검토 권장: ‘findRootCommentId’가 루트 제약 없이 동작합니다.현재 WHERE절에
parent is null제약이 없어 “루트만” 찾는 의미가 보장되지 않습니다. 실제 의도가 “루트 댓글만”이라면 필터를 추가하거나, 의도가 “ID로 단건 조회”라면 메서드명을findCommentById로 변경해 혼동을 줄여주세요. (서비스에서 이 메서드를 parentId에 사용 중이라, 루트 제약 추가 시 상위 로직도 함께 정리되어야 합니다.)src/main/java/konkuk/thip/comment/application/mapper/CommentQueryMapper.java (1)
49-55: 답글 생성 매핑에서 필요한 필드(예: parentId, parentNickname) 누락 여부 확인
QCommentQueryDto의 child 프로젝션에는 부모 ID/부모 닉네임이 포함됩니다.ReplyCommentCreateDto에 해당 필드가 존재한다면 현재 매핑에 누락되어 응답 정합성이 깨질 수 있습니다(unmappedTargetPolicy = IGNORE라 컴파일 시 경고 없이 넘어갑니다). 필요한 경우 명시 매핑을 추가해 주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (10)
src/main/java/konkuk/thip/comment/adapter/in/web/CommentCommandController.java(2 hunks)src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentCreateResponse.java(1 hunks)src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentIdResponse.java(0 hunks)src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentQueryPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepository.java(1 hunks)src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepositoryImpl.java(1 hunks)src/main/java/konkuk/thip/comment/application/mapper/CommentQueryMapper.java(4 hunks)src/main/java/konkuk/thip/comment/application/port/in/CommentCreateUseCase.java(1 hunks)src/main/java/konkuk/thip/comment/application/port/out/CommentQueryPort.java(1 hunks)src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java(4 hunks)
💤 Files with no reviewable changes (1)
- src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentIdResponse.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-03T03:05:05.031Z
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
Applied to files:
src/main/java/konkuk/thip/comment/application/port/out/CommentQueryPort.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (6)
src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentCreateResponse.java (1)
5-19: 생성 응답을 조회 응답 포맷에 맞춘 레코드 설계, 전반적으로 적절합니다필요한 필드가 균형 있게 포함되어 있고, FE 재랜더링 용도로 충분해 보입니다. 중첩 Reply DTO로 계층을 표현하는 것도 명확합니다.
src/main/java/konkuk/thip/comment/application/port/in/CommentCreateUseCase.java (1)
3-8: createComment 반환 타입 변경점 정상 반영됨–
CommentIdResponse또는BaseResponse<CommentIdResponse>사용 흔적이 전혀 없습니다.
– 모든createComment호출부가CommentCreateResponse반환으로 올바르게 업데이트되었습니다.고생 많으셨습니다!
src/main/java/konkuk/thip/comment/adapter/in/web/CommentCommandController.java (1)
45-50: 생성 API가 즉시 재랜더링 가능한 응답을 반환하도록 변경된 점, 적합합니다컨트롤러는 UseCase 결과를 그대로 감싸서 반환하므로 간결하며, FE 요구사항(조회 포맷과 동일)과 일치합니다.
src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java (1)
39-39: 생성 UseCase의 반환타입 전환(L → CommentCreateResponse) 잘 반영되었습니다.컨트롤러까지의 응답 포맷 정합성을 위해 필요한 변경으로 보이며, 트랜잭션 경계 내에서 저장→조회→매핑 순서도 적절합니다.
src/main/java/konkuk/thip/comment/application/mapper/CommentQueryMapper.java (2)
31-39: 생성 경로 전용 루트 매핑 추가(LGTM).생성 직후 ‘좋아요 여부’ 입력을 boolean으로 직접 주입하는 선택이 합리적입니다. replyList를 가변 리스트로 초기화한 점도 create 후 append 시나리오에 적합합니다.
81-88: 부분 재랜더링 전략에 부합. 단, ‘루트’ 식별만 정확하면 충분생성 응답에서 루트 1개 + 신규 답글 1개만 담아 클라이언트가 머지하도록 한 전략은 합리적입니다. 위에서 언급한대로 루트 식별(대댓글 시 루트 승격)만 보장되면 문제 없습니다.
.../java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepositoryImpl.java
Show resolved
Hide resolved
.../java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepositoryImpl.java
Show resolved
Hide resolved
src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java
Show resolved
Hide resolved
| // 5. 매퍼로 DTO 변환 후 반환 | ||
| if (command.isReplyRequest()) { | ||
| // 부모 댓글 조회 | ||
| CommentQueryDto parentCommentDto = commentQueryPort.findRootCommentById(command.parentId()); | ||
| // 사용자 부모 댓글 좋아요 여부 조회 | ||
| boolean isLikedParentComment = commentLikeQueryPort.isLikedCommentByUser(command.userId(),parentCommentDto.commentId()); | ||
|
|
||
| CommentQueryDto savedReplyCommentDto = commentQueryPort.findChildCommentById(command.parentId(), savedCommentId); | ||
| return commentQueryMapper.toRootCommentResponseWithChildren(parentCommentDto, savedReplyCommentDto,isLikedParentComment,command.userId()); | ||
| } else { | ||
| CommentQueryDto savedCommentDto = commentQueryPort.findRootCommentById(savedCommentId); | ||
| return commentQueryMapper.toRoot(savedCommentDto, false, command.userId()); | ||
| } |
#️⃣ 연관된 이슈
📝 작업 내용
📸 스크린샷
💬 리뷰 요구사항
📌 PR 진행 시 이러한 점들을 참고해 주세요
Summary by CodeRabbit
신규 기능
버그 수정