Skip to content

Conversation

@dungbik
Copy link
Contributor

@dungbik dungbik commented Sep 24, 2025

📝 변경 내용


✅ 체크리스트

  • 코드가 정상적으로 동작함
  • 테스트 코드 통과함
  • 문서(README 등)를 최신화함
  • 코드 스타일 가이드 준수

💬 기타 참고 사항

Summary by CodeRabbit

  • Refactor
    • 좋아요 대상 조회 및 가시성 검증 로직을 일원화해 처리 흐름을 간소화하고 안정성을 높였습니다.
    • 좋아요 조회 응답 구성 방식을 정비해 결과 일관성과 유지보수성을 개선했습니다.
  • Bug Fixes
    • 비공개/접근 불가 콘텐츠에 대한 좋아요/취소 시점의 검증과 오류처리가 더 일관되게 동작합니다.
    • 중복 좋아요 검사 및 삭제 처리의 신뢰성을 강화했습니다.

@dungbik dungbik self-assigned this Sep 24, 2025
@dungbik dungbik added the enhancement New feature or request label Sep 24, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 24, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

like 모듈의 모델 클래스를 request/response 하위패키지로 분리하고, 페처 맵 기반의 타깃 조회/가시성 로직을 중앙화한 LikeTargetFetchService로 대체했으며 LikeReader를 추가하고 관련 서비스와 페처 인터페이스 시그니처를 사용자 컨텍스트(userId) 기반으로 변경했습니다.

Changes

Cohort / File(s) Summary
Controller imports
src/main/java/project/flipnote/like/controller/LikeController.java, src/main/java/project/flipnote/like/controller/docs/LikeControllerDocs.java
모델 타입 import 경로를 project.flipnote.like.model.* → `project.flipnote.like.model.request
Model - request 이동
src/main/java/project/flipnote/like/model/request/LikeSearchRequest.java, src/main/java/project/flipnote/like/model/request/LikeTargetTypeRequest.java
패키지 선언을 ...model...model.request로 변경. 클래스 내용/멤버 불변, FQN 변경.
Model - response 이동
src/main/java/project/flipnote/like/model/response/CardSetLikeResponse.java, .../LikeResponse.java, .../LikeTargetResponse.java
패키지 선언을 ...model...model.response로 변경. 클래스 내용/멤버 불변, FQN 변경.
Service - 정책 검증 변경
src/main/java/project/flipnote/like/service/LikePolicyService.java
CardSetService 직접 검사 제거. validateTargetExists(LikeTargetType, Long, Long userId) 시그니처로 변경하고 LikeTargetFetchService.isTargetViewable(...) 사용해 가시성 기반 검증으로 전환.
Service - 리더 추가
src/main/java/project/flipnote/like/service/LikeReader.java
신규 서비스 추가: findByTargetAndUserId(LikeTargetType, Long, Long) 제공. 조회 실패 시 LIKE_NOT_FOUND 예외 발생.
Service - 주요 흐름 교체
src/main/java/project/flipnote/like/service/LikeService.java
페처 리스트/맵 및 @PostConstruct 제거. LikeTargetFetchService<LikeTargetResponse>, LikeReader 주입. addLike/removeLike/getLikes 흐름을 중앙화 서비스와 사용자 컨텍스트로 변경(제네릭 반환형 단일화).
Service - 페처 집약
src/main/java/project/flipnote/like/service/LikeTargetFetchService.java
신규 제네릭 서비스 추가. @PostConstruct로 페처들을 맵에 등록. isTargetViewable(...), fetchByTypeAndIds(..., userId) 제공. 미매칭 타입은 INVALID_LIKE_TYPE 예외.
Fetcher 인터페이스/구현 변경
src/main/java/project/flipnote/like/service/fetcher/LikeTargetFetcher.java, .../LikeCardSetFetcher.java
인터페이스 변경: fetchByIds(Set<Long>) 제거 → fetchByIds(Set<Long>, Long userId)로 시그니처 변경, isTargetViewable(Long targetId, Long userId) 추가. 구현체(예: CardSetFetcher)에서 해당 변경 반영.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as 사용자
  participant C as LikeController
  participant S as LikeService
  participant P as LikePolicyService
  participant F as LikeTargetFetchService
  participant R as LikeRepository
  participant E as EventPublisher

  U->>C: POST /likes {targetType, targetId}
  C->>S: addLike(userId, targetType, targetId)
  S->>P: validateTargetExists(targetType, targetId, userId)
  P->>F: isTargetViewable(targetType, targetId, userId)
  F-->>P: boolean
  alt not viewable
    P-->>S: throws LIKE_TARGET_NOT_FOUND
    S-->>C: error response
  else viewable
    S->>R: save(new Like)
    R-->>S: Like
    S->>E: publish LikeEvent
    S-->>C: success
  end
Loading
sequenceDiagram
  autonumber
  actor U as 사용자
  participant C as LikeController
  participant S as LikeService
  participant LR as LikeRepository
  participant F as LikeTargetFetchService

  U->>C: GET /likes?type=...&page=...
  C->>S: getLikes(userId, targetType, searchReq)
  S->>LR: findAllByUserAndType(paging)
  LR-->>S: Page<Like>
  S->>F: fetchByTypeAndIds(targetType, likeIdsSet, userId)
  F-->>S: Map<id, LikeTargetResponse>
  S-->>C: PagingResponse<LikeResponse<LikeTargetResponse>>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • stoneTiger0912

Poem

달그림자 길에 살짝 찍힌 좋아요 하나,
작은 토끼가 와서 귀를 쫑긋, 권한을 물어보네.
페처들이 줄 맞춰 호출되고 서비스가 춤추며,
이벤트에 실려 별빛으로 전송되네 — 껑충! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.75% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 제목 "Refactor: Like 도메인 리팩토링"은 변경사항의 핵심인 Like 도메인 리팩토링(패키지 이동 및 서비스 구조 변경)을 명확히 나타내어 PR 목적과 일치합니다. 다만 "Refactor"와 "리팩토링"이 중복되어 있어 약간 장황하게 느껴질 수 있습니다.

📜 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 70c8828 and eed6391.

📒 Files selected for processing (1)
  • src/main/java/project/flipnote/like/service/LikePolicyService.java (1 hunks)

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.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/main/java/project/flipnote/like/service/fetcher/LikeCardSetFetcher.java (1)

33-37: userId 미사용 — LikeCardSetFetcher.fetchByIds에 가시성 필터 적용 필요

LikeTargetFetchService.fetchByTypeAndIds(..., userId)가 userId를 전달하므로 각 fetcher는 userId로 가시성 필터링해야 합니다. 현재 LikeCardSetFetcher.fetchByIds는 userId를 무시합니다.

  public Map<Long, CardSetLikeResponse> fetchByIds(Set<Long> ids, Long userId) {
-    return cardSetService.getCardSetsByIds(ids).stream()
-      .map(CardSetLikeResponse::from)
-      .collect(Collectors.toMap(LikeTargetResponse::getId, Function.identity()));
+    return cardSetService.getCardSetsByIds(ids).stream()
+      .map(CardSetLikeResponse::from)
+      .filter(res -> isTargetViewable(res.getId(), userId))
+      .collect(Collectors.toMap(LikeTargetResponse::getId, Function.identity()));
  }

또는 CardSetService에 userId 기반 배치 조회 API를 만들어 권한 체크 횟수를 줄이세요. 필터링할 경우 LikeService.getLikes 쪽에서 targetMap.get(...)이 null을 반환할 수 있으니 누락된 대상 처리(혹은 건너뛰기)를 검토하세요.

src/main/java/project/flipnote/like/model/request/LikeTargetTypeRequest.java (1)

5-11: ENUM 네이밍: UPPER_SNAKE_CASE 권장 — PathVariable 매핑 보존 필요

Java 관례상 enum 상수는 UPPER_SNAKE_CASE를 권장합니다. 그러나 이 enum은 컨트롤러의 @PathVariable로 바인딩되고 있어(src/main/java/project/flipnote/like/controller/LikeController.java), 단순히 CARD_SET으로 이름만 바꾸고 @JsonProperty("card_set")만 추가하면 PathVariable('card_set') → enum 매핑은 깨집니다(@JsonProperty는 Jackson 전용).

  • 옵션 A (간단): 외부 API 계약을 유지하려면 현재처럼 card_set 이름 유지.
  • 옵션 B (리팩터, 권장 컨벤션): 상수명 변경 → CARD_SET 및 JSON 바인딩 보장을 위해 @JsonProperty/@JsonCreator 추가하고, PathVariable 매핑 유지를 위해 아래 중 하나를 반드시 적용:
    • Spring Converter<String, LikeTargetTypeRequest> 빈 등록(문자열 "card_set"을 CARD_SET으로 변환), 또는
    • 컨트롤러에서 @PathVariable을 String으로 받고 내부에서 변환 처리.
  • 참고: 저장소 검색 결과 상수명을 직접 참조한 곳은 enum 내부 switch 뿐이며(따라서 컴파일 에러는 발생하지 않음) 런타임의 PathVariable 매핑은 별도 처리 필요.
🧹 Nitpick comments (7)
src/main/java/project/flipnote/like/model/response/LikeTargetResponse.java (1)

1-5: OpenAPI 다형성 스키마 노출 제안

LikeControllerDocs에서 LikeResponse를 노출하므로, 베이스 타입에 oneOf로 서브타입을 명시하면 문서 가독성이 크게 개선됩니다.

아래와 같이 Swagger 어노테이션을 추가하는 것을 권장합니다(서브타입은 예시이며 실제 존재 타입으로 확장):

 package project.flipnote.like.model.response;
 
+import io.swagger.v3.oas.annotations.media.Schema;
+import project.flipnote.like.model.response.CardSetLikeResponse;
 
+@Schema(description = "좋아요 대상 응답의 베이스 타입", oneOf = {CardSetLikeResponse.class})
 public abstract class LikeTargetResponse {
 	public abstract Long getId();
 }
src/main/java/project/flipnote/like/controller/docs/LikeControllerDocs.java (1)

25-25: 제네릭 공변성 반영으로 문서/타입 안정성 개선

서브타입을 반환할 수 있도록 와일드카드 사용을 권장합니다.

-	ResponseEntity<PagingResponse<LikeResponse<LikeTargetResponse>>> getLikes(
+	ResponseEntity<PagingResponse<LikeResponse<? extends LikeTargetResponse>>> getLikes(
src/main/java/project/flipnote/like/service/LikeReader.java (1)

18-21: 읽기 전용 트랜잭션 추가 권장

조회 전용 메서드에 readOnly 트랜잭션을 부여하면 불필요한 flush 방지 및 성능/일관성에 도움이 됩니다.

 package project.flipnote.like.service;
 
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 ...
 	public Like findByTargetAndUserId(LikeTargetType targetType, Long targetId, Long userId) {
+		@Transactional(readOnly = true)
 		return likeRepository.findByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId)
 			.orElseThrow(() -> new BizException(LikeErrorCode.LIKE_NOT_FOUND));
 	}
src/main/java/project/flipnote/like/service/fetcher/LikeTargetFetcher.java (1)

12-15: 계약 명확화: 가시성/존재 판단과 일괄 조회의 책임 정의

isTargetViewable와 fetchByIds의 동작 계약(존재/가시성 실패 시 반환/부재 처리)을 인터페이스 문서로 명시하면 구현/호출 일관성이 좋아집니다. 또한 fetchByIds가 “가시성 필터링된 결과만 반환하는지” 여부를 명확히 해 주세요.

 public interface LikeTargetFetcher<T extends LikeTargetResponse> {
 	LikeTargetType getTargetType();
 
-	boolean isTargetViewable(Long targetId, Long userId);
+	/**
+	 * 대상이 존재하고 사용자 컨텍스트에서 조회 가능하면 true.
+	 * - 존재하지 않거나 비공개/권한 부족이면 false.
+	 */
+	boolean isTargetViewable(Long targetId, Long userId);
 
-	Map<Long, T> fetchByIds(Set<Long> targetIds, Long userId);
+	/**
+	 * targetIds에 대해 사용자 컨텍스트를 고려한 조회 결과를 반환.
+	 * - 반환 맵에는 조회 가능(존재 + 가시성 충족)한 대상만 포함하는 것을 권장.
+	 * - 입력 ID 대비 누락된 키는 비존재/비가시성을 의미.
+	 */
+	Map<Long, T> fetchByIds(Set<Long> targetIds, Long userId);
 }
src/main/java/project/flipnote/like/service/LikeTargetFetchService.java (2)

29-31: 중복 키 시 Collectors.toMap가 즉시 실패합니다 — 방어 코드 추가 권장.

동일 LikeTargetType을 반환하는 페처가 2개 이상 등록되면 IllegalStateException이 발생합니다. 초기화 시점에서 명시적으로 실패시키는 편이 디버깅에 유리합니다.

아래처럼 병합 함수를 추가해 중복을 감지하며 명시적으로 예외를 던지세요.

-        this.fetcherMap = this.fetchers.stream()
-            .collect(Collectors.toMap(LikeTargetFetcher::getTargetType, Function.identity()));
+        this.fetcherMap = this.fetchers.stream()
+            .collect(Collectors.toMap(
+                LikeTargetFetcher::getTargetType,
+                Function.identity(),
+                (a, b) -> {
+                    throw new IllegalStateException(
+                        "Duplicate LikeTargetFetcher for type: " + a.getTargetType()
+                    );
+                }
+            ));

49-56: 테스트 환경에서 @PostConstruct 미호출 대비.

Spring 컨텍스트 없이 단위 테스트로 인스턴스화할 때 fetcherMap이 null이라 NPE가 납니다. 방어적으로 지연 초기화를 추가하는 것을 권장합니다.

다음 한 줄로 충분합니다.

     private LikeTargetFetcher<?> getFetcher(LikeTargetType targetType) {
+        if (fetcherMap == null) init();
         LikeTargetFetcher<?> fetcher = fetcherMap.get(targetType);
         if (fetcher == null) {
             throw new BizException(LikeErrorCode.INVALID_LIKE_TYPE);
         }
         return fetcher;
     }
src/main/java/project/flipnote/like/service/LikeService.java (1)

104-107: 타겟 미존재/비가시성 케이스 처리 누락 가능성.

fetchByTypeAndIds는 존재/가시성 기준으로 일부 ID를 제외해 반환할 수 있습니다. targetMap.get(...)이 null이면 LikeResponse 생성 시 NPE 또는 응답 스키마 누락이 발생할 수 있습니다. “누락 시 제외” 또는 “명시적 에러/플레이스홀더” 중 정책을 정해 처리하세요.

예시(정책에 따라 택일):

  • 제외: Page를 리스트로 변환 후 null 필터링하여 PageImpl로 재조립(단, totalElements/페이지 크기 영향 고려).
  • 실패: null 발견 시 BizException(LikeErrorCode.LIKE_TARGET_NOT_FOUND) 발생.
  • 기록: 경고 로그 남기고 안전한 placeholder 응답 구성.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df5978f and 70c8828.

📒 Files selected for processing (13)
  • src/main/java/project/flipnote/like/controller/LikeController.java (1 hunks)
  • src/main/java/project/flipnote/like/controller/docs/LikeControllerDocs.java (1 hunks)
  • src/main/java/project/flipnote/like/model/request/LikeSearchRequest.java (1 hunks)
  • src/main/java/project/flipnote/like/model/request/LikeTargetTypeRequest.java (1 hunks)
  • src/main/java/project/flipnote/like/model/response/CardSetLikeResponse.java (1 hunks)
  • src/main/java/project/flipnote/like/model/response/LikeResponse.java (1 hunks)
  • src/main/java/project/flipnote/like/model/response/LikeTargetResponse.java (1 hunks)
  • src/main/java/project/flipnote/like/service/LikePolicyService.java (1 hunks)
  • src/main/java/project/flipnote/like/service/LikeReader.java (1 hunks)
  • src/main/java/project/flipnote/like/service/LikeService.java (6 hunks)
  • src/main/java/project/flipnote/like/service/LikeTargetFetchService.java (1 hunks)
  • src/main/java/project/flipnote/like/service/fetcher/LikeCardSetFetcher.java (2 hunks)
  • src/main/java/project/flipnote/like/service/fetcher/LikeTargetFetcher.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
src/main/java/project/flipnote/like/service/LikeReader.java (1)
src/main/java/project/flipnote/like/service/LikePolicyService.java (1)
  • RequiredArgsConstructor (13-31)
src/main/java/project/flipnote/like/controller/LikeController.java (1)
src/main/java/project/flipnote/like/model/response/LikeTargetResponse.java (1)
  • LikeTargetResponse (3-5)
src/main/java/project/flipnote/like/controller/docs/LikeControllerDocs.java (1)
src/main/java/project/flipnote/like/model/response/LikeTargetResponse.java (1)
  • LikeTargetResponse (3-5)
src/main/java/project/flipnote/like/service/LikeTargetFetchService.java (3)
src/main/java/project/flipnote/like/service/LikePolicyService.java (1)
  • RequiredArgsConstructor (13-31)
src/main/java/project/flipnote/like/service/fetcher/LikeCardSetFetcher.java (1)
  • RequiredArgsConstructor (16-38)
src/main/java/project/flipnote/like/model/response/LikeTargetResponse.java (1)
  • LikeTargetResponse (3-5)
src/main/java/project/flipnote/like/service/LikePolicyService.java (3)
src/main/java/project/flipnote/like/model/response/LikeTargetResponse.java (1)
  • LikeTargetResponse (3-5)
src/main/java/project/flipnote/like/service/LikeTargetFetchService.java (1)
  • RequiredArgsConstructor (19-57)
src/main/java/project/flipnote/like/service/fetcher/LikeCardSetFetcher.java (1)
  • RequiredArgsConstructor (16-38)
src/main/java/project/flipnote/like/service/fetcher/LikeCardSetFetcher.java (1)
src/main/java/project/flipnote/like/model/response/LikeTargetResponse.java (1)
  • LikeTargetResponse (3-5)
src/main/java/project/flipnote/like/service/fetcher/LikeTargetFetcher.java (1)
src/main/java/project/flipnote/like/model/response/LikeTargetResponse.java (1)
  • LikeTargetResponse (3-5)
src/main/java/project/flipnote/like/service/LikeService.java (1)
src/main/java/project/flipnote/like/model/response/LikeTargetResponse.java (1)
  • LikeTargetResponse (3-5)
🔇 Additional comments (11)
src/main/java/project/flipnote/like/service/fetcher/LikeCardSetFetcher.java (2)

13-15: import 경로 변경 일관성 양호

response 서브패키지로의 이동 반영이 올바릅니다.


28-30: 가시성 위임 메서드 추가 적절

도메인 서비스로 위임하는 isTargetViewable 추가 방향성 좋습니다. 서비스가 삭제/비공개/차단 등의 상태를 모두 포함해 false를 반환하는지 확인만 부탁드립니다.

src/main/java/project/flipnote/like/model/response/LikeResponse.java (1)

1-1: 패키지 이동 LGTM — 구 FQN 참조만 재확인 필요
컨트롤러/문서의 제네릭 응답 타입과 일치함. 리포 검색 결과가 빈 출력이라 확증 불가하니 로컬에서 아래 명령을 실행해 구 경로 참조가 없는지 확인.
rg -nP -C2 'project.flipnote.like.model.LikeResponse'
rg -nP -C2 'project.flipnote.like.model.LikeTargetResponse'

src/main/java/project/flipnote/like/model/request/LikeSearchRequest.java (1)

1-1: 패키지 분리 정돈 좋습니다 — 구 패키지 참조 없음 확인
project.flipnote.like.model.LikeSearchRequest 같은 이전 경로 참조는 발견되지 않았고, 현재는 project.flipnote.like.model.request 및 model.response 임포트만 사용되고 있습니다.

src/main/java/project/flipnote/like/model/response/CardSetLikeResponse.java (1)

1-1: 패키지 이동 LGTM — 구 FQN 잔존 참조 없음

project.flipnote.like.model.CardSetLikeResponse에 대한 정확한 FQN 검색 결과 없음; 선언과 사용은 모두 project.flipnote.like.model.response 패키지로 업데이트되어 있습니다.

src/main/java/project/flipnote/like/controller/LikeController.java (1)

18-21: import 경로 갱신 적절 — 구 패키지 미발견(자동 검색 결과)

저장소 전체 검색(rg)에서 project.flipnote.like.model.* 경로가 발견되지 않았습니다. 문서(LikeControllerDocs)와 테스트가 동일 기준으로 갱신되었는지 수동으로 확인하고 CI/테스트를 실행하세요.

src/main/java/project/flipnote/like/model/response/LikeTargetResponse.java (1)

1-5: 패키지 이동 및 기본 형태 유지 LGTM

공개 API의 패키지 재배치만 존재하며, 시그니처 변경 없음. 문제 없습니다.

src/main/java/project/flipnote/like/controller/docs/LikeControllerDocs.java (1)

10-13: import 경로 변경 적합

request/response 서브패키지로 이동한 타입들에 맞춘 import 업데이트 적절합니다.

src/main/java/project/flipnote/like/service/LikeReader.java (1)

12-22: 크로스-파일: 잘못된 에러 코드 도메인 사용 가능성

LikePolicyService.validateTargetExists에서 BookmarkErrorCode.BOOKMARK_TARGET_NOT_FOUND를 던지는 것으로 보입니다. Like 도메인에서 북마크 에러 코드를 사용하는 것은 응답 일관성과 추적성에 문제를 일으킬 수 있습니다. Like 전용 에러 코드 또는 공통 Not Found 코드 사용을 검토해 주세요.

문제 위치 확인 스크립트:

src/main/java/project/flipnote/like/service/LikeService.java (2)

50-50: 가시성 기반의 대상 검증 추가는 적절합니다.

요청자 기준으로 보이는 대상만 좋아요 가능하도록 선검증하는 흐름이 명확합니다.


94-98: 반환 타입 변경(제네릭 제거)은 파급이 큽니다 — 상위 호출부/문서 확인 필요.

PagingResponse<LikeResponse>로 고정되면서 기존 제네릭 기대치와 달라졌을 수 있습니다. 컨트롤러, 문서 스니펫, 프런트 소비 코드가 컴파일/스키마 레벨에서 깨지지 않는지 확인하세요.

Comment on lines 20 to 23
public void validateTargetExists(LikeTargetType targetType, Long targetId, Long userId) {
if (!likeTargetFetchService.isTargetViewable(targetType, targetId, userId)) {
throw new BizException(BookmarkErrorCode.BOOKMARK_TARGET_NOT_FOUND);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Like 도메인의 오류 코드를 사용하세요 (북마크 도메인으로 던짐).

가시성 검증 실패 시 BOOKMARK_TARGET_NOT_FOUND를 던지는 것은 도메인 불일치입니다. Like 흐름에서는 LikeErrorCode 쪽의 대상-부재 코드를 사용해야 API/문서 일관성이 유지됩니다. 또한 현재 메서드명은 “존재”를 암시하나, 실제로는 “가시성(뷰어블)”을 검증하므로 네이밍/주석 정비가 필요합니다.

다음과 같이 수정을 제안합니다(상수명은 기존 코드베이스에 맞춰 확인 필요합니다. 기존에 LIKE_TARGET_NOT_FOUND가 있었다면 그걸 사용하세요).

-    if (!likeTargetFetchService.isTargetViewable(targetType, targetId, userId)) {
-        throw new BizException(BookmarkErrorCode.BOOKMARK_TARGET_NOT_FOUND);
-    }
+    if (!likeTargetFetchService.isTargetViewable(targetType, targetId, userId)) {
+        throw new BizException(LikeErrorCode.LIKE_TARGET_NOT_FOUND);
+    }

추가로, 실질 검증이 “존재”가 아니라 “가시성”이므로 아래 중 하나를 권장합니다.

  • 메서드명 변경: validateTargetViewable(...)
  • 또는 Javadoc으로 “가시성(접근 가능성) 검증”임을 명시

🏁 Script executed:

#!/bin/bash
set -e
echo "Searching for BookmarkErrorCode occurrences..."
rg -n --hidden -S 'BookmarkErrorCode' || true

echo -e "\nSearching for LikeErrorCode occurrences..."
rg -n --hidden -S 'LikeErrorCode' || true

echo -e "\nSearching for LIKE_TARGET_NOT_FOUND or BOOKMARK_TARGET_NOT_FOUND..."
rg -n --hidden -S 'LIKE_TARGET_NOT_FOUND|BOOKMARK_TARGET_NOT_FOUND' || true

echo -e "\nSearching for validateTargetExists / validateTargetViewable..."
rg -n --hidden -S 'validateTargetExists|validateTargetViewable' || true

echo -e "\nLocating LikePolicyService.java..."
rg -n --hidden -S 'class\s+LikePolicyService' || true

if [ -f src/main/java/project/flipnote/like/service/LikePolicyService.java ]; then
  echo -e "\n--- src/main/java/project/flipnote/like/service/LikePolicyService.java ---"
  sed -n '1,240p' src/main/java/project/flipnote/like/service/LikePolicyService.java || true
fi

Length of output: 5522


Like 도메인 오류 코드 사용 및 메서드명 정비

src/main/java/project/flipnote/like/service/LikePolicyService.java(라인 20-23): 가시성 검증 실패에 BookmarkErrorCode.BOOKMARK_TARGET_NOT_FOUND를 던지고 있음 — LikeErrorCode.LIKE_TARGET_NOT_FOUND로 교체하세요.

-    if (!likeTargetFetchService.isTargetViewable(targetType, targetId, userId)) {
-        throw new BizException(BookmarkErrorCode.BOOKMARK_TARGET_NOT_FOUND);
-    }
+    if (!likeTargetFetchService.isTargetViewable(targetType, targetId, userId)) {
+        throw new BizException(LikeErrorCode.LIKE_TARGET_NOT_FOUND);
+    }

메서드명은 '존재'가 아니라 '가시성(viewable)'을 검증하므로 validateTargetViewable(...)로 변경하거나 Javadoc으로 가시성 검증임을 명시하세요.

📝 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.

Suggested change
public void validateTargetExists(LikeTargetType targetType, Long targetId, Long userId) {
if (!likeTargetFetchService.isTargetViewable(targetType, targetId, userId)) {
throw new BizException(BookmarkErrorCode.BOOKMARK_TARGET_NOT_FOUND);
}
public void validateTargetExists(LikeTargetType targetType, Long targetId, Long userId) {
if (!likeTargetFetchService.isTargetViewable(targetType, targetId, userId)) {
throw new BizException(LikeErrorCode.LIKE_TARGET_NOT_FOUND);
}
🤖 Prompt for AI Agents
In src/main/java/project/flipnote/like/service/LikePolicyService.java around
lines 20-23, the method validateTargetExists throws
BookmarkErrorCode.BOOKMARK_TARGET_NOT_FOUND on visibility check failure; change
the thrown error to LikeErrorCode.LIKE_TARGET_NOT_FOUND and rename the method to
validateTargetViewable (or, if you prefer not to rename, add a Javadoc
clarifying it validates "viewable" not mere existence). Update the method
signature, its usages across the codebase (calls/tests), and any imports so they
compile, and run unit tests to ensure all references are adjusted.

Comment on lines +21 to +24
public class LikeTargetFetchService<T extends LikeTargetResponse> {

private final List<LikeTargetFetcher<T>> fetchers;

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

제네릭 불변성으로 인한 Spring 주입 실패 가능성 (치명적).

List<LikeTargetFetcher>는 제네릭 불변(invariant)이라 LikeTargetFetcher가 LikeTargetFetcher에 주입되지 않습니다. 현재 LikeService/LikePolicyService에서 LikeTargetFetchService를 주입하고 있어, 실제로는 컨텍스트 로딩 시점에 주입 실패가 발생할 확률이 높습니다.

아래처럼 서비스의 제네릭을 제거하고, 내부에서 안전한(경고 억제) 캐스팅을 사용하면 주입 문제가 해소됩니다.

-@RequiredArgsConstructor
-@Service
-public class LikeTargetFetchService<T extends LikeTargetResponse> {
+@RequiredArgsConstructor
+@Service
+public class LikeTargetFetchService {

-    private final List<LikeTargetFetcher<T>> fetchers;
+    private final List<LikeTargetFetcher<?>> fetchers;

-    private Map<LikeTargetType, LikeTargetFetcher<T>> fetcherMap;
+    private Map<LikeTargetType, LikeTargetFetcher<?>> fetcherMap;

     @PostConstruct
     public void init() {
-        this.fetcherMap = this.fetchers.stream()
-            .collect(Collectors.toMap(LikeTargetFetcher::getTargetType, Function.identity()));
+        this.fetcherMap = this.fetchers.stream()
+            .collect(Collectors.toMap(LikeTargetFetcher::getTargetType, Function.identity()));
     }

     public boolean isTargetViewable(LikeTargetType targetType, Long targetId, Long userId) {
-        LikeTargetFetcher<T> targetFetcher = getFetcher(targetType);
+        LikeTargetFetcher<?> targetFetcher = getFetcher(targetType);
         return targetFetcher.isTargetViewable(targetId, userId);
     }

-    public Map<Long, T> fetchByTypeAndIds(
+    public <T extends LikeTargetResponse> Map<Long, T> fetchByTypeAndIds(
         LikeTargetType targetType,
         Set<Long> targetIds,
         Long userId
     ) {
-        LikeTargetFetcher<T> targetFetcher = getFetcher(targetType);
-        return targetFetcher.fetchByIds(targetIds, userId);
+        @SuppressWarnings("unchecked")
+        LikeTargetFetcher<T> targetFetcher = (LikeTargetFetcher<T>) getFetcher(targetType);
+        return targetFetcher.fetchByIds(targetIds, userId);
     }

-    private LikeTargetFetcher<T> getFetcher(LikeTargetType targetType) {
-        LikeTargetFetcher<T> fetcher = fetcherMap.get(targetType);
+    private LikeTargetFetcher<?> getFetcher(LikeTargetType targetType) {
+        LikeTargetFetcher<?> fetcher = fetcherMap.get(targetType);
         if (fetcher == null) {
             throw new BizException(LikeErrorCode.INVALID_LIKE_TYPE);
         }
         return fetcher;
     }
 }

이 변경에 맞춰 LikeService/LikePolicyService의 필드 타입도 LikeTargetFetchService (비제네릭)로 조정해야 합니다.

Also applies to: 39-47, 49-56

🤖 Prompt for AI Agents
In src/main/java/project/flipnote/like/service/LikeTargetFetchService.java
around lines 21-24 (and similarly at 39-47, 49-56), the class is declared with a
generic parameter T which makes the injected List<LikeTargetFetcher<T>>
invariant and prevents Spring from injecting implementations like
LikeTargetFetcher<CardSetLikeResponse>; remove the generic from the service
class declaration, change the internal fetchers field to use a wildcard or raw
type (e.g. List<LikeTargetFetcher<?>> or List) and perform any necessary
unchecked casts inside methods with @SuppressWarnings("unchecked") where you
return or operate on a specific LikeTargetResponse subtype, and then update
callers (LikeService and LikePolicyService) to inject LikeTargetFetchService
(non-generic) instead of LikeTargetFetchService<LikeTargetResponse>.

@dungbik dungbik merged commit f6bc2ad into develop Oct 6, 2025
2 checks passed
@dungbik dungbik deleted the refactor/like branch October 6, 2025 11:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants