Skip to content

[refactor] 피드 저장 시 일급컬렉션이 아닌 boolean으로 저장 여부 조회#250

Merged
buzz0331 merged 8 commits into
developfrom
refactor/#162-feed-save
Aug 18, 2025
Merged

[refactor] 피드 저장 시 일급컬렉션이 아닌 boolean으로 저장 여부 조회#250
buzz0331 merged 8 commits into
developfrom
refactor/#162-feed-save

Conversation

@hd0rable
Copy link
Copy Markdown
Member

@hd0rable hd0rable commented Aug 18, 2025

#️⃣ 연관된 이슈

closes #162

📝 작업 내용

  • 피드 저장시 일급컬렉션이 아닌 boolean으로 저장여부 조회를 하도록 수정했습니다.
  • 기존 책 저장시에도 똑같은 로직이었지만 현준님이 이전에 리펙해두셔서 피드 저장만 수정했습니닷

📸 스크린샷

💬 리뷰 요구사항

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

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

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

Summary by CodeRabbit

  • 버그 수정

    • 피드 저장/해제 시 중복 저장 또는 미저장 항목에 대한 검증을 강화해 일관된 오류 메시지와 처리 결과를 제공합니다.
    • 중복 피드 관련 서버 오류 코드가 제거되어 관련 오류 발생 가능성을 줄였습니다.
  • 리팩터링

    • 저장 상태 확인을 경량화해 불필요한 데이터 로딩을 제거하고 처리 흐름을 단순화하여 성능과 안정성을 개선했습니다.
    • 저장 여부 확인 API를 정비해 저장/해제 요청 처리 로직을 간소화했습니다.
  • 기타

    • 저장 여부 조회 시 파라미터 바인딩을 명확히 해 존재 확인 정확도를 향상시켰습니다.
    • 배포 스크립트의 정리 동작을 부분적으로 변경했습니다.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 18, 2025

Walkthrough

피드 저장 로직을 SavedFeeds 일급컬렉션 기반에서 boolean 존재 여부 검사 기반으로 전환했고, 이에 따라 도메인 객체 및 관련 에러 코드가 제거되었으며 포트/어댑터/레포지토리에 존재 여부 조회 API가 추가되고 서비스 로직이 해당 흐름으로 변경되었습니다.

Changes

Cohort / File(s) Change Summary
ErrorCode 정리
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java
DUPLICATED_FEEDS_IN_COLLECTION enum 값 제거
피드 조회 퍼시스턴스 어댑터
src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedQueryPersistenceAdapter.java
SavedFeeds 반환 메서드 제거; findSavedFeedIdsByUserIdAndFeedIds(...) 추가; existsSavedFeedByUserIdAndFeedId(...) 추가
SavedFeed 레포지토리
src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/SavedFeedJpaRepository.java
existsByUserIdAndFeedId(Long, Long) JPQL 존재 여부 메서드 추가
쿼리 포트
src/main/java/konkuk/thip/feed/application/port/out/FeedQueryPort.java
findSavedFeedsByUserId(...) 제거; existsSavedFeedByUserIdAndFeedId(...) 추가
서비스 로직
src/main/java/konkuk/thip/feed/application/service/FeedSavedService.java
저장/삭제 검증을 SavedFeeds 대신 boolean 조회 기반으로 변경; 중앙화된 상태 검증 메서드 추가; BusinessException + ErrorCode 사용
도메인 제거
src/main/java/konkuk/thip/feed/domain/SavedFeeds.java
SavedFeeds 일급 컬렉션 클래스 삭제
도서 저장 레포지토리
src/main/java/konkuk/thip/book/adapter/out/persistence/repository/SavedBookJpaRepository.java
existsByUserIdAndBookId 메서드의 파라미터에 @Param 추가로 named parameter 바인딩 명시
CI/CD 워크플로우
.github/workflows/cd-workflow-dev.yml
기존 docker-compose down --rmi all 호출을 주석 처리하고 특정 컨테이너 stop + rm 명령으로 교체(관련 라인 변경)

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Controller
  participant FeedSavedService
  participant FeedQueryPort
  participant FeedCommandPort

  User->>Controller: changeSavedFeed(command)
  Controller->>FeedSavedService: changeSavedFeed(command)
  FeedSavedService->>FeedQueryPort: existsSavedFeedByUserIdAndFeedId(userId, feedId)
  FeedQueryPort-->>FeedSavedService: alreadySaved (boolean)
  FeedSavedService->>FeedSavedService: validateSaveFeedAction(isSaveRequest, alreadySaved)
  alt isSaveRequest == true
    FeedSavedService->>FeedCommandPort: saveSavedFeed(userId, feedId)
  else
    FeedSavedService->>FeedCommandPort: deleteSavedFeed(userId, feedId)
  end
  FeedSavedService-->>Controller: result(command.isSaved)
  Controller-->>User: response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
피드 저장 로직을 일급컬렉션이 아닌 boolean 조회로 수정 (#162)
책 저장 로직을 일급컬렉션이 아닌 boolean 조회로 수정 (#162) PR 내 변경은 @Param 바인딩 명시만 포함되어 있으며 책 저장 로직을 일급컬렉션→boolean으로 전환하는 구현이 없음

Out-of-scope changes

Code Change Explanation
CI/CD cleanup 명령 변경 (.github/workflows/cd-workflow-dev.yml) 배포/워크플로우 스크립트 변경은 linked issue(#162)의 리팩터링 목표(피드/책 저장 로직)와 관련이 없음 — 인프라/배포 조치로 보임 (파일: .github/workflows/cd-workflow-dev.yml).

Possibly related PRs

Suggested reviewers

  • seongjunnoh

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
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/#162-feed-save

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
🪧 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

@hd0rable hd0rable requested a review from seongjunnoh August 18, 2025 06:54
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Aug 18, 2025

Test Results

407 tests   407 ✅  31s ⏱️
121 suites    0 💤
121 files      0 ❌

Results for commit 0ac4b6d.

♻️ This comment has been updated with latest results.

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: 2

🧹 Nitpick comments (2)
src/main/java/konkuk/thip/feed/application/port/out/FeedQueryPort.java (1)

41-41: existsSavedFeedByUserIdAndFeedId 추가: 방향성 좋습니다만, 유사 API와 파라미터 순서 일관성 고려 권장

신규 exists API는 본 PR 목적과 잘 맞습니다. 다만 동일 섹션의 findSavedFeedIdsByUserIdAndFeedIds(Set<Long> feedIds, Long userId)와 파라미터 순서가 서로 달라(여기선 userId, feedId 순 / 다른 메서드는 feedIds, userId 순) 혼선을 유발할 수 있습니다. 장기적으로는 두 메서드 모두 userId를 앞에 두는 방향으로 통일하는 것을 권장합니다. (추후 마이그레이션을 위해 새 시그니처를 추가하고, 기존은 Deprecated 처리하는 전략 권장)

src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedQueryPersistenceAdapter.java (1)

103-105: 인자 순서 스왑으로 인한 가독성 저하 가능성

포트 시그니처가 (Set<Long> feedIds, Long userId)이고, 레포지토리 시그니처가 (Long userId, Set<Long> feedIds)라 어댑터에서 순서를 뒤집어 호출하고 있습니다. 현재 구현은 올바르게 매핑하고 있으나, 추후 유지보수 시 실수 가능성이 있어 포트/레포지토리/서비스 전반의 파라미터 순서를 userId 우선으로 통일하는 것을 권장합니다. (이번 PR 범위를 넘는다면 후속 리팩터로도 충분합니다)

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

📥 Commits

Reviewing files that changed from the base of the PR and between 484749e and d11416c.

📒 Files selected for processing (6)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (0 hunks)
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedQueryPersistenceAdapter.java (1 hunks)
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/SavedFeedJpaRepository.java (1 hunks)
  • src/main/java/konkuk/thip/feed/application/port/out/FeedQueryPort.java (2 hunks)
  • src/main/java/konkuk/thip/feed/application/service/FeedSavedService.java (2 hunks)
  • src/main/java/konkuk/thip/feed/domain/SavedFeeds.java (0 hunks)
💤 Files with no reviewable changes (2)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java
  • src/main/java/konkuk/thip/feed/domain/SavedFeeds.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/konkuk/thip/feed/application/service/FeedSavedService.java (4)
src/main/java/konkuk/thip/feed/application/service/BasicFeedShowAllService.java (1)
  • Service (20-65)
src/main/java/konkuk/thip/feed/application/service/FollowingPriorityFeedShowAllService.java (1)
  • Service (20-64)
src/main/java/konkuk/thip/feed/application/service/FeedShowAllOfUserService.java (1)
  • Service (20-72)
src/main/java/konkuk/thip/feed/application/service/FeedShowSingleService.java (1)
  • Service (19-50)
⏰ 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 (3)
src/main/java/konkuk/thip/feed/application/port/out/FeedQueryPort.java (1)

52-55: LGTM — 모든 태그 조회 시그니처 유지

기존 시그니처를 유지하면서 위치만 재정렬된 것으로 보이며 문제 없습니다.

src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedQueryPersistenceAdapter.java (1)

108-110: LGTM — 존재 여부 위임 구현 적절

existsSavedFeedByUserIdAndFeedId를 레포지토리로 위임하는 구현이 간결하고 목적에 부합합니다.

src/main/java/konkuk/thip/feed/application/service/FeedSavedService.java (1)

43-50: 검증 로직 분리 좋습니다

validateSaveFeedAction으로 상태 전이 규칙을 명확히 캡슐화한 점 좋습니다. 예외 코드도 구체적이라 추적/대응 용이합니다.

Comment on lines +25 to +41
public FeedIsSavedResult changeSavedFeed(FeedIsSavedCommand command) {

// 1. 피드 검증 및 조회
Feed feed = feedCommandPort.getByIdOrThrow(feedIsSavedCommand.feedId());
Feed feed = feedCommandPort.getByIdOrThrow(command.feedId());

// 2. 유저가 저장한 피드 목록 조회
SavedFeeds savedFeeds = feedQueryPort.findSavedFeedsByUserId(feedIsSavedCommand.userId());
// 2. 유저가 해당 피드를 저장했는지 여부 조회
boolean alreadySaved = feedQueryPort.existsSavedFeedByUserIdAndFeedId(command.userId(), feed.getId());
validateSaveFeedAction(command.isSaved(), alreadySaved);

if (feedIsSavedCommand.isSaved()) {
// 저장 요청 시 이미 저장되어 있으면 예외 발생
savedFeeds.validateNotAlreadySaved(feed);
feedCommandPort.saveSavedFeed(feedIsSavedCommand.userId(), feed.getId());
if (command.isSaved()) {
feedCommandPort.saveSavedFeed(command.userId(), feed.getId());
} else {
// 삭제 요청 시 저장되어 있지 않으면 예외 발생
savedFeeds.validateCanDelete(feed);
feedCommandPort.deleteSavedFeed(feedIsSavedCommand.userId(), feed.getId());
feedCommandPort.deleteSavedFeed(command.userId(), feed.getId());
}

return FeedIsSavedResult.of(feed.getId(), feedIsSavedCommand.isSaved());
return FeedIsSavedResult.of(feed.getId(), command.isSaved());
}
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

check-then-act 경쟁 상태 가능성: 유니크 제약 및 예외 매핑으로 보강 권장

현재 플로우는

  • Line 31-33: 사전 존재 여부 조회
  • Line 34-38: 결과에 따라 저장/삭제

형태의 check-then-act 패턴입니다. 동시 요청이 들어오면, 존재 여부 확인 직후 상태가 바뀌는 경쟁 상태가 발생할 수 있습니다. 이를 안전하게 만들기 위해 다음을 권장합니다.

필수 보강(권장 구현):

  • SavedFeed(userId, feedId) 조합에 DB 유니크 제약을 두고,
  • 저장 시 DataIntegrityViolationException을 잡아 FEED_ALREADY_SAVED로 변환하여 최종 일관성을 보장

예시 패치:

@@
-        if (command.isSaved()) {
-            feedCommandPort.saveSavedFeed(command.userId(), feed.getId());
-        } else {
-            feedCommandPort.deleteSavedFeed(command.userId(), feed.getId());
-        }
+        if (command.isSaved()) {
+            try {
+                feedCommandPort.saveSavedFeed(command.userId(), feed.getId());
+            } catch (org.springframework.dao.DataIntegrityViolationException e) {
+                // 동시성 등으로 인한 중복 저장 시도를 비즈니스 예외로 변환
+                throw new BusinessException(FEED_ALREADY_SAVED);
+            }
+        } else {
+            // 사전 검증으로 not-saved 삭제는 방지되나, 동시성으로 사라진 경우도 고려하여
+            // deleteSavedFeed 구현이 영향 행 수를 반환한다면 체크/로깅을 고려할 수 있습니다.
+            feedCommandPort.deleteSavedFeed(command.userId(), feed.getId());
+        }

또한, SavedFeed 테이블에 유니크 인덱스가 없으면 위 보강만으로는 완전하지 않습니다. 아래 스크립트로 유니크 제약 및 예외 매핑 유무를 확인해 주세요. 필요 시 후속 PR에서 제약 추가/예외 매핑을 도와드릴 수 있습니다.


🏁 Script executed:

#!/bin/bash
# SavedFeed 유니크 제약 및 저장/삭제 구현에서의 예외 매핑 여부 확인

echo "== SavedFeedJpaEntity 정의 위치 =="
rg -n "class\s+SavedFeedJpaEntity\b" -C2

echo
echo "== @Table(uniqueConstraints=...), unique=true 등 유니크 제약 여부 =="
rg -n "@Table\(" -C2
rg -n "uniqueConstraints" -C2
rg -n "unique\s*=\s*true" -C2

echo
echo "== saveSavedFeed/deleteSavedFeed 구현 및 예외 매핑 흔적 =="
rg -n "saveSavedFeed\s*\(" -C3
rg -n "deleteSavedFeed\s*\(" -C3
rg -n "DataIntegrityViolationException|DuplicateKeyException|FEED_ALREADY_SAVED|FEED_NOT_SAVED_CANNOT_DELETE" -C2

Length of output: 21004


DB 유니크 제약 및 예외 처리 보강 필요

현재 SavedFeed 엔티티에는 (user_id, feed_id) 복합 유니크 제약이 없고, 저장 로직에서도 동시 중복 저장 시 발생하는 예외를 잡아 변환하는 처리가 빠져 있습니다. 경쟁 상태를 방지하고 최종 일관성을 보장하기 위해 아래와 같이 반영을 권장합니다.

  • src/main/java/konkuk/thip/feed/adapter/out/jpa/SavedFeedJpaEntity.java

    • @table 어노테이션에 uniqueConstraints 추가
      @Entity
      @Table(
        name = "saved_feeds",
        uniqueConstraints = @UniqueConstraint(
          name = "uq_saved_feeds_user_feed",
          columnNames = {"user_id", "feed_id"}
        )
      )
      public class SavedFeedJpaEntity extends BaseJpaEntity { … }
  • src/main/java/konkuk/thip/feed/application/service/FeedSavedService.java

    • feedCommandPort.saveSavedFeed(...) 호출부를 DataIntegrityViolationException 핸들링으로 래핑
       if (command.isSaved()) {
      -    feedCommandPort.saveSavedFeed(command.userId(), feed.getId());
      +    try {
      +        feedCommandPort.saveSavedFeed(command.userId(), feed.getId());
      +    } catch (DataIntegrityViolationException e) {
      +        // 동시 중복 저장 시 비즈니스 예외로 변환
      +        throw new BusinessException(FEED_ALREADY_SAVED);
      +    }
       } else {
         feedCommandPort.deleteSavedFeed(command.userId(), feed.getId());
       }
  • 선택 사항: delete 로직도 long deleteByUserIdAndFeedId(...) 처럼 삭제 행 수를 반환하도록 리포지토리 시그니처를 변경하고, 0건 삭제 시 로깅 또는 예외 처리 고려

위 보강 작업 후, FeedChangeSavedApiTest에서 중복 저장/삭제 시나리오가 여전히 올바르게 동작하는지 재검증해주세요.

🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/feed/application/service/FeedSavedService.java
around lines 25–41, the save path does not handle DB uniqueness violations under
concurrent saves; wrap the call to feedCommandPort.saveSavedFeed(...) in a
try-catch that catches DataIntegrityViolationException (or the project's
equivalent), treat it as idempotent (log and continue or translate to a
domain-safe response) so concurrent duplicate-insert errors do not bubble up,
and preserve current return semantics; additionally consider changing delete
flow to use a deleteByUserIdAndFeedId(...) that returns deleted count and log or
handle a 0-delete result. Also add the unique constraint to
src/main/java/konkuk/thip/feed/adapter/out/jpa/SavedFeedJpaEntity.java as
described by the reviewer to enforce (user_id, feed_id) uniqueness at the DB
level.

buzz0331
buzz0331 previously approved these changes Aug 18, 2025
Copy link
Copy Markdown
Contributor

@buzz0331 buzz0331 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 +43 to +50
private void validateSaveFeedAction(boolean isSaveRequest, boolean alreadySaved) {
if (isSaveRequest && alreadySaved) {
// 이미 저장되어 있는 피드를 다시 저장하려는 경우 예외 처리
throw new BusinessException(FEED_ALREADY_SAVED);
} else if (!isSaveRequest && !alreadySaved) {
// 저장되지 않은 피드를 삭제하려는 경우 예외 처리
throw new BusinessException(FEED_NOT_SAVED_CANNOT_DELETE);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

굿굿 깔끔쓰하네여

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: 0

🔭 Outside diff range comments (1)
src/main/java/konkuk/thip/book/adapter/out/persistence/repository/SavedBookJpaRepository.java (1)

15-16: DELETE @query에 @Param 누락 — 런타임 바인딩 실패 가능성 높음

동일 파일 내 exists 메서드는 @Param을 추가하셨는데, delete 메서드는 네임드 파라미터(:userId, :bookId)를 사용하면서 @Param이 없습니다. 컴파일 옵션에 따라 메서드 파라미터 이름이 유지되지 않으면 런타임에 "Named parameter not bound" 예외가 발생할 수 있습니다. exists와 동일하게 @Param을 추가해 주세요.

아래 패치 제안:

-    void deleteByUserIdAndBookId(Long userId, Long bookId);
+    void deleteByUserIdAndBookId(@Param("userId") Long userId, @Param("bookId") Long bookId);
🧹 Nitpick comments (2)
src/main/java/konkuk/thip/book/adapter/out/persistence/repository/SavedBookJpaRepository.java (2)

10-12: exists 쿼리는 파생 메서드로 더 단순화 가능 (선택사항)

현재 @query + CASE WHEN COUNT 패턴도 동작에는 문제 없습니다. 다만 Spring Data JPA의 파생 메서드를 사용하면 쿼리 문자열을 제거하고 유지보수를 단순화할 수 있습니다.

아래처럼 파생 메서드로 대체 가능:

-    @Query("SELECT CASE WHEN COUNT(s) > 0 THEN true ELSE false END FROM SavedBookJpaEntity s " +
-           "WHERE s.userJpaEntity.userId = :userId AND s.bookJpaEntity.bookId = :bookId")
-    boolean existsByUserIdAndBookId(@Param("userId") Long userId, @Param("bookId") Long bookId);
+    boolean existsByUserJpaEntity_UserIdAndBookJpaEntity_BookId(Long userId, Long bookId);

참고:

  • 중첩 프로퍼티 경로는 언더스코어(_)로 구분해 탐색 가능합니다.
  • 쿼리 제거로 리팩터링 비용 및 오타 리스크를 낮출 수 있습니다.

10-16: 조회/삭제 조건 칼럼(userId, bookId)에 인덱스/유니크 제약 고려 (선택사항)

  • 빈번한 exists/delete 패턴이므로 (user_id, book_id) 복합 인덱스가 있으면 I/O를 줄일 수 있습니다.
  • 동일 사용자-도서 중복 저장을 방지하려면 유니크 제약까지 고려해 보세요. 애그리게이트 규칙을 DB 레벨에서 강제할 수 있습니다.
📜 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.

📥 Commits

Reviewing files that changed from the base of the PR and between d11416c and 5199172.

📒 Files selected for processing (2)
  • src/main/java/konkuk/thip/book/adapter/out/persistence/repository/SavedBookJpaRepository.java (1 hunks)
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/SavedFeedJpaRepository.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/SavedFeedJpaRepository.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 (2)
src/main/java/konkuk/thip/book/adapter/out/persistence/repository/SavedBookJpaRepository.java (2)

7-7: @Param import 추가 적절

네임드 파라미터 바인딩을 위해 필요한 의존성 추가가 정확합니다.


14-16: @Modifying 쿼리의 트랜잭션 경계 보장 확인 요청

@Modifying 메서드는 트랜잭션 내에서 실행되어야 합니다. 상위 서비스 계층에 @transactional이 적용되어 있는지 확인 부탁드립니다. 없다면 레포지토리 인터페이스(권장 X) 대신 호출하는 서비스 메서드에 @transactional을 명시해 주세요.

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: 1

🧹 Nitpick comments (1)
.github/workflows/cd-workflow-dev.yml (1)

59-60: 서드파티 액션 버전 고정 제안

appleboy/ssh-action@master는 가변적입니다. 보안/재현성을 위해 태그(권장)나 커밋 SHA로 pinning 해 주세요. 예: appleboy/ssh-action@v0.1.x 또는 특정 SHA.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5199172 and 0ac4b6d.

📒 Files selected for processing (1)
  • .github/workflows/cd-workflow-dev.yml (1 hunks)
🧰 Additional context used
🪛 YAMLlint (1.37.1)
.github/workflows/cd-workflow-dev.yml

[error] 72-72: syntax error: expected , but found ''

(syntax)

⏰ 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

Comment on lines +71 to +73
# sudo docker-compose down --rmi all
sudo docker-compose stop ${{ secrets.DOCKER_IMAGE }}
sudo docker rm -f ${{ secrets.DOCKER_IMAGE }}
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

YAML 블록 들여쓰기 오류 + 잘못된 대상(이미지명)으로 컨테이너 중지/삭제 시도

  • Line 71: script: | 블록 내부 줄은 반드시 들여쓰기 되어야 합니다. 현재 줄 시작에 들여쓰기가 없어 블록이 조기 종료되어 YAML 파싱 오류가 납니다. YAMLlint 에러(“expected …”)가 이 문제를 가리킵니다.
  • Lines 72-73: docker-compose stop은 “서비스명” 기준이며, docker rm은 “컨테이너명/ID” 기준입니다. secrets.DOCKER_IMAGE는 일반적으로 “이미지명”이라 중지/삭제 대상이 달라 실행 실패할 가능성이 높습니다.

단순하고 안전한 방법은 compose 프로젝트 단위로 컨테이너를 중지/정리하는 것입니다. 아래처럼 교체하면:

  • YAML 들여쓰기 문제를 해소
  • 대상 식별 오류를 제거
  • 이미지는 유지(아래에서 prune로 정리됨)

적용 제안:

-#            sudo docker-compose down --rmi all
-            sudo docker-compose stop ${{ secrets.DOCKER_IMAGE }}
-            sudo docker rm -f ${{ secrets.DOCKER_IMAGE }}
+            # docker-compose down --rmi all  # 이미지는 유지하고 컨테이너만 교체합니다 (이미지는 아래 prune에서 정리)
+            sudo docker-compose down --remove-orphans

참고:

  • 특정 서비스만 중지/삭제하려면 “이미지명”이 아니라 “서비스명”이 필요합니다. 해당 경우 별도 secret(예: DOCKER_SERVICE)을 두고 docker-compose stop ${{ secrets.DOCKER_SERVICE }} / docker-compose rm -f ${{ secrets.DOCKER_SERVICE }} 형태로 사용하세요.
📝 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
# sudo docker-compose down --rmi all
sudo docker-compose stop ${{ secrets.DOCKER_IMAGE }}
sudo docker rm -f ${{ secrets.DOCKER_IMAGE }}
# docker-compose down --rmi all # 이미지는 유지하고 컨테이너만 교체합니다 (이미지는 아래 prune에서 정리)
sudo docker-compose down --remove-orphans
🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 72-72: syntax error: expected , but found ''

(syntax)

🤖 Prompt for AI Agents
In .github/workflows/cd-workflow-dev.yml around lines 71–73 the lines inside the
script: | block are not indented and therefore break YAML parsing, and the
commands use secrets.DOCKER_IMAGE (an image name) with docker-compose stop /
docker rm which expect a service name and a container id/name respectively. Fix
by indenting the lines so they are inside the script block, and replace the two
lines with project-level compose teardown (e.g. run docker-compose stop and
docker-compose rm -f with no argument or run docker-compose down) so you
stop/remove the compose project instead of referencing an image; if you really
need to target a single service, introduce a secret like DOCKER_SERVICE and call
docker-compose stop ${{ secrets.DOCKER_SERVICE }} / docker-compose rm -f ${{
secrets.DOCKER_SERVICE }}.

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 +71 to +73
# sudo docker-compose down --rmi all
sudo docker-compose stop ${{ secrets.DOCKER_IMAGE }}
sudo docker rm -f ${{ secrets.DOCKER_IMAGE }}
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

Choose a reason for hiding this comment

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

기존에는 mysql redis까지 모두 내렸다가 다시 올렸는데 생각해보니 두 컨테이너는 굳이 내릴 필요가 없다고 판단해서 그냥 서버 컨테이너만 내렸다가 올리려고 합니다!

Comment on lines +30 to +32
// 2. 유저가 해당 피드를 저장했는지 여부 조회
boolean alreadySaved = feedQueryPort.existsSavedFeedByUserIdAndFeedId(command.userId(), feed.getId());
validateSaveFeedAction(command.isSaved(), alreadySaved);
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.

굳굳

@buzz0331 buzz0331 merged commit 2b774c7 into develop Aug 18, 2025
4 checks passed
@buzz0331 buzz0331 deleted the refactor/#162-feed-save branch August 18, 2025 13:24
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-220] [refactor] 피드/책 저장 로직 수정

3 participants