Skip to content

Fix/#54 mark processed on lazy trip plan creation#55

Open
toychip wants to merge 3 commits intomainfrom
fix/#54-mark-processed-on-lazy-trip-plan-creation
Open

Fix/#54 mark processed on lazy trip plan creation#55
toychip wants to merge 3 commits intomainfrom
fix/#54-mark-processed-on-lazy-trip-plan-creation

Conversation

@toychip
Copy link
Copy Markdown
Contributor

@toychip toychip commented Apr 2, 2026

관련 이슈


변경 내용

1. Lazy 생성 경로 processed 미반영 문제 수정

  • TripPlanService에서 Lazy 생성 이후
    TripPlanRequest.processed 상태를 명시적으로 업데이트하도록 수정
  • 동일 videoAnalysisTaskId + memberId 조건의 미처리 요청 조회 후 일괄 처리

2. 상태 변경 책임을 도메인으로 이동

  • TripPlanRequest.markProcessed() 메서드 추가
  • 외부에서 상태를 직접 변경하지 않고, 도메인 메서드를 통해 처리하도록 개선

3. Persistence 구조 개선

  • markAsProcessed(ids) 제거
  • saveAll(requests) 기반으로 변경하여
    도메인 상태 변경 → 저장 흐름으로 일관성 유지

4. EventListener 로직 단순화

  • processedIds 제거
  • 요청 객체에 직접 markProcessed() 수행 후 saveAll 호출
  • 성공 카운트 로직을 processed 상태 기반으로 변경

5. 네이밍 개선

  • markAsProcessedmarkBatchProcessed (이전 커밋)
  • 의미를 더 명확하게 표현하도록 수정

체크리스트

  • Ktlint
  • 테스트 통과 여부

Summary by CodeRabbit

릴리스 노트

  • Refactor

    • 여행 계획 요청의 처리 상태 추적 메커니즘을 개선했습니다.
    • 요청 상태 관리 방식을 업데이트하여 일관성과 신뢰성을 강화했습니다.
  • Chores

    • 코드 포매팅 정리

@toychip toychip linked an issue Apr 2, 2026 that may be closed by this pull request
6 tasks
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 2, 2026

📝 Walkthrough

Walkthrough

TripPlanRequestprocessed 속성이 불변에서 가변으로 변경되었으며, markProcessed() 메서드가 추가되었습니다. 이에 따라 비동기 이벤트 리스너와 서비스 로직에서 처리 상태를 객체 내에서 직접 표시하고 저장하는 방식으로 변경되었으며, 지속성 포트는 ID 기반 표시 메서드를 전체 객체 저장 메서드로 대체했습니다.

Changes

Cohort / File(s) Summary
Domain Model Update
linktrip-application/src/main/kotlin/com/linktrip/application/domain/trip/TripPlanRequest.kt
processed 속성을 val에서 var로 변경하고, 인스턴스 메서드 markProcessed()를 추가하여 처리 상태를 변경 가능하게 만들었습니다.
Service & Event Listener Logic
linktrip-application/src/main/kotlin/com/linktrip/application/domain/trip/TripPlanService.kt, linktrip-application/src/main/kotlin/com/linktrip/application/domain/video/VideoAnalyzeEventListener.kt
미처리 요청을 조회한 후 각각에 대해 markProcessed()를 호출하고, 새로운 saveAll() 메서드를 사용하여 객체 단위로 지속성 포트에 저장하도록 변경했습니다.
Persistence Port & Adapter
linktrip-application/src/main/kotlin/com/linktrip/application/port/output/persistence/TripPlanRequestPersistencePort.kt, linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/adapter/TripPlanRequestPersistenceAdapter.kt
markAsProcessed(ids: List<String>) 메서드를 saveAll(requests: List<TripPlanRequest>) 메서드로 대체하여, ID 기반에서 도메인 객체 기반 저장 방식으로 전환했습니다.
Minor Formatting
linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/repository/TripPlanRequestQuerydslRepository.kt
파일 끝에 줄바꿈만 추가되었습니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 분

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 PR의 주요 변경사항(lazy trip plan 생성 시 processed 상태 업데이트 수정)을 명확하게 나타내며, 변경 내용과 직접적으로 관련이 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/#54-mark-processed-on-lazy-trip-plan-creation

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@linktrip-application/src/main/kotlin/com/linktrip/application/domain/trip/TripPlanService.kt`:
- Around line 71-74: The current registerRequest() leaves some requests
unprocessed when an existing TripPlan exists because the unprocessed-request
cleanup (requestPort.findUnprocessedByVideoAnalysisTaskId(...).filter {
it.memberId == memberId }.onEach { it.markProcessed() } and
requestPort.saveAll(...)) runs only after creating a new TripPlan; move that
retrieval/mark-and-save block to execute before the early-return that checks for
an existing TripPlan so cleanup always runs regardless of whether a new plan is
created, keeping the same calls to markProcessed() and saveAll(...) and using
the same memberId/videoAnalysisTaskId filtering.
- Around line 71-74: Ktlint is failing because the chained call after the
assignment in TripPlanService (the expression starting with
requestPort.findUnprocessedByVideoAnalysisTaskId(...)) begins on the same line
as the `=`; split the expression into a multiline assignment so the chain starts
on the next line, e.g. place the call to
requestPort.findUnprocessedByVideoAnalysisTaskId(videoAnalysisTaskId) on a new
indented line and keep subsequent .filter { ... } and .onEach { ... } each on
their own lines, then call requestPort.saveAll(unprocessedRequests); this fixes
the line-break rule without changing logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ffcb5390-f46e-4c52-830e-43264711243e

📥 Commits

Reviewing files that changed from the base of the PR and between 643e941 and 5b6e702.

📒 Files selected for processing (6)
  • linktrip-application/src/main/kotlin/com/linktrip/application/domain/trip/TripPlanRequest.kt
  • linktrip-application/src/main/kotlin/com/linktrip/application/domain/trip/TripPlanService.kt
  • linktrip-application/src/main/kotlin/com/linktrip/application/domain/video/VideoAnalyzeEventListener.kt
  • linktrip-application/src/main/kotlin/com/linktrip/application/port/output/persistence/TripPlanRequestPersistencePort.kt
  • linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/adapter/TripPlanRequestPersistenceAdapter.kt
  • linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/repository/TripPlanRequestQuerydslRepository.kt
📜 Review details
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-03-18T01:08:05.661Z
Learnt from: toychip
Repo: Link-Trip/BackEnd PR: 24
File: linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/adapter/YouTubeVideoPersistenceAdapter.kt:16-28
Timestamp: 2026-03-18T01:08:05.661Z
Learning: Similarly, `linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/adapter/YouTubeChannelPersistenceAdapter.kt` likely has the same deferred concurrent-write policy (single server, ShedLock planned for replication phase).

Applied to files:

  • linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/repository/TripPlanRequestQuerydslRepository.kt
📚 Learning: 2026-03-18T01:07:53.575Z
Learnt from: toychip
Repo: Link-Trip/BackEnd PR: 24
File: linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/adapter/YouTubeVideoPersistenceAdapter.kt:16-28
Timestamp: 2026-03-18T01:07:53.575Z
Learning: Apply the same deferred concurrent-write policy to all Kotlin persistence adapters in the MySQL adapter package: ensure writes are coordinated on a single server with ShedLock considered for the replication phase. For YouTubeVideoPersistenceAdapter.kt (and other adapters in this directory), verify that concurrent writes are serialized or properly guarded, document the policy in code comments, and ensure CI checks or deployment gating will catch any regression where multiple instances might attempt a conflicting write during replication.

Applied to files:

  • linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/adapter/TripPlanRequestPersistenceAdapter.kt
📚 Learning: 2026-03-18T01:08:05.661Z
Learnt from: toychip
Repo: Link-Trip/BackEnd PR: 24
File: linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/adapter/YouTubeVideoPersistenceAdapter.kt:16-28
Timestamp: 2026-03-18T01:08:05.661Z
Learning: In `linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/adapter/YouTubeVideoPersistenceAdapter.kt`, the concurrent-write safety (duplicate videoId in batch, race condition on uk_youtube_video_video_id) is intentionally deferred. The maintainer (toychip) confirmed the system is currently single-server, so this is not a concern yet. When replication is introduced in the future, ShedLock will be used for distributed locking to address this. At that point, in-batch videoId deduplication should also be applied.

Applied to files:

  • linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/adapter/TripPlanRequestPersistenceAdapter.kt
🪛 GitHub Actions: CI - Pull request
linktrip-application/src/main/kotlin/com/linktrip/application/domain/trip/TripPlanService.kt

[error] 71-71: ktlintMainSourceSetCheck failed (ktlint). Violation at :71:35: "A multiline expression should start on a new line".

🔇 Additional comments (4)
linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/repository/TripPlanRequestQuerydslRepository.kt (1)

33-33: 변경 사항 이상 없습니다.

Line 33의 개행 추가만 있으며, 동작/쿼리 로직 영향은 없습니다.

linktrip-application/src/main/kotlin/com/linktrip/application/domain/trip/TripPlanRequest.kt (1)

14-16: 상태 전이를 도메인 메서드로 모은 방향 좋습니다.

호출부가 processed 플래그를 직접 건드리지 않고 의도를 드러내게 돼서 읽기 쉬워졌습니다.

linktrip-application/src/main/kotlin/com/linktrip/application/domain/video/VideoAnalyzeEventListener.kt (1)

92-92: markProcessed()를 바로 쓰도록 정리한 점 좋습니다.

보조 컬렉션 없이 요청 객체 상태로 흐름을 맞춰서 처리/로그 계산이 더 단순해졌습니다.

linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/adapter/TripPlanRequestPersistenceAdapter.kt (1)

30-31: 부분 엔티티 스냅샷으로 인한 감사 상태 덮어쓰기 우려는 현재 구현에서 발생하지 않습니다.

TripPlanRequestEntity의 설계가 이미 이 문제를 방지하고 있습니다:

  • createdAt: @Column(updatable = false) 지정으로 JPA가 MERGE 중 업데이트하지 않음
  • updatedAt: @LastModifiedDate 데코레이터가 자동 관리
  • deleted: protected set 지정으로 외부에서 직접 수정 불가

jpaRepository.saveAll(requests.map { TripPlanRequestEntity.from(it) })을 호출해도 감사 필드와 soft-delete 상태는 보호됩니다. 부분 엔티티로 MERGE될 때 JPA는 명시적으로 채워진 필드(id, memberId, videoAnalysisTaskId, processed)만 적용하고 감사 필드는 무시합니다.

Comment on lines +71 to +74
val unprocessedRequests = requestPort.findUnprocessedByVideoAnalysisTaskId(videoAnalysisTaskId)
.filter { it.memberId == memberId }
.onEach { it.markProcessed() }
requestPort.saveAll(unprocessedRequests)
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 | 🟠 Major

요청 정리를 새 플랜 생성 뒤에만 두면 미처리 요청이 남습니다.

Lines 71-74는 새 TripPlan을 만든 경우에만 실행됩니다. 그런데 registerRequest()는 기존 플랜 존재 여부를 보지 않고 요청을 저장하므로(Lines 29-35), 이미 플랜이 있는 상태에서는 Lines 45-47에서 바로 반환되어 processed=false가 그대로 남습니다. 멤버+task 기준 요청 조회/정리를 early-return 앞쪽으로 옮겨서 기존 플랜 경로도 같이 정리해주세요.

🧰 Tools
🪛 GitHub Actions: CI - Pull request

[error] 71-71: ktlintMainSourceSetCheck failed (ktlint). Violation at :71:35: "A multiline expression should start on a new line".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@linktrip-application/src/main/kotlin/com/linktrip/application/domain/trip/TripPlanService.kt`
around lines 71 - 74, The current registerRequest() leaves some requests
unprocessed when an existing TripPlan exists because the unprocessed-request
cleanup (requestPort.findUnprocessedByVideoAnalysisTaskId(...).filter {
it.memberId == memberId }.onEach { it.markProcessed() } and
requestPort.saveAll(...)) runs only after creating a new TripPlan; move that
retrieval/mark-and-save block to execute before the early-return that checks for
an existing TripPlan so cleanup always runs regardless of whether a new plan is
created, keeping the same calls to markProcessed() and saveAll(...) and using
the same memberId/videoAnalysisTaskId filtering.

⚠️ Potential issue | 🟡 Minor

Ktlint가 Line 71에서 깨집니다.

= 뒤 체이닝이 같은 줄에서 시작해서 CI가 실패합니다. multiline expression 형태로 줄바꿈만 맞추면 됩니다.

예시 수정
-        val unprocessedRequests = requestPort.findUnprocessedByVideoAnalysisTaskId(videoAnalysisTaskId)
-            .filter { it.memberId == memberId }
-            .onEach { it.markProcessed() }
+        val unprocessedRequests =
+            requestPort.findUnprocessedByVideoAnalysisTaskId(videoAnalysisTaskId)
+                .filter { it.memberId == memberId }
+                .onEach { it.markProcessed() }
         requestPort.saveAll(unprocessedRequests)
📝 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
val unprocessedRequests = requestPort.findUnprocessedByVideoAnalysisTaskId(videoAnalysisTaskId)
.filter { it.memberId == memberId }
.onEach { it.markProcessed() }
requestPort.saveAll(unprocessedRequests)
val unprocessedRequests =
requestPort.findUnprocessedByVideoAnalysisTaskId(videoAnalysisTaskId)
.filter { it.memberId == memberId }
.onEach { it.markProcessed() }
requestPort.saveAll(unprocessedRequests)
🧰 Tools
🪛 GitHub Actions: CI - Pull request

[error] 71-71: ktlintMainSourceSetCheck failed (ktlint). Violation at :71:35: "A multiline expression should start on a new line".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@linktrip-application/src/main/kotlin/com/linktrip/application/domain/trip/TripPlanService.kt`
around lines 71 - 74, Ktlint is failing because the chained call after the
assignment in TripPlanService (the expression starting with
requestPort.findUnprocessedByVideoAnalysisTaskId(...)) begins on the same line
as the `=`; split the expression into a multiline assignment so the chain starts
on the next line, e.g. place the call to
requestPort.findUnprocessedByVideoAnalysisTaskId(videoAnalysisTaskId) on a new
indented line and keep subsequent .filter { ... } and .onEach { ... } each on
their own lines, then call requestPort.saveAll(unprocessedRequests); this fixes
the line-break rule without changing logic.

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.

[Fix] Lazy 생성 경로에서 trip_plan_request.processed 미업데이트 수정

1 participant