Skip to content

[feat] 내 팔로잉 리스트 조회 api 개발#94

Merged
buzz0331 merged 13 commits into
developfrom
feat/#90-my-following-list
Jul 22, 2025
Merged

[feat] 내 팔로잉 리스트 조회 api 개발#94
buzz0331 merged 13 commits into
developfrom
feat/#90-my-following-list

Conversation

@buzz0331
Copy link
Copy Markdown
Contributor

@buzz0331 buzz0331 commented Jul 21, 2025

#️⃣ 연관된 이슈

closes #90

📝 작업 내용

이번 PR은 “팔로워 리스트 조회 API #77”과 로직 구조가 매우 유사하여, 리팩토링 중심으로 작업을 진행하였습니다. 주요 변경 사항은 다음과 같습니다:

  1. CursorBasedList 도입
    대부분의 조회 기능이 무한 스크롤 기반으로 동작하기 때문에, 커서 기반 페이징 방식이 적절하다고 판단하였습니다. 이에 따라 커서 기반 페이징 처리를 표준화하고자 CursorBasedList를 도입하였으며, 관련 상세 내용은 노션에 정리해두었으니 참고 부탁드립니다.
  2. QueryProjection 기반 공통 DTO 적용
    Querydsl의 @QueryProjection을 활용하면 공통 응답 DTO를 생성하는 데 유용하다는 점을 확인하여, 이번에 유사한 조회 로직들에 적용해보았습니다.
    FollowQueryDto는 팔로워 및 팔로잉 사용자 정보를 모두 담을 수 있는 구조로 설계되어 있으며, 응답 형식의 차이는 주로 followerCount 포함 여부입니다.
    따라서, 쿼리 결과는 FollowQueryDto로 통일하여 받아오고, 서비스 계층에서 필요한 응답 DTO로 변환하는 방식으로 구성해 조회 로직의 재사용성과 응집도를 높였습니다.
  3. MapStruct 도입
    서비스 계층에서 FollowQueryDto를 실제 응답 DTO로 변환하는 작업을 명확하게 분리하고자 MapStruct를 도입하였습니다.
    기존에는 조회 DTO → 응답 DTO 변환 책임이 응답 DTO나 서비스 클래스 중 한 곳에 맡아 혼용되고 있었고, 이는 의존성 침해 및 서비스 로직 복잡도 증가라는 단점이 있었습니다.
    이러한 문제를 해소하고자 명확한 매핑 책임을 가진 Mapper 클래스를 분리하여 도입하였고, 이를 통해 매핑 로직을 간결하고 명시적으로 관리할 수 있게 되었습니다.

앞으로 다른 조회 API 개발 시에도, 공통 필드는 QueryDto로 분리하고, 매핑에는 MapStruct를 활용하는 구조를 적용하면 유지보수성과 일관성을 높일 수 있을 것으로 기대합니다. 감사합니다.

📸 스크린샷

💬 리뷰 요구사항

현재 조회 dto(FollowQueryDto) -> 응답 dto(UserFollowingResponse)를 파싱하는 매퍼 (FollowMapper)를 application하위에 mapper 패키지를 만들어서 두었는데 이 위치에 대한 부분도 한번 확인 부탁드릴게요!

📌 PR 진행 시 이러한 점들을 참고해 주세요

* P1 : 꼭 반영해 주세요 (Request Changes) - 이슈가 발생하거나 취약점이 발견되는 케이스 등
* P2 : 반영을 적극적으로 고려해 주시면 좋을 것 같아요 (Comment)
* P3 : 이런 방법도 있을 것 같아요~ 등의 사소한 의견입니다 (Chore)

Summary by CodeRabbit

  • 신규 기능
    • 사용자의 팔로워 및 팔로잉 목록을 커서 기반 페이지네이션으로 조회할 수 있는 API가 추가되었습니다.
    • 내 팔로잉 목록을 조회하는 새로운 엔드포인트가 제공됩니다.
    • 팔로워/팔로잉 목록 조회 시 페이지 크기를 지정할 수 있습니다.
  • API 변경
    • 팔로워 응답 필드명이 변경되고, 일부 불필요한 필드가 제거되었습니다.
    • 팔로워/팔로잉 응답 구조가 통일되고, 커서 및 마지막 페이지 여부를 명확히 제공합니다.
  • 문서화
    • 사용자 관련 컨트롤러 메소드에 JavaDoc 주석이 추가되어 API 이해도가 향상되었습니다.
  • 테스트
    • 팔로잉 목록 조회 API에 대한 통합 테스트가 추가되었습니다.
    • 팔로워 목록 조회 테스트가 변경된 응답 구조를 반영하도록 수정되었습니다.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jul 21, 2025

"""

Walkthrough

본 변경사항은 팔로워/팔로잉 목록 조회 기능에 커서 기반 페이징을 도입하고, 팔로잉(내가 팔로우한 유저) 리스트 API를 신규 추가합니다. 이를 위해 공통 유틸, DTO, 매퍼, 서비스, 포트, 어댑터, 컨트롤러 등 전반적인 구조가 리팩토링 및 확장되었으며, 관련 테스트 코드도 추가 및 수정되었습니다.

Changes

파일/경로 요약 변경 요약
build.gradle MapStruct 의존성 및 annotationProcessor 추가
.../common/util/CursorBasedList.java, .../common/util/CursorExtractor.java 커서 기반 페이징 유틸 및 커서 추출 함수형 인터페이스 신규 추가
.../user/adapter/in/web/UserCommandController.java, .../user/adapter/out/jpa/FollowingJpaEntity.java JavaDoc 및 필드 설명 주석 추가 (로직 변경 없음)
.../user/adapter/in/web/UserQueryController.java 팔로워 조회 API 페이징 파라미터 추가, 팔로잉(내가 팔로우한 유저) 조회 API 신규 추가, JavaDoc 보강
.../user/adapter/in/web/response/UserFollowersResponse.java 필드명 변경(followerListfollowers), 불필요 필드 제거, nextCursor 타입 변경, 불필요 import 제거
.../user/adapter/in/web/response/UserFollowingResponse.java 팔로잉 응답 레코드 및 하위 레코드 신규 추가
.../user/adapter/out/persistence/FollowingQueryPersistenceAdapter.java 팔로워/팔로잉 조회 메서드 커서 기반 페이징 및 DTO 반환 구조로 리팩토링, 팔로잉 조회 메서드 추가
.../user/adapter/out/persistence/repository/following/FollowingQueryRepository.java,
.../FollowingQueryRepositoryImpl.java
팔로워/팔로잉 DTO 조회 메서드로 변경 및 추가, 쿼리 로직 리팩토링(동적 join, DTO 프로젝션)
.../user/application/mapper/FollowDtoMapper.java MapStruct 기반 팔로워/팔로잉 DTO→응답 매핑 인터페이스 신규 추가
.../user/application/port/in/UserGetFollowUsecase.java 팔로워/팔로잉 조회 유스케이스 인터페이스 신규 추가
.../user/application/port/in/UserGetFollowersUsecase.java,
.../user/application/service/following/UserGetFollowersService.java
기존 팔로워 조회 유스케이스/서비스 삭제
.../user/application/port/out/FollowingQueryPort.java 팔로워/팔로잉 조회 메서드 커서 기반 반환 구조로 변경 및 팔로잉 조회 메서드 추가
.../user/application/port/out/dto/FollowQueryDto.java 팔로워/팔로잉 공통 DTO 신규 추가(QueryDSL 프로젝션 지원)
.../user/application/service/following/UserGetFollowService.java 팔로워/팔로잉 커서 기반 페이징 서비스 신규 추가
.../user/adapter/in/web/response/UserFollowersResponse.java 응답 구조 변경(필드명, 타입 등)
.../user/adapter/in/web/UserGetFollowersApiTest.java 트랜잭션 적용, DB 클린업 방식 변경, 응답 구조 변경에 따른 검증 코드 수정
.../user/adapter/in/web/UserGetFollowingApiTest.java 팔로잉 커서 기반 페이징 API 통합 테스트 신규 추가

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant UserQueryController
    participant UserGetFollowService
    participant FollowingQueryPort
    participant FollowDtoMapper

    Client->>UserQueryController: GET /users/{id}/followers?cursor&size
    UserQueryController->>UserGetFollowService: getUserFollowers(userId, cursor, size)
    UserGetFollowService->>FollowingQueryPort: getFollowersByUserId(userId, cursor, size)
    FollowingQueryPort-->>UserGetFollowService: CursorBasedList<FollowQueryDto>
    UserGetFollowService->>FollowDtoMapper: toFollowerList(FollowQueryDto)
    FollowDtoMapper-->>UserGetFollowService: UserFollowersResponse.Follower
    UserGetFollowService-->>UserQueryController: UserFollowersResponse
    UserQueryController-->>Client: BaseResponse<UserFollowersResponse>

    Client->>UserQueryController: GET /users/my/following?cursor&size
    UserQueryController->>UserGetFollowService: getMyFollowing(userId, cursor, size)
    UserGetFollowService->>FollowingQueryPort: getFollowingByUserId(userId, cursor, size)
    FollowingQueryPort-->>UserGetFollowService: CursorBasedList<FollowQueryDto>
    UserGetFollowService->>FollowDtoMapper: toFollowingList(FollowQueryDto)
    FollowDtoMapper-->>UserGetFollowService: UserFollowingResponse.Following
    UserGetFollowService-->>UserQueryController: UserFollowingResponse
    UserQueryController-->>Client: BaseResponse<UserFollowingResponse>
Loading

Estimated code review effort

4 (60–120 minutes)

Possibly related PRs

Poem

( ˘ ³˘)♥
커서 따라 hop-hop, 토끼가 뛰네
팔로워도 팔로잉도 줄 맞춰 오네
한 페이지, 두 페이지 넘기며
MapStruct와 함께 변신한 DTO
토끼 서버엔 페이징의 봄바람
API도 테스트도 모두 새단장!

(。•̀ᴗ-)✧
"""


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 596c918 and b61097a.

📒 Files selected for processing (1)
  • src/main/java/konkuk/thip/user/application/port/out/dto/FollowQueryDto.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/konkuk/thip/user/application/port/out/dto/FollowQueryDto.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
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@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 (4)
src/main/java/konkuk/thip/user/application/port/out/dto/FollowQueryDto.java (1)

21-21: followerCount null 처리를 명시적으로 개선해주세요.

주석으로만 처리하는 대신, 명시적으로 null 허용 여부를 나타내는 것이 좋겠습니다.

다음과 같이 개선할 수 있습니다:

-//        Assert.notNull(followerCount, "followerCount must not be null"); // 내 팔로잉 목록 조회에서는 필요 x
+        // followerCount는 팔로잉 목록 조회 시 null 허용

또는 @Nullable 어노테이션을 필드에 추가하여 명시적으로 표현할 수도 있습니다.

src/main/java/konkuk/thip/common/util/CursorBasedList.java (1)

10-15: 효율적인 커서 기반 페이지네이션 유틸리티입니다.

페이지네이션 로직이 정확하고 재사용 가능한 구조로 잘 설계되어 있습니다. 다만 엣지 케이스 처리를 고려해보세요.

빈 리스트나 size가 0인 경우에 대한 방어 로직을 추가할 수 있습니다:

 public static <T> CursorBasedList<T> of(List<T> queryList, int size, CursorExtractor<T> extractor) {
+    if (queryList == null || queryList.isEmpty() || size <= 0) {
+        return new CursorBasedList<>(List.of(), null, false);
+    }
     boolean hasNext = queryList.size() > size;
     List<T> contents = hasNext ? queryList.subList(0, size) : queryList;
     String nextCursor = hasNext ? extractor.extractCursor(contents.get(size - 1)) : null;
     return new CursorBasedList<>(contents, nextCursor, hasNext);
 }
src/main/java/konkuk/thip/user/application/service/following/UserGetFollowService.java (1)

47-50: 메서드명과 파라미터 불일치

메서드명 getMyFollowing은 현재 인증된 사용자의 팔로잉 목록을 조회하는 것을 암시하지만, 실제로는 userId 파라미터를 받아 해당 사용자의 팔로잉을 조회합니다. PR 목표에 따르면 "내 팔로잉 리스트 조회"가 목적이므로, 이것이 의도된 설계인지 확인이 필요합니다.

만약 실제로 인증된 사용자만을 위한 메서드라면, 메서드명을 getUserFollowing으로 변경하거나, 컨트롤러 레벨에서 인증된 사용자의 ID만 전달되도록 보장하는 것이 좋습니다.

src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.java (1)

21-42: 중복 코드 리팩토링 제안

두 메서드가 거의 동일한 로직을 가지고 있습니다. 커서 파싱과 CursorBasedList 생성 로직을 공통 private 메서드로 추출하면 유지보수성이 향상될 것입니다.

+    private LocalDateTime parseCursor(String cursor) {
+        return cursor != null && !cursor.isBlank() ? DateUtil.parseDateTime(cursor) : null;
+    }
+
     @Override
     public CursorBasedList<FollowQueryDto> getFollowersByUserId(Long userId, String cursor, int size) {
-        LocalDateTime cursorVal = cursor != null && !cursor.isBlank() ? DateUtil.parseDateTime(cursor) : null;
+        LocalDateTime cursorVal = parseCursor(cursor);
         List<FollowQueryDto> followerDtos = followingJpaRepository.findFollowerDtosByUserIdBeforeCreatedAt(
                 userId,
                 cursorVal,
                 size
         );

         return CursorBasedList.of(followerDtos, size, followerDto -> followerDto.createdAt().toString());
     }

     @Override
     public CursorBasedList<FollowQueryDto> getFollowingByUserId(Long userId, String cursor, int size) {
-        LocalDateTime cursorVal = cursor != null && !cursor.isBlank() ? DateUtil.parseDateTime(cursor) : null;
+        LocalDateTime cursorVal = parseCursor(cursor);
         List<FollowQueryDto> followingDtos = followingJpaRepository.findFollowingDtosByUserIdBeforeCreatedAt(
                 userId,
                 cursorVal,
                 size
         );

         return CursorBasedList.of(followingDtos, size, followingDto -> followingDto.createdAt().toString());
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb2f1a4 and 596c918.

📒 Files selected for processing (20)
  • build.gradle (1 hunks)
  • src/main/java/konkuk/thip/common/util/CursorBasedList.java (1 hunks)
  • src/main/java/konkuk/thip/common/util/CursorExtractor.java (1 hunks)
  • src/main/java/konkuk/thip/user/adapter/in/web/UserCommandController.java (2 hunks)
  • src/main/java/konkuk/thip/user/adapter/in/web/UserQueryController.java (2 hunks)
  • src/main/java/konkuk/thip/user/adapter/in/web/response/UserFollowersResponse.java (2 hunks)
  • src/main/java/konkuk/thip/user/adapter/in/web/response/UserFollowingResponse.java (1 hunks)
  • src/main/java/konkuk/thip/user/adapter/out/jpa/FollowingJpaEntity.java (1 hunks)
  • src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.java (2 hunks)
  • src/main/java/konkuk/thip/user/adapter/out/persistence/repository/following/FollowingQueryRepository.java (2 hunks)
  • src/main/java/konkuk/thip/user/adapter/out/persistence/repository/following/FollowingQueryRepositoryImpl.java (2 hunks)
  • src/main/java/konkuk/thip/user/application/mapper/FollowDtoMapper.java (1 hunks)
  • src/main/java/konkuk/thip/user/application/port/in/UserGetFollowUsecase.java (1 hunks)
  • src/main/java/konkuk/thip/user/application/port/in/UserGetFollowersUsecase.java (0 hunks)
  • src/main/java/konkuk/thip/user/application/port/out/FollowingQueryPort.java (1 hunks)
  • src/main/java/konkuk/thip/user/application/port/out/dto/FollowQueryDto.java (1 hunks)
  • src/main/java/konkuk/thip/user/application/service/following/UserGetFollowService.java (1 hunks)
  • src/main/java/konkuk/thip/user/application/service/following/UserGetFollowersService.java (0 hunks)
  • src/test/java/konkuk/thip/user/adapter/in/web/UserGetFollowersApiTest.java (5 hunks)
  • src/test/java/konkuk/thip/user/adapter/in/web/UserGetFollowingApiTest.java (1 hunks)
💤 Files with no reviewable changes (2)
  • src/main/java/konkuk/thip/user/application/port/in/UserGetFollowersUsecase.java
  • src/main/java/konkuk/thip/user/application/service/following/UserGetFollowersService.java
🧰 Additional context used
🧠 Learnings (8)
src/main/java/konkuk/thip/user/adapter/out/jpa/FollowingJpaEntity.java (2)

Learnt from: buzz0331
PR: #75
File: src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepositoryImpl.java:50-83
Timestamp: 2025-07-14T14:19:38.796Z
Learning: Vote와 VoteItem 엔티티는 자주 함께 사용되므로, N+1 문제를 방지하기 위해 양방향 매핑과 fetch join을 고려하는 것이 좋습니다. 특히 기록장 조회 API 등에서도 함께 사용될 가능성이 높습니다.

Learnt from: seongjunnoh
PR: #36
File: src/main/java/konkuk/thip/user/adapter/out/persistence/UserJpaRepository.java:7-7
Timestamp: 2025-06-29T09:47:31.299Z
Learning: Spring Data JPA에서 findBy{FieldName} 패턴의 메서드는 명시적 선언 없이 자동으로 생성되며, Optional 반환 타입을 사용하는 것이 null 안전성을 위해 권장됩니다.

src/test/java/konkuk/thip/user/adapter/in/web/UserGetFollowersApiTest.java (1)

Learnt from: seongjunnoh
PR: #93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java:369-388
Timestamp: 2025-07-21T08:18:15.744Z
Learning: THIP 프로젝트의 커서 페이지네이션에서는 클라이언트가 "현재 조회할 페이지의 첫 번째 레코드 정보"를 cursor로 전달하며, 서버는 해당 커서 이상(inclusive)의 데이터를 조회하도록 goe, loe를 사용하여 구현되어 있다.

src/main/java/konkuk/thip/user/application/port/out/FollowingQueryPort.java (1)

Learnt from: seongjunnoh
PR: #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로부터 조회하는 메서드를 추가함.

src/main/java/konkuk/thip/common/util/CursorBasedList.java (1)

Learnt from: seongjunnoh
PR: #93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java:369-388
Timestamp: 2025-07-21T08:18:15.744Z
Learning: THIP 프로젝트의 커서 페이지네이션에서는 클라이언트가 "현재 조회할 페이지의 첫 번째 레코드 정보"를 cursor로 전달하며, 서버는 해당 커서 이상(inclusive)의 데이터를 조회하도록 goe, loe를 사용하여 구현되어 있다.

src/main/java/konkuk/thip/user/application/service/following/UserGetFollowService.java (1)

Learnt from: seongjunnoh
PR: #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로부터 조회하는 메서드를 추가함.

src/main/java/konkuk/thip/user/adapter/out/persistence/repository/following/FollowingQueryRepositoryImpl.java (3)

Learnt from: seongjunnoh
PR: #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로부터 조회하는 메서드를 추가함.

Learnt from: buzz0331
PR: #78
File: src/main/java/konkuk/thip/user/application/port/out/FollowingQueryPort.java:3-3
Timestamp: 2025-07-14T18:22:56.538Z
Learning: THIP 프로젝트에서는 Query API(조회 API)에 한해서는 application 계층에서 adapter.in.web.response 패키지의 response DTO를 직접 참조하는 것을 허용함. 이는 CQRS 아키텍처에서 읽기 전용 작업의 효율성을 위한 팀 컨벤션임.

Learnt from: seongjunnoh
PR: #36
File: src/main/java/konkuk/thip/user/adapter/out/persistence/UserJpaRepository.java:7-7
Timestamp: 2025-06-29T09:47:31.299Z
Learning: Spring Data JPA에서 findBy{FieldName} 패턴의 메서드는 명시적 선언 없이 자동으로 생성되며, Optional 반환 타입을 사용하는 것이 null 안전성을 위해 권장됩니다.

src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.java (1)

Learnt from: seongjunnoh
PR: #93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java:369-388
Timestamp: 2025-07-21T08:18:15.744Z
Learning: THIP 프로젝트의 커서 페이지네이션에서는 클라이언트가 "현재 조회할 페이지의 첫 번째 레코드 정보"를 cursor로 전달하며, 서버는 해당 커서 이상(inclusive)의 데이터를 조회하도록 goe, loe를 사용하여 구현되어 있다.

src/main/java/konkuk/thip/user/adapter/out/persistence/repository/following/FollowingQueryRepository.java (1)

Learnt from: seongjunnoh
PR: #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로부터 조회하는 메서드를 추가함.

🧬 Code Graph Analysis (2)
src/test/java/konkuk/thip/user/adapter/in/web/UserGetFollowingApiTest.java (2)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
  • TestEntityFactory (21-172)
src/test/java/konkuk/thip/user/adapter/in/web/UserGetFollowersApiTest.java (1)
  • SpringBootTest (29-101)
src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.java (1)
src/main/java/konkuk/thip/common/util/DateUtil.java (1)
  • DateUtil (12-62)
⏰ 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 (23)
src/main/java/konkuk/thip/user/adapter/out/jpa/FollowingJpaEntity.java (1)

24-24: 관계 필드의 의미를 명확히 하는 주석 추가 잘하셨습니다

팔로우 관계에서 혼동하기 쉬운 필드들의 역할을 명확히 하는 주석이 추가되어 코드 가독성이 향상되었습니다.

Also applies to: 28-28

build.gradle (1)

68-70: MapStruct 의존성 추가 적절합니다

DTO 매핑 책임 분리를 위한 MapStruct 라이브러리 추가가 올바르게 구성되었습니다. 런타임 의존성과 애노테이션 프로세서가 모두 포함되어 있어 정상적으로 동작할 것입니다.

src/main/java/konkuk/thip/common/util/CursorExtractor.java (1)

1-6: 커서 기반 페이지네이션을 위한 함수형 인터페이스 설계 좋습니다

제네릭을 활용한 재사용 가능한 커서 추출 인터페이스가 잘 설계되었습니다. 함수형 인터페이스 패턴을 올바르게 적용하고 단일 책임 원칙을 준수하고 있습니다.

src/main/java/konkuk/thip/user/adapter/in/web/UserCommandController.java (1)

36-38: API 메서드 문서화 잘하셨습니다

각 컨트롤러 메서드에 대한 명확하고 간결한 JavaDoc 주석이 추가되어 코드의 가독성과 유지보수성이 향상되었습니다.

Also applies to: 49-51, 59-61

src/main/java/konkuk/thip/user/adapter/in/web/response/UserFollowersResponse.java (1)

9-12: 커서 기반 페이지네이션에 맞는 응답 구조 개선

필드명을 followers로 간소화하고, nextCursor를 String 타입으로 변경하여 새로운 커서 기반 페이지네이션 구조와 일치시킨 변경사항이 적절합니다. 불필요한 필드들을 제거하여 응답 구조가 더 간결해졌습니다.

src/main/java/konkuk/thip/user/application/mapper/FollowDtoMapper.java (1)

1-14: 깔끔한 MapStruct 매퍼 구현입니다.

MapStruct를 활용한 DTO 매핑 로직이 잘 구현되어 있습니다. Spring 컴포넌트 모델 사용과 메서드 네이밍이 적절합니다.

src/main/java/konkuk/thip/user/application/port/in/UserGetFollowUsecase.java (1)

6-10: 잘 설계된 유스케이스 인터페이스입니다.

헥사고날 아키텍처 원칙을 잘 따르고 있으며, 커서 기반 페이지네이션 파라미터가 일관되게 적용되어 있습니다.

src/test/java/konkuk/thip/user/adapter/in/web/UserGetFollowersApiTest.java (3)

33-33: @transactional 어노테이션 추가로 테스트 정리가 개선되었습니다.

수동 데이터베이스 정리를 대체하여 테스트 코드가 더 깔끔해졌습니다.


79-79: API 응답 구조 변경이 테스트에 올바르게 반영되었습니다.

"followerList"에서 "followers"로의 필드명 변경이 테스트에 정확히 적용되어 있습니다.

Also applies to: 97-97


72-100: 커서 기반 페이지네이션 테스트가 포괄적으로 구현되었습니다.

첫 페이지와 후속 페이지 모두에 대한 테스트 커버리지가 우수하며, 커서 추출 및 사용 로직이 정확히 검증되고 있습니다.

src/main/java/konkuk/thip/user/application/port/out/FollowingQueryPort.java (1)

3-8: CQRS 컨벤션을 잘 따른 깔끔한 Query Port 인터페이스입니다.

커서 기반 페이징을 위한 일관된 메서드 시그니처와 CursorBasedList<FollowQueryDto> 반환 타입으로 표준화된 조회 로직을 제공합니다. 메서드 명도 팔로워/팔로잉을 명확히 구분하여 가독성이 좋습니다.

src/main/java/konkuk/thip/user/adapter/in/web/response/UserFollowingResponse.java (1)

7-21: 모던 Java 패턴을 잘 활용한 Response DTO입니다.

Record 문법과 중첩 구조를 통해 깔끔하게 설계되었으며, 커서 기반 페이징에 필요한 nextCursorisLast 필드가 적절히 포함되어 있습니다. @Builder 어노테이션으로 객체 생성의 편의성도 제공합니다.

src/test/java/konkuk/thip/user/adapter/in/web/UserGetFollowingApiTest.java (3)

29-52: 표준적인 Spring Boot 통합 테스트 설정입니다.

기존 팔로워 API 테스트와 일관된 구조로 작성되어 있으며, 필요한 의존성들이 적절히 주입되어 있습니다.


54-68: 페이징 테스트를 위한 적절한 테스트 데이터 설정입니다.

12명의 팔로잉 사용자로 2페이지에 걸친 페이징을 검증할 수 있도록 잘 구성되어 있으며, TestEntityFactory를 활용하여 일관된 엔티티 생성을 보장합니다.


70-102: 커서 기반 페이징을 철저히 검증하는 테스트입니다.

2페이지에 걸친 요청을 통해 페이징 로직을 체계적으로 검증하고 있으며, 응답 구조와 페이징 메타데이터를 모두 확인합니다. requestAttr("userId")를 통한 인증 컨텍스트 시뮬레이션도 적절합니다.

src/main/java/konkuk/thip/user/adapter/out/persistence/repository/following/FollowingQueryRepository.java (1)

4-4: 쿼리 성능 최적화를 위한 좋은 아키텍처 개선입니다.

JPA 엔티티 대신 FollowQueryDto를 반환하여 필요한 필드만 선택적으로 조회할 수 있게 되었고, 커서 기반 페이징을 위한 일관된 메서드 시그니처를 제공합니다. 메서드명도 팔로워/팔로잉 쿼리를 명확히 구분하여 가독성이 좋습니다.

Also applies to: 13-14

src/main/java/konkuk/thip/user/adapter/in/web/UserQueryController.java (3)

3-4: 팔로우 관련 기능을 통합한 좋은 아키텍처 개선입니다.

UserGetFollowUsecase로 통합하여 코드 응집도를 높였고, 유효성 검증과 새로운 응답 타입을 위한 임포트가 적절히 추가되었습니다.

Also applies to: 6-6, 8-8, 10-10, 23-23


35-43: 페이징 크기 제어와 문서화가 개선된 팔로워 조회 엔드포인트입니다.

@Max(10) @Min(1) 검증으로 적절한 페이지 크기를 보장하고, JavaDoc 주석으로 문서화를 개선했습니다. 통합된 유스케이스 사용도 깔끔합니다.


45-53: 기존 패턴을 일관되게 따른 새로운 팔로잉 조회 엔드포인트입니다.

@UserId 어노테이션으로 인증 처리를 깔끔하게 하고, 팔로워 엔드포인트와 동일한 유효성 검증 패턴을 적용하여 일관성을 유지했습니다.

src/main/java/konkuk/thip/user/application/service/following/UserGetFollowService.java (1)

29-31: 사용자 존재 여부 검증 로직 확인 및 예외 처리 보장 필요

  • UserCommandPort.findById(userId) 호출 시, 존재하지 않는 userId에 대해 예외(예: EntityNotFoundException)를 던지는지 확인해주세요.
  • 현재 구현체(UserCommandPersistenceAdapter)가 userJpaRepository.findById(id).orElseThrow(...) 형태로 예외를 처리하고 있지 않다면, orElseThrow 등을 통해 반드시 예외가 발생하도록 수정해야 합니다.
  • 서비스 레이어(UserGetFollowService)에서 예외 발생 시 적절한 HTTP 상태 코드(404 등)로 매핑되는지 함께 검토 바랍니다.
src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.java (1)

22-29: 커서 형식 일관성 확인 완료

DateUtil.parseDateTime이 ISO_LOCAL_DATE_TIME 포맷을 사용하고, LocalDateTime.toString()도 동일한 ISO_LOCAL_DATE_TIME 형식(소수점 이하 초 단위 포함 가능)으로 출력을 보장하므로 커서 생성 및 파싱 간 형식 불일치는 없습니다.

src/main/java/konkuk/thip/user/adapter/out/persistence/repository/following/FollowingQueryRepositoryImpl.java (2)

83-84: 조인 alias 매개변수 확인 필요
leftJoin(targetUser, user)에서 두 번째 인자 user는 이미 사용 중인 엔티티 alias입니다. QueryDSL의 leftJoin(Path<?>, Path<?>) 메서드 두 번째 파라미터는 새로운 별칭(alias)이어야 하므로, 별도 alias 변수(예: targetUserAlias)를 생성·사용했는지 검토해주세요.

발견 위치:

  • src/main/java/konkuk/thip/user/adapter/out/persistence/repository/following/FollowingQueryRepositoryImpl.java (Lines 83–84)

74-81: DTO 프로젝션 @QueryProjection 적용 확인 완료

FollowQueryDto 생성자에 @QueryProjection 어노테이션이 올바르게 적용되어 있어 QFollowQueryDto 프로젝션을 안전하게 사용할 수 있습니다. 별도 수정이 불필요합니다.

  • 확인 위치: src/main/java/konkuk/thip/user/application/port/out/dto/FollowQueryDto.java:15

Comment on lines +23 to +27
List<FollowQueryDto> followerDtos = followingJpaRepository.findFollowerDtosByUserIdBeforeCreatedAt(
userId,
cursorVal,
size
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

커서 기반 페이지네이션 구현 확인

메서드명 findFollowerDtosByUserIdBeforeCreatedAt는 커서 이전(exclusive) 데이터를 조회하는 것을 암시하지만, 학습된 내용에 따르면 THIP 프로젝트는 커서 이상(inclusive)의 데이터를 조회해야 합니다.


🏁 Script executed:

#!/bin/bash
# Repository 구현에서 실제 쿼리 조건 확인
rg -B 5 -A 10 "findFollowerDtosByUserIdBeforeCreatedAt.*cursor.*size" src/main/java/konkuk/thip/user/adapter/out/persistence/repository/

Length of output: 3627


🏁 Script executed:

#!/bin/bash
# find the implementation of findFollowDtos to check cursor comparison operator
rg -n "private List<FollowQueryDto> findFollowDtos" -A 20 src/main/java/konkuk/thip/user/adapter/out/persistence/repository/following/FollowingQueryRepositoryImpl.java

Length of output: 1269


커서 이상(inclusive) 조회로 조건 수정 필요

THIP 프로젝트의 커서 페이지네이션 컨벤션에 따라, 전달받은 커서 이상의 데이터를 조회해야 합니다. 현재 findFollowDtos 구현에서 following.createdAt.lt(cursor)로 strict하게(exclusive) 필터링하고 있으므로, loe(cursor)로 수정해주세요.

  • 대상 파일:
    src/main/java/konkuk/thip/user/adapter/out/persistence/repository/following/FollowingQueryRepositoryImpl.java
  • 대상 라인:
    // 변경 전 (exclusive)
    if (cursor != null) {
        condition.and(following.createdAt.lt(cursor));
    }
  • 변경 제안:
    if (cursor != null) {
    -    condition.and(following.createdAt.lt(cursor));
    +    condition.and(following.createdAt.loe(cursor));
    }
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/user/adapter/out/persistence/repository/following/FollowingQueryRepositoryImpl.java
around the lines where the cursor condition is applied, change the filtering
condition from exclusive (following.createdAt.lt(cursor)) to inclusive
(following.createdAt.loe(cursor)) to align with the THIP project's cursor
pagination convention that requires fetching data with createdAt values greater
than or equal to the cursor.

Comment on lines 67 to 69
if (cursor != null) {
condition.and(following.createdAt.lt(cursor));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

커서 조건이 프로젝트 컨벤션과 불일치

학습된 내용에 따르면 THIP 프로젝트의 커서 페이지네이션은 "커서 이상(inclusive)"의 데이터를 조회해야 하므로 goe/loe를 사용해야 합니다. 하지만 현재 구현은 lt()를 사용하여 exclusive 조건으로 되어 있습니다.

         if (cursor != null) {
-            condition.and(following.createdAt.lt(cursor));
+            condition.and(following.createdAt.loe(cursor));
         }
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/user/adapter/out/persistence/repository/following/FollowingQueryRepositoryImpl.java
at lines 67 to 69, the cursor pagination condition uses an exclusive less-than
(lt) comparison, which conflicts with the THIP project convention requiring
inclusive cursor pagination. Change the condition to use a greater-than-or-equal
(goe) or less-than-or-equal (loe) comparison as appropriate to include the
cursor value in the query results.

Comment thread src/main/java/konkuk/thip/user/application/port/out/dto/FollowQueryDto.java Outdated
Copy link
Copy Markdown
Member

@hd0rable hd0rable left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨어요~~
확실히 복잡한 쿼리를 QueryDSL로 다룰 때, 공통 DTO로 추출해두면 코드가 훨씬 가벼워지네요
다만, 이전 회의에서 이야기했던 것처럼 도메인이 달라지면 공통 DTO를 재사용하지 못하고 계속 새로운 DTO가 늘어날 수 있다는 점은 고민이 필요한 부분인 것 같아요.
그래서 나중에 리펙토링할 때는
"어떤 조회에서 다른 도메인의 데이터까지 함께 사용하는가?"
"그게 진짜 필요한 설계인가?"
이런 기준을 먼저 세우고, 필요한 곳만 공통화하거나 분리하면 좋을 것 같습니다!!👍🏻

private final UserGetFollowersUsecase userGetFollowersUsecase;
private final UserGetFollowUsecase userGetFollowUsecase;

/**
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "following_user_id")
private UserJpaEntity followingUserJpaEntity;
private UserJpaEntity followingUserJpaEntity; // 팔로우 당하는 유저
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅋㅋㅋ 팔로우당하는유저(비웃은거아님웃겨서웃은거임)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅋㅋㅋㅋ 팔로잉 팔로워 헷갈리긴 하네여

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

필드 이름이 너무 애매해서 그냥 주석 남겼습니다 ㅎ

Copy link
Copy Markdown
Collaborator

@seongjunnoh seongjunnoh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다!! 처음 보는 기술들이 있어서 공부하면서 리뷰할 수 있었습니다!

논의하고 싶은 부분이 있어서 리뷰달았는데 확인해주시면 감사하겠습니다!

Comment on lines +7 to +9
UserFollowersResponse getUserFollowers(Long userId, String cursor, int size);

UserFollowingResponse getMyFollowing(Long userId, String cursor, int size);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM
메서드 네이밍이 직관적이어서 보기 편하네요!!

Comment on lines +5 to +9
public record CursorBasedList<T>(
List<T> contents,
String nextCursor,
boolean hasNext
) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

클라이언트와 주고받는 cursor 값을 String 타입으로 명시하고, 영속성 adapter 내부에서 클라이언트에게 전달받은 커서의 타입을 LocalDateTime 으로 수정하셨군요!!

전 "내 모임방 목록 조회 api" 에서

  1. controller 의 cursor request parameter 의 타입을 LocalDate, Long 으로 설정
  2. 따라서 스프링부트가 자동으로 String -> LocalDate, String -> Long 으로 변환해줌
  3. LocalDate, Long 타입의 커서를 사용하여 페이징 처리
  4. 다시 클라이언트에게 nextCursor 값을 전달하기 위해 response에 LocalDate, Long 타입의 nextCursor 를 정의
  5. 클라이언트는 JSON 형식의 response를 받는데, 내부 nextCursor의 값은 LocalDate의 String 형식(ex "2020-00-00") 과 JS의 Number 타입 으로 변환
  6. 따라서 nextCursor 의 값을 이용해 다시 요청을 보낼 경우, response 로 응답받은 커서값을 형변환없이 그대로 사용 가능

위와 같은 플로우를 생각하여 controller 에서 받는 cursor request parameter, response 로 전달하는 nextCursor 의 타입을 모두 저희가 페이징 처리시 사용하는 타입 그대로 정의하였습니다.

@buzz0331 @hd0rable 혹시 이 부분에 대해서 어떻게 생각하시나요??

위 플로우처럼 스프링부트가 제공해주는 Jackson 라이브러리를 활용하면 클라이언트와 주고받는 커서의 타입을 신경쓰지 않아도 되고, 명시한 커서 타입이 아니라 다른 타입이 요청 파라미터로 들어올 경우 스프링이 내부적으로 "파라미터 바인딩 실패 -> 400 error throw" 해주므로 저희가 추가로 예외처리를 하지 않아도 되는 장점이 있을 것 같습니다!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 개인적으로 커서를 하나의 문자열로 받아 처리하는 방식이 응답 구조의 일관성이나 확장성 측면에서 더 적합하다고 생각하고 있습니다. 앞으로 저희가 커서 페이징에서 공통적으로 사용할 CursorBasedList 구조에서도 nextCursor는 단일 String 타입으로 정의되어 있기 때문에, 커서 역시 하나의 문자열로 통합하여 사용하는 것이 다양한 조회 API에 일관되게 적용할 수 있고, 응답 구조 또한 통일된 형태로 유지할 수 있다는 점에서 장점이 있다고 생각합니다.

반대로, 커서를 여러 파라미터로 나누게 되면 각 API마다 커서 필드를 개별적으로 정의해야 하며, 공통 응답 객체를 사용하는 데에도 제한이 생기게 됩니다. 또한 이 경우 응답 DTO뿐만 아니라 요청 파라미터도 각각의 이름을 따로 지정해야 하기 때문에, 예를 들어 cursorDate, cursorRoomId처럼 네이밍이 API마다 달라질 가능성이 높고, 이는 클라이언트 측에서도 사용 시 혼란을 줄 수 있다고 생각합니다.

반면 커서를 cursor={String} 형태로 일관되게 주고받게 되면, 클라이언트는 커서 값을 단순히 저장하고 그대로 다음 요청에 넘겨주는 방식으로 처리할 수 있기 때문에, 구현 부담도 줄고 인터페이스도 단순해진다는 장점이 있습니다.

무엇보다 커서 구조를 외부에 드러내지 않고, 서버 내부 로직에 따라 파싱하여 처리하는 방식은 캡슐화 측면에서도 바람직하며, 추후 커서 구조가 변경되거나 인코딩/보안 처리 등을 추가해야 할 때도 서버 단에서만 수정하면 되므로 유지보수에도 유리하다고 생각합니다.

이런 이유로 성준님 PR에서 사용하신 LocalDate와 roomId 두 개의 커서 값을 "2024-07-22|102"와 같은 형식으로 하나의 문자열 커서로 묶어서 전달하고, 이를 서버 내부에서 분리·파싱하여 사용하는 방식도 한번 고려해보면 좋을 것 같습니다!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아아 CursorBasedList에 정의해두신 String nextCursor 가 커서 하나의 String 형변환값이 아니라, 그냥 저희가 페이징처리시에 사용할 커서 자체를 의미하신 거군요!

코멘트로 남겨주신 내용처럼 복합 커서가 필요할 경우, 저희가 내부적으로 복합 커서를 String 으로 변환하고, 받은 cursor 값을 다시 복합 커서로 파싱하는 방식 좋은 것 같습니다!!

현준님 pr 머지되면 복합 커서로 작성된 코드 수정해보겠습니다!!

Comment on lines +40 to +51
@RequestParam(required = false) final String cursor,
@RequestParam(defaultValue = "10") @Max(value = 10) @Min(value = 1) final int size) {
return BaseResponse.ok(userGetFollowUsecase.getUserFollowers(userId, cursor, size));
}

/**
* 내 팔로잉 리스트 조회
*/
@GetMapping("/users/my/following")
public BaseResponse<UserFollowingResponse> showMyFollowing(@UserId final Long userId,
@RequestParam(required = false) final String cursor,
@RequestParam(defaultValue = "10") @Max(value = 10) @Min(value = 1) final int size) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3 : 저희 페이지 사이즈 BE 내부에서 정의하는게 아니라 요청 파라미터로 받는 건가요??

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵, 프론트 쪽에서 단일 페이지 크기를 요청 파라미터로 전달할 수도 있고, 전달하지 않는 경우에는 최대 페이지 수 10을 기본값(default) 으로 설정해두었습니다
이렇게 하면 API의 유연성이 좀 더 높아지지 않을까 생각했습니다!

Comment on lines +8 to +14
@Mapper(componentModel = "spring")
public interface FollowDtoMapper {

UserFollowersResponse.Follower toFollowerList(FollowQueryDto dto);

UserFollowingResponse.Following toFollowingList(FollowQueryDto dto);
} No newline at end of file
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 새로운 기술 도입 좋습니다!

size
);

return CursorBasedList.of(followingDtos, size, followingDto -> followingDto.createdAt().toString());
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 함수형 인터페이스 좋네요

@buzz0331
Copy link
Copy Markdown
Contributor Author

수고하셨어요~~ 확실히 복잡한 쿼리를 QueryDSL로 다룰 때, 공통 DTO로 추출해두면 코드가 훨씬 가벼워지네요 다만, 이전 회의에서 이야기했던 것처럼 도메인이 달라지면 공통 DTO를 재사용하지 못하고 계속 새로운 DTO가 늘어날 수 있다는 점은 고민이 필요한 부분인 것 같아요. 그래서 나중에 리펙토링할 때는 "어떤 조회에서 다른 도메인의 데이터까지 함께 사용하는가?" "그게 진짜 필요한 설계인가?" 이런 기준을 먼저 세우고, 필요한 곳만 공통화하거나 분리하면 좋을 것 같습니다!!👍🏻

넵 좋습니다! 회의에서는 제 조회 흐름을 공유드리면서 일종의 컨벤션처럼 설명드리긴 했지만, 의도했던 바는 ‘조회 DTO를 무조건 사용하자’가 아니라 자주 사용되는 컴포넌트는 공통 조회 DTO로 추출해서 재사용하자는 것이었습니다.
가능한 필드들을 최대한 공통 조회 DTO에 담아두고, 서비스 로직에서는 그중 필요한 필드만 파싱해 사용하는 방식이라면, 유지보수성과 가독성 모두 향상될 수 있다고 생각합니다!

@buzz0331 buzz0331 merged commit 55be1df into develop Jul 22, 2025
2 checks passed
@buzz0331 buzz0331 deleted the feat/#90-my-following-list branch July 22, 2025 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[THIP2025-133] [feat] 내 팔로잉 리스트 조회 api 개발

3 participants