Skip to content

링크 조회 API 및 링크 목록 조회 API 응답 개선#158

Merged
ckdals4600 merged 1 commit intomainfrom
feature/#157-refacator-link-list-api
Dec 29, 2025
Merged

링크 조회 API 및 링크 목록 조회 API 응답 개선#158
ckdals4600 merged 1 commit intomainfrom
feature/#157-refacator-link-list-api

Conversation

@ckdals4600
Copy link
Contributor

@ckdals4600 ckdals4600 commented Dec 21, 2025

관련 이슈

PR 설명

  • 기존 링크 조회 API 및 링크 목록 조회 API의 응답 구조를 개선하여Summary 정보를 포함하도록 변경
  • 링크 목록 조회 시 대용량 데이터 환경에서도 성능 저하 없는 무한 스크롤을 구현하기 위해 기존 Offset 기반 페이징을 Cursor 기반 페이징)으로 변경했습니다.

핵심 변경 사항

1. 커서 기반 페이징(Cursor Pagination) 도입 이유

  • 무한 스크롤(Infinite Scroll) 환경에서 기존 Pageable(Offset) 방식 대신 lastId를 사용하는 커서 방식을 도입했습니다.
  1. 성능 최적화:
    • Offset 방식(LIMIT N OFFSET M)은 페이지가 뒤로 갈수록 앞에서부터 모든 행을 읽고 버려야 하므로 데이터가 많아질수록 조회 속도가 급격히 느려집니다.
    • 반면, Cursor 방식(WHERE id < lastId LIMIT size)은 인덱스를 타고 조회 시작 위치를 바로 찾을 수 있어 데이터 양과 관계없이 일정한 조회 성능을 보장합니다.
  2. 데이터 정합성:
    • 사용자가 스크롤을 내리는 동안 새로운 링크가 추가되거나 삭제될 경우, Offset 방식은 데이터가 밀려 중복 노출되거나 누락되는 문제가 발생할 수 있습니다.
    • Cursor 방식은 고유한 ID를 기준으로 다음 데이터를 가져오므로 이러한 문제 없이 안정적인 데이터 흐름을 제공합니다.

2. 검증 책임 이동 (Validation Refactoring)

  • Spring Bean Validation(@Min, @Max, @Valid) 로직을 구현체(LinkController)에서 인터페이스(LinkApi)로 이동했습니다.
  • 개발 과정에서 **"검증 책임은 어디에 있어야 하는가?"**에 대한 고민 끝에, 다음과 같은 이유로 인터페이스 레벨로 검증 로직을 이동하는 방식을 제안합니다.
  1. 제약 조건 선언의 중복 및 충돌 방지 (ConstraintDeclarationException):
    • Bean Validation 스펙(JSR-380)상, 인터페이스와 구현 클래스 양쪽에 제약 조건을 중복 선언하거나, 구현체에서 제약 조건을 강화/약화할 경우 런타임 에러가 발생합니다.
    • 이를 방지하기 위해 "Interface에만 제약 조건을 명시"하는 원칙을 적용했습니다.
  2. 계약에 의한 설계 (Design by Contract):
    • 인터페이스는 클라이언트와의 **약속(Spec)**입니다. "어떤 데이터가 유효한지"에 대한 정의는 구현 로직(How)이 아닌 명세(What) 단계에서 정의되는 것이 아키텍처 관점에서 더 명확하다고 판단했습니다.
  3. AOP 프록시 동작 보장:
    • @Validated는 Spring AOP 기반으로 동작합니다. 인터페이스에 제약 조건이 명시되어야 JDK Dynamic Proxy가 생성될 때 해당 메서드의 규약을 정확히 가로채어 검증할 수 있습니다.
  4. 어노테이션 역할 분리:
    • Interface (LinkApi): @Valid, @Min, @Max데이터 검증(Validation) 관련 어노테이션 배치.
    • Controller (LinkController): @RequestBody, @PathVariable, @RequestParam데이터 바인딩(Binding) 관련 어노테이션 유지.

작업 내용

1. API 명세 변경 (LinkController)

  • 단건 조회 (GET /v1/links/{id}):
    • 기존 LinkRes 대신 요약 정보가 포함된 LinkCardRes를 반환하도록 변경했습니다.
  • 목록 조회 (GET /v1/links):
    • Pageable 파라미터를 제거하고 lastId(커서)와 size를 받도록 수정했습니다.
    • 응답 객체에 요약 정보를 포함하고, 다음 페이지 존재 여부(hasNext)를 반환합니다.

2. DTO 설계 (데이터 전송 객체 분리)

계층 간 결합도를 낮추기 위해 내부 전달용 DTO외부 응답용 DTO를 명확히 분리했습니다.

  • Internal DTO: LinkDto(Link+Summary 엔티티 래핑), LinksDto(hasNext 포함) - Repository/Service 계층 간 이동.
  • Response DTO: LinkCardResLinkDetailRes 분리
    • LinkCardRes: 링크 카드 정보 제공,
      • 링크ID, 제목, Url, 이미지, 요약 내용
    • LinkDetailRes: 링크 상세 정보 제공
      • 링크ID, 제목, Url, 이미지, 메모, 요약 정보(요약ID, 요약 정보)

3. Repository 최적화 (LinkRepository)

  • DTO 직접 조회 (Projection):
    • new com.sofa...LinkDto(l, s) 생성자 표현식을 사용하여 엔티티 전체를 로딩하는 대신 필요한 데이터만 DB 레벨에서 매핑했습니다.
  • LEFT JOIN 활용:
    • LEFT JOIN Summary s ON ... s.selected = true 구문을 사용하여, 링크와 선택된 요약본을 단 한 번의 쿼리로 조회하도록 최적화했습니다 (N+1 문제 방지).

4. Service 계층 분리

  • LinkQueryService (조회 전담):
    • findByIdWithSummary: 요약 포함 단건 조회.
    • findAllByMemberWithSummaryAndCursor: size + 1개를 조회하여 hasNext 여부를 판단하고 목록을 반환하는 핵심 로직 구현.
  • LinkService (Facade 성격):
    • QueryService에서 받은 내부 DTO를 응답 DTO(LinkCardRes)로 변환하는 역할 수행.

5. 테스트 작성

  • LinkRepositoryTest: 커서 페이징 쿼리(WHERE id < lastId) 동작 및 요약 데이터 조인(LEFT JOIN) 검증.
  • LinkQueryServiceTest: 데이터 개수에 따른 hasNext 계산 로직 및 삭제된 링크 필터링 검증.
  • LinkServiceTest: 내부 DTO -> 응답 DTO 변환 로직 검증.

@ckdals4600 ckdals4600 linked an issue Dec 21, 2025 that may be closed by this pull request
@ckdals4600 ckdals4600 requested review from Goder-0 and minibr December 21, 2025 00:15
@ckdals4600 ckdals4600 self-assigned this Dec 21, 2025
@github-actions
Copy link

github-actions bot commented Dec 21, 2025

📊 코드 커버리지 리포트

Overall Project 87.91% 🍏
Files changed 100% 🍏

File Coverage
LinkController.java 100% 🍏
LinkFacade.java 100% 🍏
LinkService.java 100% 🍏
LinkQueryService.java 98.81% 🍏

@ckdals4600 ckdals4600 force-pushed the feature/#157-refacator-link-list-api branch from 0b40596 to 12b52f6 Compare December 24, 2025 01:25
@ckdals4600 ckdals4600 requested a review from Goder-0 December 24, 2025 01:26
@ckdals4600 ckdals4600 force-pushed the feature/#157-refacator-link-list-api branch 3 times, most recently from 4e18968 to f4b169f Compare December 24, 2025 07:29
@ckdals4600
Copy link
Contributor Author

디스코드 논의 결과에 따라. 링크 카드와 링크 상세 조회 DTO 분리 하였습니다.

@ckdals4600 ckdals4600 force-pushed the feature/#157-refacator-link-list-api branch from f4b169f to 950356e Compare December 24, 2025 10:15
@ckdals4600 ckdals4600 force-pushed the feature/#157-refacator-link-list-api branch from 950356e to e7514d4 Compare December 26, 2025 17:42
@ckdals4600 ckdals4600 requested a review from minibr December 29, 2025 17:20
@ckdals4600 ckdals4600 merged commit ded8c8e into main Dec 29, 2025
1 check passed
@ckdals4600 ckdals4600 deleted the feature/#157-refacator-link-list-api branch December 29, 2025 17:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

링크 조회 API 및 링크 목록 조회 API 응답 개선

3 participants