Skip to content

[Feat] 멱등성 처리 인프라 및 API 문서 추가#64

Merged
toychip merged 8 commits into
mainfrom
feat/#56-idempotency-key
Apr 16, 2026
Merged

[Feat] 멱등성 처리 인프라 및 API 문서 추가#64
toychip merged 8 commits into
mainfrom
feat/#56-idempotency-key

Conversation

@toychip
Copy link
Copy Markdown
Contributor

@toychip toychip commented Apr 16, 2026

관련 이슈


변경 내용

  • @Idempotent 어노테이션을 추가하고, AOP 기반 IdempotencyAspect를 구현해 멱등성 처리 흐름을 구성
  • IdempotencyStore 포트와 CachedResponse, IdempotencyStatus를 정의하고, Caffeine 기반 저장소 구현체를 추가
  • 중복 요청 처리 시 사용할 DUPLICATE_REQUEST 예외 코드를 추가
  • Video analyze, TripPlan delete API에 대한 멱등성 적용 준비를 위해 컨트롤러 import 및 Swagger 문서를 반영
  • Swagger 문서에 Idempotency-Key 헤더, 중복 요청 동작 방식, 409 응답 예시를 추가

체크리스트

  • Ktlint
  • 테스트 통과 여부

Summary by CodeRabbit

  • 새로운 기능

    • API 요청 멱등성(Idempotency) 지원 추가: 중복 요청 방지, 완료된 요청에 대한 응답 재사용(캐시)
    • 중복(진행 중) 요청에 대해 409 응답 반환
  • 문서

    • Swagger/OpenAPI 설명에 멱등성 관련 안내 및 409 응답 예시 추가 (현재 일부 컨트롤러에는 적용 준비 중임을 명시)

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

coderabbitai Bot commented Apr 16, 2026

Warning

Rate limit exceeded

@toychip has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 57 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 1 minutes and 57 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6bf8ec3c-f48f-415a-a8b1-aa0ce767606d

📥 Commits

Reviewing files that changed from the base of the PR and between c0ce05b and 559e884.

📒 Files selected for processing (7)
  • linktrip-application/src/main/kotlin/com/linktrip/application/port/output/idempotency/IdempotencyStore.kt
  • linktrip-common/src/main/kotlin/com/linktrip/common/exception/ExceptionCode.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/docs/AuthDocs.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/docs/TripPlanDocs.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/docs/VideoDocs.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/idempotency/IdempotencyAspect.kt
  • linktrip-output-cache/caffeine/src/main/kotlin/com/linktrip/output/cache/caffeine/adapter/CaffeineIdempotencyStore.kt
📝 Walkthrough

Walkthrough

멱등성 처리 인프라가 추가되었습니다: @Idempotent 애노테이션, AOP 기반 IdempotencyAspect, idempotency 모델(CachedResponse, IdempotencyStatus), IdempotencyStore 포트와 Caffeine 구현, 관련 예외 코드 및 Swagger 문서가 함께 도입/갱신되었습니다.

Changes

Cohort / File(s) Summary
Idempotency 핵심 모델
linktrip-application/src/main/kotlin/com/linktrip/application/port/output/idempotency/CachedResponse.kt, .../IdempotencyStatus.kt, .../IdempotencyStore.kt
캐시된 응답(CachedResponse), 상태열거(IdempotencyStatus), 저장소 포트(IdempotencyStore) 추가 — 조회/락/완료/실패 API 정의
애노테이션
linktrip-common/src/main/kotlin/com/linktrip/common/annotation/Idempotent.kt
런타임 대상의 @Idempotent 애노테이션 추가
AOP 멱등성 검사
linktrip-input-http/src/main/kotlin/com/linktrip/input/http/idempotency/IdempotencyAspect.kt
@Idempotent 적용 메서드 가로채기: 요청에서 키 빌드, 상태 조회, PROCESSING/COMPLETED/FAILED 분기, 락 확보(tryLock), 완료/실패 저장 로직 구현
Caffeine 기반 저장소
linktrip-output-cache/caffeine/src/main/kotlin/.../CaffeineIdempotencyStore.kt
Caffeine 캐시로 IdempotencyStore 구현 (TTL=10분, 최대 10_000), 원자적 락 확보 via cache.get(key) { processing }
예외 코드
linktrip-common/src/main/kotlin/com/linktrip/common/exception/ExceptionCode.kt
DUPLICATE_REQUEST(HTTP 409) 항목 추가
컨트롤러 주석/문서화 변경
linktrip-input-http/src/main/kotlin/.../TripPlanController.kt, .../VideoController.kt, .../docs/TripPlanDocs.kt, .../docs/VideoDocs.kt
컨트롤러에 주석 처리된 @Idempotent 표기 추가 및 Swagger 설명에 멱등성 관련 설명(현행 미적용, 향후 적용 예정) 확장
배치 스케줄러 의존성 추가
linktrip-input-batch/src/main/kotlin/.../VideoAnalysisRetryJobScheduler.kt
VideoAnalysisQueuePort 주입 추가 및 큐 사이즈 로깅 스케줄러 메서드 추가

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller
    participant IdempotencyAspect
    participant IdempotencyStore
    participant Cache as Caffeine\ Cache

    Client->>Controller: 요청 (Idempotency-Key 포함)
    Controller->>IdempotencyAspect: `@Idempotent` 메서드 호출
    IdempotencyAspect->>IdempotencyStore: find(key)
    IdempotencyStore->>Cache: getIfPresent(key)
    Cache-->>IdempotencyStore: CachedResponse?
    IdempotencyStore-->>IdempotencyAspect: CachedResponse?

    alt COMPLETED
        IdempotencyAspect-->>Client: 저장된 body 반환
    else PROCESSING
        IdempotencyAspect-->>Client: 409 DUPLICATE_REQUEST
    else null or FAILED
        IdempotencyAspect->>IdempotencyStore: tryLock(key)
        IdempotencyStore->>Cache: cache.get(key) { PROCESSING }
        Cache-->>IdempotencyStore: CachedResponse (참조 비교)
        IdempotencyStore-->>IdempotencyAspect: 락 성공 여부

        alt 락 성공
            IdempotencyAspect->>Controller: 원본 메서드 실행
            Controller-->>IdempotencyAspect: 결과
            IdempotencyAspect->>IdempotencyStore: saveCompleted(key, 결과)
            IdempotencyStore->>Cache: COMPLETED로 저장
            IdempotencyAspect-->>Client: 결과 반환
        else 락 실패
            IdempotencyAspect-->>Client: 409 DUPLICATE_REQUEST
        end
    end
Loading
sequenceDiagram
    participant Client
    participant IdempotencyAspect
    participant IdempotencyStore
    participant Cache as Caffeine\ Cache
    participant BusinessLogic

    Client->>IdempotencyAspect: 요청 (Idempotency-Key)
    IdempotencyAspect->>IdempotencyAspect: 키 생성 (method:uri:memberId:rawKey)

    rect rgba(200, 150, 100, 0.5)
    IdempotencyAspect->>IdempotencyStore: tryLock(key)
    IdempotencyStore->>Cache: cache.get(key) { CachedResponse(PROCESSING) }
    Cache-->>IdempotencyStore: CachedResponse
    IdempotencyStore-->>IdempotencyAspect: 락 성공 여부
    end

    IdempotencyAspect->>BusinessLogic: 원본 실행

    alt 성공
        BusinessLogic-->>IdempotencyAspect: 결과
        IdempotencyAspect->>IdempotencyStore: saveCompleted(key, body)
        IdempotencyStore->>Cache: COMPLETED 저장
        IdempotencyAspect-->>Client: 결과 반환
    else 예외
        BusinessLogic-->>IdempotencyAspect: 예외
        IdempotencyAspect->>IdempotencyStore: saveFailed(key)
        IdempotencyStore->>Cache: invalidate(key)
        IdempotencyAspect-->>Client: 예외 전파
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

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 31.58% 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 제목은 변경사항의 핵심을 명확하게 요약하고 있습니다. '[Feat] 멱등성 처리 인프라 및 API 문서 추가'는 새로운 멱등성 처리 기능의 인프라 추가와 API 문서화라는 주요 변경사항을 정확하게 반영하고 있습니다.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#56-idempotency-key

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

🧹 Nitpick comments (1)
linktrip-output-cache/caffeine/src/main/kotlin/com/linktrip/output/cache/caffeine/adapter/CaffeineIdempotencyStore.kt (1)

13-18: 멱등성 TTL/용량은 설정값으로 빼두는 편이 안전합니다.

지금 값은 운영 정책인데 코드 상수로 고정돼 있어서 환경별 조정이 어렵습니다. 트래픽이나 엔드포인트 특성에 따라 보존 시간과 캐시 크기가 달라질 수 있으니 프로퍼티로 외부화해 두는 쪽이 유지보수에 유리합니다.

예시 diff
 import com.linktrip.application.port.output.idempotency.CachedResponse
 import com.linktrip.application.port.output.idempotency.IdempotencyStatus
 import com.linktrip.application.port.output.idempotency.IdempotencyStore
 import org.springframework.stereotype.Component
+import org.springframework.beans.factory.annotation.Value
 import java.time.Duration
 
 `@Component`
-class CaffeineIdempotencyStore : IdempotencyStore {
+class CaffeineIdempotencyStore(
+    `@Value`("\${idempotency.cache.ttl-minutes:10}") private val ttlMinutes: Long,
+    `@Value`("\${idempotency.cache.max-size:10000}") private val maxSize: Long,
+) : IdempotencyStore {
     private val cache: Cache<String, CachedResponse> =
         Caffeine.newBuilder()
-            .expireAfterWrite(Duration.ofMinutes(TTL_MINUTES))
-            .maximumSize(MAX_SIZE)
+            .expireAfterWrite(Duration.ofMinutes(ttlMinutes))
+            .maximumSize(maxSize)
             .build()
@@
-
-    companion object {
-        private const val TTL_MINUTES = 10L
-        private const val MAX_SIZE = 10_000L
-    }
 }

Also applies to: 36-38

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

In
`@linktrip-output-cache/caffeine/src/main/kotlin/com/linktrip/output/cache/caffeine/adapter/CaffeineIdempotencyStore.kt`
around lines 13 - 18, The hardcoded TTL and capacity (TTL_MINUTES and MAX_SIZE)
used when building the Caffeine cache in CaffeineIdempotencyStore should be
externalized to configuration: add constructor parameters or inject a
config/properties object into CaffeineIdempotencyStore to provide ttlMinutes and
maxSize, replace Duration.ofMinutes(TTL_MINUTES) with
Duration.ofMinutes(ttlMinutes) and maximumSize(MAX_SIZE) with
maximumSize(maxSize), and do the same refactor for the other cache instance
referenced at the same locations (lines 36-38) so both caches read values from
properties/env rather than constants.
🤖 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/port/output/idempotency/IdempotencyStore.kt`:
- Around line 17-21: IdempotencyStore 인터페이스의 KDoc이 깨져 읽히지 않으니 tryLock 함수의 주석을
복구하세요; IdempotencyStore 및 메서드 tryLock의 KDoc을 명확한 한국어 문장으로 교체(예: "특정 key에 대해
PROCESSING 상태로 락을 건다. 이미 다른 상태가 존재하면 false를 반환한다.")하여 구현체와 AOP가 참조하는 계약 문구가 올바르게
남도록 수정하세요.

In
`@linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/TripPlanController.kt`:
- Around line 70-71: The deleteTripPlan endpoint currently has the `@Idempotent`
annotation commented out so the Idempotency-Key behavior described in docs
(409/retry semantics) is not applied; restore the annotation above the
deleteTripPlan handler (uncomment or re-add `@Idempotent`) and ensure the relevant
idempotency interceptor/import is present so the Idempotency-Key header is
processed, or if you intentionally won't support idempotency, update the API
docs to remove/mark the 409/retry behavior as not applicable for deleteTripPlan.

In
`@linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/VideoController.kt`:
- Around line 41-42: The analyzeVideo endpoint in VideoController has the
`@Idempotent` annotation commented out which breaks the documented Idempotency-Key
behavior; re-enable the annotation by removing the comment so `@Idempotent` is
applied to the method annotated with `@PostMapping`("/analyze") (ensure the class
VideoController still imports the Idempotent annotation and that analyzeVideo
will read/validate the Idempotency-Key header and return the documented
409/cache responses when duplicates are detected).

In
`@linktrip-input-http/src/main/kotlin/com/linktrip/input/http/idempotency/IdempotencyAspect.kt`:
- Around line 25-39: The idempotency key is currently just the raw
Idempotency-Key header, causing cross-user/endpoint collisions; update
checkIdempotency to build a namespaced key by combining extractIdempotencyKey()
with request-specific metadata (e.g., authenticated user id or principal, HTTP
method, request path) and reject or ignore empty header values; if the endpoint
semantics depend on body content, include a stable request-body fingerprint
(e.g., SHA-256) in the composite key before using idempotencyStore.find(key) or
storing responses in execute; ensure all references to Idempotency-Key handling
(extractIdempotencyKey, checkIdempotency, execute, and idempotencyStore usage)
use the new composite key format.
- Around line 49-55: 현재 try/catch가 Exception만 잡아 일부 Throwable(예: Error) 발생 시
idempotencyStore.saveFailed(key)가 호출되지 않아 키가 PROCESSING 상태로 남습니다; 변경할 곳은
IdempotencyAspect의 joinPoint.proceed() 블록으로, Exception 대신 Throwable을 잡도록 catch를
확장하여 어떤 Throwable이든 발생하면 idempotencyStore.saveFailed(key)를 호출하고 발생한 Throwable을
그대로 다시 던지도록 수정하세요; 참고 심볼: joinPoint.proceed(), idempotencyStore.saveFailed(key),
idempotencyStore.saveCompleted(key).

---

Nitpick comments:
In
`@linktrip-output-cache/caffeine/src/main/kotlin/com/linktrip/output/cache/caffeine/adapter/CaffeineIdempotencyStore.kt`:
- Around line 13-18: The hardcoded TTL and capacity (TTL_MINUTES and MAX_SIZE)
used when building the Caffeine cache in CaffeineIdempotencyStore should be
externalized to configuration: add constructor parameters or inject a
config/properties object into CaffeineIdempotencyStore to provide ttlMinutes and
maxSize, replace Duration.ofMinutes(TTL_MINUTES) with
Duration.ofMinutes(ttlMinutes) and maximumSize(MAX_SIZE) with
maximumSize(maxSize), and do the same refactor for the other cache instance
referenced at the same locations (lines 36-38) so both caches read values from
properties/env rather than constants.
🪄 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: b233a576-1351-4361-b023-367dea0ace4e

📥 Commits

Reviewing files that changed from the base of the PR and between 611a836 and 5228557.

📒 Files selected for processing (11)
  • linktrip-application/src/main/kotlin/com/linktrip/application/port/output/idempotency/CachedResponse.kt
  • linktrip-application/src/main/kotlin/com/linktrip/application/port/output/idempotency/IdempotencyStatus.kt
  • linktrip-application/src/main/kotlin/com/linktrip/application/port/output/idempotency/IdempotencyStore.kt
  • linktrip-common/src/main/kotlin/com/linktrip/common/annotation/Idempotent.kt
  • linktrip-common/src/main/kotlin/com/linktrip/common/exception/ExceptionCode.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/TripPlanController.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/VideoController.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/docs/TripPlanDocs.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/docs/VideoDocs.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/idempotency/IdempotencyAspect.kt
  • linktrip-output-cache/caffeine/src/main/kotlin/com/linktrip/output/cache/caffeine/adapter/CaffeineIdempotencyStore.kt
📜 Review details
⏰ 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: 자동 검증 (ktlint + test)
🧰 Additional context used
🧠 Learnings (2)
📚 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-input-http/src/main/kotlin/com/linktrip/input/http/controller/VideoController.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: 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-cache/caffeine/src/main/kotlin/com/linktrip/output/cache/caffeine/adapter/CaffeineIdempotencyStore.kt
🔇 Additional comments (4)
linktrip-common/src/main/kotlin/com/linktrip/common/exception/ExceptionCode.kt (1)

26-27: DUPLICATE_REQUEST 코드 추가는 적절합니다.

Line 26-27의 409 예외 코드 추가가 멱등성 중복 요청 처리 흐름과 잘 맞습니다.

linktrip-application/src/main/kotlin/com/linktrip/application/port/output/idempotency/CachedResponse.kt (1)

3-6: CachedResponse 모델링은 현재 idempotency 흐름과 잘 맞습니다.

상태(IdempotencyStatus)와 응답 본문(body) 분리가 명확해 사용처와 일관됩니다.

linktrip-application/src/main/kotlin/com/linktrip/application/port/output/idempotency/IdempotencyStatus.kt (1)

3-12: 상태 enum 정의가 명확합니다.

Line 3-12의 상태 분리가 멱등성 라이프사이클 표현에 충분합니다.

linktrip-common/src/main/kotlin/com/linktrip/common/annotation/Idempotent.kt (1)

14-16: 어노테이션 메타 설정이 AOP 적용 목적에 적합합니다.

FUNCTION 타겟 + RUNTIME 보존 설정이 의도와 정확히 맞습니다.

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)
linktrip-input-batch/src/main/kotlin/com/linktrip/input/batch/VideoAnalysisRetryJobScheduler.kt (1)

39-42: 10초 주기 info 로그는 운영 로그 노이즈를 크게 늘릴 수 있습니다.

Line 39-41은 고정 10초마다 info를 남겨서 로그 비용/가독성에 부담이 큽니다. 로그 레벨을 debug로 낮추거나, queue size가 0보다 클 때만 기록하고 예외를 내부 처리해 반복 에러 로그 폭주를 막는 쪽을 권장합니다.

제안 diff
-    `@Scheduled`(fixedDelay = 10_000)
+    `@Scheduled`(fixedDelayString = "\${batch.video-analysis.queue-log-delay-ms:10000}")
     fun logQueueSize() {
-        logger.info { "영상 분석 큐 사이즈: ${videoAnalysisQueuePort.size()}" }
+        runCatching { videoAnalysisQueuePort.size() }
+            .onSuccess { size ->
+                if (size > 0) {
+                    logger.debug { "영상 분석 큐 사이즈: $size" }
+                }
+            }
+            .onFailure { e ->
+                logger.warn(e) { "영상 분석 큐 사이즈 조회 실패" }
+            }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@linktrip-input-batch/src/main/kotlin/com/linktrip/input/batch/VideoAnalysisRetryJobScheduler.kt`
around lines 39 - 42, The logQueueSize() scheduled task currently emits an info
log every 10 seconds which creates noise; change it to either logger.debug or
only log when videoAnalysisQueuePort.size() > 0, and wrap the body in a
try/catch to swallow and log errors at debug/trace level (not repeatedly at
error/info) to prevent log flooding. Locate the scheduled function
logQueueSize() and update the logging call from logger.info to logger.debug or
add a conditional check on videoAnalysisQueuePort.size(), and add exception
handling around the size() call so any failure logs minimally and does not
repeat.
🤖 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-input-http/src/main/kotlin/com/linktrip/input/http/idempotency/IdempotencyAspect.kt`:
- Around line 33-42: The code calls idempotencyStore.find(key) twice in
IdempotencyAspect (checking status and then using !! to get body), which creates
a TOCTOU NPE risk if the entry disappears between calls; fix by calling val
entry = idempotencyStore.find(key) once, check entry?.status against
IdempotencyStatus.PROCESSING and IdempotencyStatus.COMPLETED, and when COMPLETED
return entry.body (safely handling null if needed) — update the branch logic in
the method where idempotencyStore.find(key) is used so all checks/read use the
single local variable instead of repeated lookups.

---

Nitpick comments:
In
`@linktrip-input-batch/src/main/kotlin/com/linktrip/input/batch/VideoAnalysisRetryJobScheduler.kt`:
- Around line 39-42: The logQueueSize() scheduled task currently emits an info
log every 10 seconds which creates noise; change it to either logger.debug or
only log when videoAnalysisQueuePort.size() > 0, and wrap the body in a
try/catch to swallow and log errors at debug/trace level (not repeatedly at
error/info) to prevent log flooding. Locate the scheduled function
logQueueSize() and update the logging call from logger.info to logger.debug or
add a conditional check on videoAnalysisQueuePort.size(), and add exception
handling around the size() call so any failure logs minimally and does not
repeat.
🪄 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: e71b0d9f-7c98-4b16-8e0d-ce8d1f2df021

📥 Commits

Reviewing files that changed from the base of the PR and between 5228557 and c0ce05b.

📒 Files selected for processing (8)
  • linktrip-application/src/main/kotlin/com/linktrip/application/port/output/idempotency/IdempotencyStore.kt
  • linktrip-input-batch/src/main/kotlin/com/linktrip/input/batch/VideoAnalysisRetryJobScheduler.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/TripPlanController.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/VideoController.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/docs/TripPlanDocs.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/docs/VideoDocs.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/idempotency/IdempotencyAspect.kt
  • linktrip-output-cache/caffeine/src/main/kotlin/com/linktrip/output/cache/caffeine/adapter/CaffeineIdempotencyStore.kt
✅ Files skipped from review due to trivial changes (2)
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/VideoController.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/docs/TripPlanDocs.kt
🚧 Files skipped from review as they are similar to previous changes (4)
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/TripPlanController.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/controller/docs/VideoDocs.kt
  • linktrip-application/src/main/kotlin/com/linktrip/application/port/output/idempotency/IdempotencyStore.kt
  • linktrip-output-cache/caffeine/src/main/kotlin/com/linktrip/output/cache/caffeine/adapter/CaffeineIdempotencyStore.kt
📜 Review details
⏰ 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: 자동 검증 (ktlint + test)
🔇 Additional comments (5)
linktrip-input-batch/src/main/kotlin/com/linktrip/input/batch/VideoAnalysisRetryJobScheduler.kt (1)

3-3: 의존성 주입 추가는 적절합니다.

VideoAnalysisQueuePort를 생성자 주입으로 연결한 변경은 스케줄러 책임과 잘 맞고 확장성 측면에서도 깔끔합니다.

Also applies to: 17-17

linktrip-input-http/src/main/kotlin/com/linktrip/input/http/idempotency/IdempotencyAspect.kt (4)

1-24: LGTM!

패키지 선언, 임포트, 클래스 구조 모두 적절합니다. IdempotencyStore를 생성자 주입으로 받아 테스트 용이성을 확보한 점이 좋습니다.


48-65: LGTM!

Throwable을 잡아 모든 실패 경로에서 saveFailed가 호출되도록 처리한 점이 좋습니다. 락 획득 → 실행 → 성공/실패 저장 흐름이 명확합니다.


81-86: LGTM!

RequestContextHolder에서 요청을 가져오는 표준 패턴이며, 헤더 상수 정의도 적절합니다.


74-76: 이 주석은 철회합니다.

TokenProvider.extractMemberId(token: String)의 반환 타입이 String이므로 타입 캐스팅 문제가 없습니다. JwtAuthenticationFilter에서 이미 String으로 저장되는 값을 IdempotencyAspect에서 as? String으로 캐스팅하면 정상적으로 작동합니다. 인증된 사용자의 memberId가 올바르게 처리되어 "anonymous"로 대체되지 않습니다.

@toychip toychip merged commit 0b1210e into main Apr 16, 2026
3 checks passed
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.

[Feat] Idempotency-Key 기반 중복 요청 방지 메커니즘 도입

1 participant