Skip to content

Conversation

@daeun-han
Copy link
Member

@daeun-han daeun-han commented Jan 2, 2026

📌 PR 제목

[refactor] 마이페이지 내 게시글 조회(내가 기록하거나 저장한) API 리팩토링 및 응답 형식 변경


✨ 요약 설명

마이페이지의 '내가 작성한 게시글 조회'과 '저장한 게시글' API를 피그마 상의 새로운 응답 형식에 맞춰 리팩토링했습니다. Facade-Service 계층 분리를 통해 아키텍처 완성도를 높였고, In-memory ID 매칭 방식을 도입하여 좋아요 여부 확인 시 발생하는 N+1 문제를 해결했습니다.


🧾 변경 사항

  • 응답 DTO 및 결과 객체 리팩토링: PostCardResponseDto와 GetPostCardResult를 수정하여 지역명과 산책 시간 정보를 포함.
  • Facade-Service 역할 분리: Facade는 흐름 제어에 집중하고, 데이터 가공(단위 변환, 문자열 조합) 로직은 도메인 서비스(UserWrittenPostQueryService)로 이전하여 응집도 향상.
  • 좋아요 여부 조회 성능 최적화: 리스트 조회 시 게시글마다 좋아요 여부를 쿼리하던 방식에서, 유저가 좋아요한 ID 목록을 한 번에 조회 후 메모리에서 매칭하는 방식으로 전환 (쿼리 횟수 1회로 고정).
  • 공통 모듈 영향도 관리: 수정된 PostCardResponseDto를 공유하는 UserLikedPostQueryFacade 등 연관 모듈의 로직을 일괄 업데이트하여 빌드 안정성 확보.

📂 PR 타입

  • 기능 추가
  • 버그 수정
  • 리팩토링
  • 문서 수정
  • 기타 (직접 작성: )

🧪 테스트

  • Postman으로 API 호출 확인
  • 로컬 실행 결과 화면 캡처 포함

✅ 체크리스트

  • 깃 & 코드 컨벤션을 지켰는가?
  • Swagger/문서화는 최신 상태인가?
  • 기능 변경 시 영향 받는 모듈을 확인했는가?

💬 리뷰어에게 전달할 말

  • 이번 작업에서는 DTO 형식 변경을 넘어 '확장 가능한 아키텍처'를 고민해 보았습니다. 기존 Facade에 몰려있던 로직을 Service로 위임하였고, 특히 duration(초)을 durationMinutes(분)로 변환하거나 지역명을 조합하는 등의 비즈니스 규칙을 서비스 내에 캡슐화하여 파사드의 복잡도를 낮췄습니다.

  • 또한, 공통 DTO인 PostCardResponseDto를 수정함에 따라 사이드 이펙트가 발생한 다른 도메인 코드들도 함께 동기화 작업을 진행했습니다. 아키텍처상 서비스와 파사드의 분리 수준이 적절한지, 혹은 가공 로직의 위치에 대해 다른 의견이 있으신지 리뷰 부탁드립니다!


🔗 관련 이슈

아래 이슈번호 에 번호를 적으면 풀리퀘스트 머지 완료 시 자동으로 해당 이슈가 닫힙니다.

Summary by CodeRabbit

릴리스 노트

  • 새 기능

    • 게시물 카드에 지역명, 작성일, 소요 시간(분) 정보 표시
  • 개선 사항

    • 게시물 카드 레이아웃 간소화로 핵심 정보 강조
    • 좋아요 상태 조회 및 관리 기능 개선

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 2, 2026

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

마이페이지 산책 기록 조회 API의 응답 필드를 재설계하여 작성자와 태그 정보를 제거하고, 지역명 및 산책 시간 필드를 추가합니다. 쿼리 로직을 새로운 서비스 계층으로 이관하고, 좋아요 상태 조회를 위한 새로운 저장소 메서드를 도입합니다.

Changes

코호트 / 파일(들) 변경 요약
DTO 재설계
src/main/java/org/sopt/pawkey/backendapi/domain/Post/api/dto/response/PostCardResponseDto.java
필드 제거: createdAt, representativeImageUrl, routeId, WriterDto, descriptionTags, isPublic, isMine
필드 추가/변경: regionName, date, durationMinutes, isLiked(isLike→isLiked), imageUrl(representativeImageUrl 대체)
from(...) 메서드 업데이트, WriterDto 중첩 클래스 제거
GetPostCardResult 레코드 업데이트
src/main/java/org/sopt/pawkey/backendapi/domain/Post/application/dto/result/GetPostCardResult.java
필드 추가: regionName, durationMinutes, isLike, routeMapImageUrl
필드 제거: author(AuthorDto), categoryTags, routeId, isPublic, isMine
레코드 시그니처 변경
좋아요 조회 저장소 메서드 추가
src/main/java/org/sopt/pawkey/backendapi/domain/Post/domain/repository/PostLikeRepository.java, PostLikeRepositoryImpl.java, SpringDataPostLikeRepository.java
findLikedPostIdsByUserId(Long userId) 메서드 추가
사용자의 좋아요한 게시물 ID 목록 조회를 위한 쿼리 메서드 구현
쿼리 저장소 로직 업데이트
src/main/java/org/sopt/pawkey/backendapi/domain/Post/infra/persistence/repository/PostQueryRepositoryImpl.java
regionName 계산: 상위 지역 + 현재 지역 연결
durationMinutes 추가: 경로 지속 시간(초)을 분으로 변환
필드 제거: isPublic, author, categoryTags, routeId, isMine 매핑
GetPostCardResult 빌더 업데이트
새로운 서비스 계층 생성
src/main/java/org/sopt/pawkey/backendapi/domain/user/application/service/UserWrittenPostQueryService.java
새로운 클래스: 사용자의 작성 게시물 조회 처리
메서드: findMyPostResults(UserEntity, List<Long>), getLikedPostIds(Long userId)
지역명 조합, 지속 시간 변환, 좋아요 상태 계산 로직 포함
Facade 리팩토링
src/main/java/org/sopt/pawkey/backendapi/domain/user/application/facade/query/UserWrittenPostQueryFacade.java
직접 PostRepository 접근 → UserWrittenPostQueryService 위임으로 변경
필드: PostRepository → UserWrittenPostQueryService
인라인 DTO 구성 로직 제거, GetPostCardResult를 통한 간접 변환 방식 도입
좋아요 게시물 조회 Facade 업데이트
src/main/java/org/sopt/pawkey/backendapi/domain/user/application/facade/query/UserLikedPostQueryFacade.java
regionName 구성: 상위 지역 + 현재 지역
durationMinutes 추가: 경로 지속 시간 변환
WriterDto 및 descriptionTags 제거
PostCardResponseDto 생성자 호출 순서 및 인자 업데이트
컨트롤러 API 문서 업데이트
src/main/java/org/sopt/pawkey/backendapi/domain/user/api/controller/UserController.java
@Parameter(hidden = true) 추가: getMyLikedPosts, getMyPosts 메서드의 userId 파라미터 숨기기
Swagger 문서에서 자동 주입 파라미터 제외

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • 마이페이지 내 산책 기록 리스트 조회 API 응답 필드 최신화 (#192): 이 PR의 변경사항이 이슈의 목표를 정확히 구현하고 있으며, DTO 수정 및 Service/Repository 로직 수정 체크리스트를 모두 다룹니다.

Possibly related PRs

Suggested reviewers

  • dltnals317
  • bingle625

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 리팩토링 작업의 핵심을 명확하게 설명하고 있으며, 변경사항(마이페이지 API 리팩토링 및 응답 형식 변경)을 정확히 반영합니다.
Description check ✅ Passed PR 설명이 템플릿의 모든 필수 섹션을 포함하고 있으며, 요약, 변경사항, PR 타입, 테스트, 체크리스트, 리뷰어 의견, 관련 이슈가 모두 작성되어 있습니다.
Linked Issues check ✅ Passed PR의 모든 코드 변경사항이 #192 이슈의 요구사항(DTO 수정, Service/Repository 로직 수정)을 충족하고 있으며, 응답 필드 최신화, 불필요한 정보 제거, 필요 필드 추가 등이 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 마이페이지 API 리팩토링 범위 내에 있으며, 추가된 UserWrittenPostQueryService, DTO 구조 변경, 좋아요 여부 조회 최적화 등 모두 #192 이슈의 목표에 부합합니다.

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.

@daeun-han daeun-han self-assigned this Jan 2, 2026
@daeun-han
Copy link
Member Author

@CodeRabbit review

@coderabbitai
Copy link

coderabbitai bot commented Jan 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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 (7)
src/main/java/org/sopt/pawkey/backendapi/domain/Post/domain/repository/PostLikeRepository.java (1)

7-8: 도메인 계층에서 인프라 의존성 제거 권장

도메인 계층 인터페이스에서 Spring Data JPA 어노테이션 import(@Query, @Param)는 클린 아키텍처 원칙에 어긋납니다. 이러한 어노테이션은 인프라 계층(SpringDataPostLikeRepository)에만 사용되어야 하며, 도메인 인터페이스는 순수 메서드 시그니처만 선언해야 합니다.

🔎 도메인 인터페이스 정리 제안
 package org.sopt.pawkey.backendapi.domain.post.domain.repository;
 
 import java.util.List;
 import java.util.Optional;
 
 import org.sopt.pawkey.backendapi.domain.post.infra.persistence.entity.PostLikeEntity;
-import org.springframework.data.jpa.repository.Query;
-import org.springframework.data.repository.query.Param;
 
 public interface PostLikeRepository {
 	PostLikeEntity save(PostLikeEntity postLike);
src/main/java/org/sopt/pawkey/backendapi/domain/Post/application/dto/result/GetPostCardResult.java (1)

4-6: 사용하지 않는 import 제거 필요

ListAuthorDto import가 더 이상 사용되지 않습니다. 리팩토링 과정에서 해당 필드들이 제거되었으므로 import도 정리해주세요.

🔎 제안된 수정
 package org.sopt.pawkey.backendapi.domain.post.application.dto.result;
 
 import java.time.LocalDateTime;
-import java.util.List;
-
-import org.sopt.pawkey.backendapi.domain.user.api.dto.AuthorDto;
 
 import lombok.Builder;
src/main/java/org/sopt/pawkey/backendapi/domain/user/application/facade/query/UserLikedPostQueryFacade.java (1)

45-48: NPE 위험 및 코드 중복 존재

  1. getRegion(), getParent(), getDuration() 접근 시 null 체크가 없어 NPE 발생 가능성이 있습니다.
  2. 이 로직이 UserWrittenPostQueryService (lines 29-32)와 동일하게 중복됩니다.

별도 헬퍼 메서드나 공통 유틸로 추출하는 것을 권장합니다.

🔎 안전한 regionName 구성 예시
// 예: PostEntity 또는 RouteEntity에 헬퍼 메서드 추가
public String getFullRegionName() {
    if (getRegion() == null) return "";
    String parent = getRegion().getParent() != null 
        ? getRegion().getParent().getRegionName() + " " 
        : "";
    return parent + getRegion().getRegionName();
}

참고: PostQueryService에서는 post.getRoute().getRegion().getFullRegionName()을 사용하고 있습니다. 동일한 패턴 적용을 검토해주세요.

src/main/java/org/sopt/pawkey/backendapi/domain/user/application/facade/query/UserWrittenPostQueryFacade.java (1)

3-12: 사용하지 않는 import 정리 필요

리팩토링 후 더 이상 사용되지 않는 import들이 남아있습니다:

  • DateTimeFormatter, Comparator
  • PetEntity, PostLikeRepository, PostRepository, PostEntity
🔎 제안된 수정
 package org.sopt.pawkey.backendapi.domain.user.application.facade.query;
 
-import java.time.format.DateTimeFormatter;
-import java.util.Comparator;
 import java.util.List;
 
-import org.sopt.pawkey.backendapi.domain.pet.infra.persistence.entity.PetEntity;
 import org.sopt.pawkey.backendapi.domain.post.api.dto.response.PostCardResponseDto;
 import org.sopt.pawkey.backendapi.domain.post.application.dto.result.GetPostCardResult;
-import org.sopt.pawkey.backendapi.domain.post.domain.repository.PostLikeRepository;
-import org.sopt.pawkey.backendapi.domain.post.domain.repository.PostRepository;
-import org.sopt.pawkey.backendapi.domain.post.infra.persistence.entity.PostEntity;
 import org.sopt.pawkey.backendapi.domain.user.application.service.UserWrittenPostQueryService;
src/main/java/org/sopt/pawkey/backendapi/domain/user/application/service/UserWrittenPostQueryService.java (3)

10-10: 사용하지 않는 import

PostLikeEntity가 import되어 있지만 사용되지 않습니다.

-import org.sopt.pawkey.backendapi.domain.post.infra.persistence.entity.PostLikeEntity;

25-45: List.contains() 성능 개선 권장

Line 40의 likedPostIds.contains(post.getPostId())는 각 게시물마다 O(n) 탐색을 수행합니다. 게시물 수가 많아지면 O(n²) 성능 문제가 발생할 수 있습니다.

ListSet으로 변환하여 O(1) 조회로 개선하는 것을 권장합니다.

🔎 Set 사용 제안
+import java.util.Set;
+import java.util.HashSet;

 public List<GetPostCardResult> findMyPostResults(UserEntity user, List<Long> likedPostIds) {
+	Set<Long> likedPostIdSet = new HashSet<>(likedPostIds);
 	return postRepository.findAllByUser(user).stream()
 		.sorted(Comparator.comparing(PostEntity::getPostId).reversed())
 		.map(post -> {
 			// ...
-			.isLike(likedPostIds.contains(post.getPostId()))
+			.isLike(likedPostIdSet.contains(post.getPostId()))
 			// ...
 		})
 		.toList();
 }

26-27: 메모리 정렬 vs DB 정렬

현재 postRepository.findAllByUser(user) 후 메모리에서 정렬하고 있습니다. 데이터 양이 적다면 문제없지만, 게시물이 많아지면 DB 레벨에서 ORDER BY post_id DESC로 정렬하는 것이 더 효율적입니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 621d851 and dc7df80.

📒 Files selected for processing (10)
  • src/main/java/org/sopt/pawkey/backendapi/domain/Post/api/dto/response/PostCardResponseDto.java
  • src/main/java/org/sopt/pawkey/backendapi/domain/Post/application/dto/result/GetPostCardResult.java
  • src/main/java/org/sopt/pawkey/backendapi/domain/Post/domain/repository/PostLikeRepository.java
  • src/main/java/org/sopt/pawkey/backendapi/domain/Post/infra/persistence/repository/PostLikeRepositoryImpl.java
  • src/main/java/org/sopt/pawkey/backendapi/domain/Post/infra/persistence/repository/PostQueryRepositoryImpl.java
  • src/main/java/org/sopt/pawkey/backendapi/domain/Post/infra/persistence/repository/SpringDataPostLikeRepository.java
  • src/main/java/org/sopt/pawkey/backendapi/domain/user/api/controller/UserController.java
  • src/main/java/org/sopt/pawkey/backendapi/domain/user/application/facade/query/UserLikedPostQueryFacade.java
  • src/main/java/org/sopt/pawkey/backendapi/domain/user/application/facade/query/UserWrittenPostQueryFacade.java
  • src/main/java/org/sopt/pawkey/backendapi/domain/user/application/service/UserWrittenPostQueryService.java
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: daeun-han
Repo: PAW-KEY/PAWKEY-Backend PR: 55
File: src/main/java/org/sopt/pawkey/backendapi/domain/user/application/facade/query/UserLikedPostQueryFacade.java:55-55
Timestamp: 2025-07-13T11:54:29.024Z
Learning: 좋아요한 게시물 목록을 조회하는 API에서 isLiked 필드를 true로 하드코딩하는 것은 성능상 합리적입니다. PostLikeEntity 리스트가 이미 좋아요를 누른 게시물들만 포함하므로 별도 쿼리로 좋아요 상태를 확인하는 것은 중복이고 비효율적입니다.
📚 Learning: 2025-07-13T11:54:29.024Z
Learnt from: daeun-han
Repo: PAW-KEY/PAWKEY-Backend PR: 55
File: src/main/java/org/sopt/pawkey/backendapi/domain/user/application/facade/query/UserLikedPostQueryFacade.java:55-55
Timestamp: 2025-07-13T11:54:29.024Z
Learning: 좋아요한 게시물 목록을 조회하는 API에서 isLiked 필드를 true로 하드코딩하는 것은 성능상 합리적입니다. PostLikeEntity 리스트가 이미 좋아요를 누른 게시물들만 포함하므로 별도 쿼리로 좋아요 상태를 확인하는 것은 중복이고 비효율적입니다.

Applied to files:

  • src/main/java/org/sopt/pawkey/backendapi/domain/user/application/facade/query/UserLikedPostQueryFacade.java
  • src/main/java/org/sopt/pawkey/backendapi/domain/Post/domain/repository/PostLikeRepository.java
🧬 Code graph analysis (1)
src/main/java/org/sopt/pawkey/backendapi/domain/user/application/service/UserWrittenPostQueryService.java (2)
src/main/java/org/sopt/pawkey/backendapi/domain/user/application/facade/query/UserWrittenPostQueryFacade.java (1)
  • Service (23-42)
src/main/java/org/sopt/pawkey/backendapi/domain/user/application/service/UserLikedPostQueryService.java (1)
  • Service (12-22)
🔇 Additional comments (11)
src/main/java/org/sopt/pawkey/backendapi/domain/Post/infra/persistence/repository/PostLikeRepositoryImpl.java (1)

39-42: LGTM! 효율적인 좋아요 조회 메서드 추가

사용자가 좋아요한 게시글 ID 목록을 반환하는 메서드가 올바르게 구현되었습니다. JPA 리포지토리에 위임하는 방식이 기존 패턴과 일관되며, N+1 문제 해결을 위한 인메모리 매칭 전략을 지원합니다.

src/main/java/org/sopt/pawkey/backendapi/domain/Post/domain/repository/PostLikeRepository.java (1)

19-19: 메서드 시그니처 확인 완료

메서드 선언이 명확하며 반환 타입과 파라미터가 적절합니다.

src/main/java/org/sopt/pawkey/backendapi/domain/Post/infra/persistence/repository/SpringDataPostLikeRepository.java (1)

34-35: LGTM! N+1 문제 해결을 위한 효율적인 쿼리

필요한 게시글 ID만 조회하는 효율적인 JPQL 쿼리입니다. 전체 엔티티를 조회하지 않고 ID만 가져오므로 성능이 최적화되며, N+1 문제 해결을 위한 인메모리 매칭 전략을 지원합니다.

src/main/java/org/sopt/pawkey/backendapi/domain/Post/infra/persistence/repository/PostQueryRepositoryImpl.java (2)

127-127: 산책 시간 계산 방식 검증 필요

정수 나눗셈으로 인해 소수점이 버려집니다(예: 90초 → 1분). 반올림이 필요한지 확인해 주세요.

현재 구현: (int)(duration / 60) → 버림 처리
대안: (int)Math.round(duration / 60.0) → 반올림 처리

UI에 표시되는 산책 시간이 버림으로 처리되어도 사용자 경험에 문제가 없는지 검토가 필요합니다.


129-138: 리팩토링된 DTO 구조로 올바르게 변환

regionName, durationMinutes 필드 추가 및 불필요한 필드 제거가 피그마 디자인 명세에 맞춰 정확히 구현되었습니다. isLike 판단도 인메모리 ID 매칭으로 효율적으로 처리됩니다.

src/main/java/org/sopt/pawkey/backendapi/domain/user/api/controller/UserController.java (1)

36-36: LGTM! Swagger 문서에서 인증 파라미터 적절히 숨김 처리

JWT 토큰에서 추출되는 userId 파라미터를 Swagger 문서에서 숨기는 것은 올바른 구현입니다. API 사용자에게 혼란을 주지 않으며, 내부 인증 메커니즘을 문서에 노출하지 않는 보안상 좋은 관행입니다.

Also applies to: 83-83, 111-111

src/main/java/org/sopt/pawkey/backendapi/domain/Post/application/dto/result/GetPostCardResult.java (1)

10-19: Record 구조 적절함

새로운 응답 형식에 맞게 필드가 잘 정리되었습니다. @Builder 패턴을 사용하여 가독성 있게 객체를 생성할 수 있습니다.

src/main/java/org/sopt/pawkey/backendapi/domain/user/application/facade/query/UserLikedPostQueryFacade.java (1)

56-58: isLiked = true 하드코딩 및 이미지 URL 접근

isLiked = true 하드코딩은 좋아요한 게시물 목록이므로 성능상 합리적입니다. (Based on learnings)

다만, Line 57의 getTrackingImage().getImageUrl() 접근 시 getTrackingImage()가 null일 경우 NPE가 발생할 수 있습니다.

🔎 null-safe 접근 제안
-				post.getRoute().getTrackingImage().getImageUrl()
+				post.getRoute().getTrackingImage() != null 
+					? post.getRoute().getTrackingImage().getImageUrl() 
+					: null
src/main/java/org/sopt/pawkey/backendapi/domain/user/application/facade/query/UserWrittenPostQueryFacade.java (1)

31-41: Facade-Service 분리 잘 적용됨

Facade가 흐름 제어만 담당하고 비즈니스 로직은 UserWrittenPostQueryService에 위임하는 구조가 잘 적용되었습니다. PR 목표에 부합합니다.

src/main/java/org/sopt/pawkey/backendapi/domain/user/application/service/UserWrittenPostQueryService.java (1)

47-49: getLikedPostIds 메서드 적절함

좋아요한 게시글 ID를 한 번에 조회하여 N+1 문제를 해결하는 In-memory ID 매칭 방식이 PR 목표대로 잘 구현되었습니다.

src/main/java/org/sopt/pawkey/backendapi/domain/Post/api/dto/response/PostCardResponseDto.java (1)

10-30: DTO 구조 및 매핑 로직 적절함

새로운 응답 형식에 맞게 lean한 DTO로 잘 리팩토링되었습니다. from() 메서드를 통한 매핑 로직이 깔끔하고, 날짜 포맷팅(yyyy/MM/dd)이 UserLikedPostQueryFacade와 일관성 있게 적용되었습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REFACTOR] 내가 기록하거나 저장한 산책 루트 리스트 조회 API (마이페이지)

2 participants