Skip to content

Conversation

@chock-cho
Copy link
Member

@chock-cho chock-cho commented Nov 1, 2025

💡 작업 내용

  • YS-570 feature: AI 공고 기능 RateLimiter 카운팅 API 작업

✅ 셀프 체크리스트

  • PR 제목을 형식에 맞게 작성했나요?
  • 브랜치 전략에 맞는 브랜치에 PR을 올리고 있나요?
  • 테스트는 잘 통과했나요?
  • 빌드에 성공했나요?
  • 본인을 assign 해주세요.
  • 해당 PR에 맞는 label을 붙여주세요.

🙋🏻‍ 확인해주세요

  • 관련된 Discussion 등이 있다면 첨부해주세요

🔗 Jira 티켓


https://yappsocks.atlassian.net/browse/YS-570

Summary by CodeRabbit

새로운 기능

  • 실험 게시물 키워드 추출에 대한 일일 사용량 제한 기능 추가
  • 사용자가 현재 사용량, 남은 횟수, 초기화 시간을 확인할 수 있는 새 API 엔드포인트 제공
  • 일일 2회 사용 제한 설정 및 초과 시 사전 검증으로 제한 사항 강화

@chock-cho chock-cho self-assigned this Nov 1, 2025
@chock-cho chock-cho added the ✨ FEATURE 기능 추가 label Nov 1, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 1, 2025

개요

일일 AI 키워드 추출 사용량 제한을 조회하고 관리하는 새로운 기능이 추가되었습니다. GetDailyLimitForExtractUseCase, UsageLimitGateway 메서드, Redis 구현체, REST API 엔드포인트, 데이터 전송 객체가 계층 전체에 추가되었습니다.

변경사항

코호트 / 파일 요약
도메인 모델
domain/src/main/kotlin/com/dobby/model/UsageSnapshot.kt
사용량 스냅샷 데이터 클래스 신규 추가 (count, limit, remainingCount, resetsAt)
도메인 게이트웨이
domain/src/main/kotlin/com/dobby/gateway/UsageLimitGateway.kt
getCurrentUsage(memberId, dailyLimit): UsageSnapshot 메서드 추가
애플리케이션 유스케이스
application/src/main/kotlin/com/dobby/usecase/experiment/GetDailyLimitForExtractUseCase.kt
사용량 제한 조회 유스케이스 신규 생성 (DAILY_USAGE_LIMIT = 2 상수 포함)
애플리케이션 유스케이스
application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt
validateDailyUsageLimit() 호출 활성화로 실행 전 일일 제한 검증 추가
애플리케이션 서비스
application/src/main/kotlin/com/dobby/service/ExperimentPostService.kt
getMyDailyUsageLimit() 메서드 및 GetDailyLimitForExtractUseCase 의존성 주입 추가
인프라 게이트웨이 구현
infrastructure/src/main/kotlin/com/dobby/external/gateway/cache/RedisUsageLimitGatewayImpl.kt
getCurrentUsage() 구현 및 Asia/Seoul 타임존 기반 리셋 시간 계산 로직 추가
프레젠테이션 DTO
presentation/src/main/kotlin/com/dobby/api/dto/response/experiment/DailyUsageSnapshotResponse.kt
일일 사용량 응답 데이터 클래스 신규 추가 (Swagger 주석 포함)
프레젠테이션 매퍼
presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt
toDailyUsageSnapshotInput(), toDailyUsageSnapshotResponse() 변환 메서드 추가
프레젠테이션 컨트롤러
presentation/src/main/kotlin/com/dobby/api/controller/ExperimentPostController.kt
GET /v1/experiment-posts/usage-limit 엔드포인트 신규 추가 (RESEARCHER 역할 가드)

순서도

sequenceDiagram
    participant Client
    participant Controller as ExperimentPostController
    participant Service as ExperimentPostService
    participant UseCase as GetDailyLimitForExtractUseCase
    participant Gateway as UsageLimitGateway (Redis)
    participant Response

    Client->>Controller: GET /v1/experiment-posts/usage-limit
    activate Controller
    Controller->>Service: getMyDailyUsageLimit(input)
    activate Service
    Service->>UseCase: execute(memberId)
    activate UseCase
    UseCase->>Gateway: getCurrentUsage(memberId, dailyLimit=2)
    activate Gateway
    Gateway->>Gateway: 캐시에서 count 조회
    Gateway->>Gateway: 남은 count 계산
    Gateway->>Gateway: TTL 또는 다음 자정(KST) 계산
    Gateway-->>UseCase: UsageSnapshot
    deactivate Gateway
    UseCase-->>Service: Output
    deactivate UseCase
    Service-->>Controller: Output
    deactivate Service
    Controller->>Controller: DailyUsageSnapshotResponse로 변환
    Controller-->>Client: 200 OK + JSON 응답
    deactivate Controller
Loading

코드 리뷰 예상 난이도

🎯 3 (중간) | ⏱️ ~20분

검토 시 주의사항:

  • 타임존 로직: RedisUsageLimitGatewayImpl의 Asia/Seoul 타임존 기반 자정 계산 및 ZonedDateTime 사용 검증 필요
  • 사용량 제한 정책: DAILY_USAGE_LIMIT = 2 상수가 비즈니스 요구사항과 일치하는지 확인
  • 트랜잭션 처리: ExperimentPostService의 트랜잭셔널 래퍼와 일관성 확인
  • 캐시 키 생성: RedisUsageLimitGatewayImpl에서 기존 캐시 키 생성 로직과의 호환성 검증
  • 역할 기반 접근 제어: ExperimentPostController의 RESEARCHER 역할 가드 적용 확인

🐰 일일 한계를 재었네,
Redis 캐시에 시간은 흐르고,
새벽을 세운 서울 시간,
두 번의 기회, 남은 것 보여주네!
사용량은 이제 투명하리.

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed PR 제목 "YS-570 feature: AI 공고 기능 RateLimiter 카운팅 API 작업"은 변경사항의 핵심을 적절히 반영하고 있습니다. 변경사항 전체는 일일 사용량 제한 기능 구현(GetDailyLimitForExtractUseCase, UsageLimitGateway 확장, Redis 구현체 추가)과 이를 조회하기 위한 API 엔드포인트 추가(GET /v1/experiment-posts/usage-limit)로 구성되어 있으며, 제목은 "RateLimiter 카운팅 API"라는 명확한 표현으로 이를 요약하고 있습니다. 이슈 추적 ID(YS-570)와 기능 유형(feature:)도 포함되어 있어 프로젝트 관례를 따르고 있습니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/YS-570

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

🧹 Nitpick comments (5)
infrastructure/src/main/kotlin/com/dobby/external/gateway/cache/RedisUsageLimitGatewayImpl.kt (1)

23-23: 타임존 설정을 설정 파일로 외부화하는 것을 고려하세요.

Asia/Seoul 타임존이 하드코딩되어 있습니다. 향후 다른 지역 서비스를 고려하거나 테스트 용이성을 위해 application.yml에서 설정하는 것이 좋습니다.

예시:

@Value("\${app.timezone:Asia/Seoul}")
private val timezoneId: String = "Asia/Seoul"
private val zone by lazy { ZoneId.of(timezoneId) }
application/src/main/kotlin/com/dobby/service/ExperimentPostService.kt (1)

159-162: 읽기 전용 Redis 작업에 @transactional이 불필요할 수 있습니다.

getMyDailyUsageLimit 메서드는 Redis에서 데이터를 읽기만 하는 작업이므로 @Transactional 어노테이션이 필요하지 않습니다. JPA 트랜잭션은 데이터베이스 작업에만 관련되며, Redis 작업에는 적용되지 않습니다.

불필요한 트랜잭션 오버헤드를 제거하려면 @Transactional을 제거하세요:

-    @Transactional
     fun getMyDailyUsageLimit(input: GetDailyLimitForExtractUseCase.Input): GetDailyLimitForExtractUseCase.Output {
         return getDailyLimitForExtractUseCase.execute(input)
     }
application/src/main/kotlin/com/dobby/usecase/experiment/GetDailyLimitForExtractUseCase.kt (3)

11-13: 일일 사용량 제한값을 설정으로 외부화하는 것을 고려하세요.

DAILY_USAGE_LIMIT이 2로 하드코딩되어 있습니다. 비즈니스 요구사항이 변경되거나 환경별로 다른 제한값이 필요한 경우 코드 수정 및 재배포가 필요합니다. 애플리케이션 설정(application.yml 등)으로 외부화하면 유연성이 향상됩니다.

예를 들어:

class GetDailyLimitForExtractUseCase(
    private val usageLimitGateway: UsageLimitGateway,
    private val dailyUsageLimit: Int  // 설정에서 주입
) : UseCase<GetDailyLimitForExtractUseCase.Input, GetDailyLimitForExtractUseCase.Output> {
    
    override fun execute(input: Input): Output {
        val snapshot = usageLimitGateway.getCurrentUsage(
            memberId = input.memberId,
            dailyLimit = dailyUsageLimit
        )
        // ...
    }
}

26-38: Redis 연산 실패에 대한 방어 코드 추가 권장

RedisUsageLimitGatewayImpl.getCurrentUsage()의 Redis 연산(redisTemplate.opsForValue().get(), redisTemplate.getExpire())이 예외를 발생시킬 때 처리 전략이 없습니다. 같은 이유로 설계된 RedisCacheGatewayImpl은 try-catch로 방어하고 있으므로(예: getAutoComplete, setAutoComplete), 일관성을 위해 다음과 같이 개선하세요:

  • getCurrentUsage()에 try-catch 블록 추가하여 Redis 연산 실패 시 적절히 로깅
  • 또는 Redis 실패를 명시적인 도메인 예외로 변환하여 호출자가 의도적으로 처리하도록 함
  • RedisCacheGatewayImpl과 동일한 에러 처리 패턴 적용

15-17: memberId는 Spring Security 인증을 통해 검증됩니다.

검증 결과, memberIdgetCurrentMemberId()를 통해 Spring Security의 인증 컨텍스트에서 가져오므로, 이미 인증된 사용자만 접근 가능합니다. 따라서 빈 값이나 null 값의 위험은 없습니다.

다만 방어적 프로그래밍 관점에서, 사용 사례(use case) 경계에 명시적 검증을 추가하는 것은 고려할 수 있습니다:

data class Input(
    val memberId: String
) {
    init {
        require(memberId.isNotBlank()) { "memberId cannot be blank" }
    }
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc64f1c and 4035406.

📒 Files selected for processing (9)
  • application/src/main/kotlin/com/dobby/service/ExperimentPostService.kt (3 hunks)
  • application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt (1 hunks)
  • application/src/main/kotlin/com/dobby/usecase/experiment/GetDailyLimitForExtractUseCase.kt (1 hunks)
  • domain/src/main/kotlin/com/dobby/gateway/UsageLimitGateway.kt (1 hunks)
  • domain/src/main/kotlin/com/dobby/model/UsageSnapshot.kt (1 hunks)
  • infrastructure/src/main/kotlin/com/dobby/external/gateway/cache/RedisUsageLimitGatewayImpl.kt (3 hunks)
  • presentation/src/main/kotlin/com/dobby/api/controller/ExperimentPostController.kt (2 hunks)
  • presentation/src/main/kotlin/com/dobby/api/dto/response/experiment/DailyUsageSnapshotResponse.kt (1 hunks)
  • presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt (1)
presentation/src/main/kotlin/com/dobby/util/AuthenticationUtils.kt (1)
  • getCurrentMemberId (5-5)
⏰ 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 (6)
domain/src/main/kotlin/com/dobby/gateway/UsageLimitGateway.kt (1)

3-7: LGTM! 인터페이스 확장이 적절합니다.

새로운 getCurrentUsage 메서드가 기존 incrementAndCheckLimit과 일관된 시그니처를 가지고 있으며, 도메인 모델 UsageSnapshot을 반환하여 사용량 조회 기능을 명확하게 제공합니다.

application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt (1)

36-36: 일일 사용량 제한 검증이 적절하게 배치되었습니다.

OpenAI API 호출 전에 사용량 제한을 검증하여 불필요한 외부 API 호출을 방지하고 있습니다. Fail-fast 패턴을 잘 적용했습니다.

domain/src/main/kotlin/com/dobby/model/UsageSnapshot.kt (1)

5-10: LGTM! 도메인 모델이 명확하게 정의되었습니다.

불변 데이터 클래스로 사용량 스냅샷을 표현하고 있으며, 프로퍼티 이름이 직관적이고 타입이 적절합니다.

presentation/src/main/kotlin/com/dobby/api/controller/ExperimentPostController.kt (1)

238-248: LGTM! API 엔드포인트가 일관성 있게 구현되었습니다.

RESEARCHER 역할 기반 보안이 적용되었고, Swagger 문서화가 포함되어 있으며, 기존 컨트롤러 패턴과 일관된 구조로 작성되었습니다.

presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt (1)

546-559: LGTM! 매퍼 함수가 명확하게 구현되었습니다.

인증된 사용자 ID를 가져와 use case input을 생성하고, output을 response DTO로 직접 매핑하는 단순하고 명확한 구조입니다.

infrastructure/src/main/kotlin/com/dobby/external/gateway/cache/RedisUsageLimitGatewayImpl.kt (1)

39-58: <!-- [scratchpad_update]
[task verify_timezone_consistency]
[findings]

  • Best practice confirmed: when timezone matters, use ZonedDateTime or explicit zone-aware API
  • LocalDateTime.now() uses system default timezone - problematic for cross-timezone systems
  • Mixing LocalDateTime.now() and ZonedDateTime.now(zone) is anti-pattern
    [pending]
  • Need to examine actual RedisUsageLimitGatewayImpl.kt file to see the complete context
  • Need to check if LocalDate.now() in line 30 also needs fixing
    [actions]
  • Fetch the full file content
    [scratchpad_end] -->Now let me get the full file to see complete context:Based on my analysis of the review comment and the verification results, I can now formulate my response. The review comment is correct in identifying a timezone inconsistency issue. Let me generate the rewritten review comment.

Line 30의 타임존 불일치를 수정하여 일관성 확보

incrementAndCheckLimit 메서드(Line 30)에서 LocalDateTime.now()를 사용하는 것과 getCurrentUsage 메서드(Lines 45-49)에서 ZonedDateTime.now(zone)을 사용하는 것 간의 타임존 처리 불일치가 확인되었습니다. LocalDateTime은 타임존 정보를 가지지 않으므로 시스템 기본 타임존을 사용하게 되는데, 이는 zone 변수로 명시된 KST와 다를 수 있어 일일 한도 재설정 시간 계산에 불일치를 초래합니다.

Line 30을 다음과 같이 수정하세요:

-val expireSeconds = Duration.between(LocalDateTime.now(), LocalDate.now().plusDays(1).atStartOfDay()).seconds
+val expireSeconds = Duration.between(ZonedDateTime.now(zone).toLocalDateTime(), LocalDate.now().plusDays(1).atStartOfDay()).seconds

Likely an incorrect or invalid review comment.

@chock-cho chock-cho merged commit 1773a87 into dev Nov 1, 2025
4 checks passed
@chock-cho chock-cho deleted the feature/YS-570 branch November 1, 2025 09:10
chock-cho added a commit that referenced this pull request Nov 1, 2025
* feature: add domain layer to view limit logics for daily snapshot

* feature: add application layer usecase to execute logic

* feature: add infra layer to implement gateway logic

* feature: add presentation layer logic to controller
@chock-cho chock-cho mentioned this pull request Nov 1, 2025
6 tasks
chock-cho added a commit that referenced this pull request Nov 1, 2025
* feature: add domain layer to view limit logics for daily snapshot

* feature: add application layer usecase to execute logic

* feature: add infra layer to implement gateway logic

* feature: add presentation layer logic to controller
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ FEATURE 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants