Skip to content

캐시 처리에 대한 표준화 객체 추가 및 학생 정원 수 관리 로직 추가#133

Merged
polyglot-k merged 13 commits intodevelopfrom
feat/cache
Jul 25, 2025
Merged

캐시 처리에 대한 표준화 객체 추가 및 학생 정원 수 관리 로직 추가#133
polyglot-k merged 13 commits intodevelopfrom
feat/cache

Conversation

@polyglot-k
Copy link
Contributor

@polyglot-k polyglot-k commented Jul 25, 2025

✨ 구현한 기능

  • Reader, Writer, Loader, Evictor 로 분리하여, Manager 객체에서 이를 처리 하도록 함.
  • Event 기반 처리로 구성 및 Event 를 Resolver를 통해서 전략 메소드(Consumer)를 발생 시킴.

Summary by CodeRabbit

  • New Features

    • Introduced a flexible event-driven system for managing exam quotas, including new event types and statuses for application counts and capacity.
    • Added interfaces and components for cache management, supporting easier extension and improved maintainability.
    • Enhanced cache initialization and management for exam quotas, with improved key handling and abstraction.
    • Added reactive event listener to handle exam quota events asynchronously.
  • Refactor

    • Streamlined exam quota cache logic by abstracting direct cache operations and centralizing key generation.
    • Replaced direct cache updates with event publishing for quota management.
  • Chores

    • Updated package organization for repository implementations.
    • Centralized OpenAPI server configuration.
  • Revert

    • Removed the component responsible for preloading school data at application startup.

@polyglot-k polyglot-k self-assigned this Jul 25, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jul 25, 2025

Warning

Rate limit exceeded

@polyglot-k has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 17 minutes and 43 seconds before requesting another review.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6a26113 and f2f15b4.

📒 Files selected for processing (2)
  • src/main/java/life/mosu/mosuserver/application/exam/ExamService.java (2 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/ExamQuotaEventType.java (1 hunks)

Walkthrough

This update introduces a new event-driven caching architecture for managing exam quotas. It abstracts Redis cache operations via new interfaces and implementations, centralizes cache logic in a refactored manager, and introduces event types, statuses, and listeners for quota-related actions. Several new DTOs, enums, service, and resolver classes are added, while legacy direct cache manipulation and pre-warm runner logic are removed. Additionally, several Redis cache interfaces and their default implementations are introduced to support the abstraction.

Changes

File(s) / Path(s) Change Summary
src/main/java/life/mosu/mosuserver/application/exam/ExamQuotaCacheManager.java Refactored: Now extends abstract cache manager, uses injected cache interfaces, adds key prefixing, new methods.
src/main/java/life/mosu/mosuserver/application/exam/ExamQuotaService.java New: Service to handle exam quota events via resolver.
src/main/java/life/mosu/mosuserver/application/exam/ExamService.java Modified: Replaces direct cache updates with event publishing for quota management.
src/main/java/life/mosu/mosuserver/application/exam/resolver/ExamQuotaEventResolver.java New: Resolves event types to cache update consumers for quota management.
src/main/java/life/mosu/mosuserver/global/runner/ApplicationSchoolPreWarmRunner.java Deleted: Removed legacy application pre-warm runner.
src/main/java/life/mosu/mosuserver/infra/config/SwaggerConfig.java Refactored: Server list extracted to a class field.
src/main/java/life/mosu/mosuserver/infra/persistence/jpa/ApplicationQueryRepositoryImpl.java
src/main/java/life/mosu/mosuserver/infra/persistence/jpa/RefundQueryRepositoryImpl.java
src/main/java/life/mosu/mosuserver/infra/persistence/jpa/StudentQueryRepositoryImpl.java
Package declarations changed; imports updated.
src/main/java/life/mosu/mosuserver/infra/persistence/redis/CacheEvictor.java New: Interface for cache eviction contract.
src/main/java/life/mosu/mosuserver/infra/persistence/redis/CacheLoader.java New: Interface for cache loading operations.
src/main/java/life/mosu/mosuserver/infra/persistence/redis/CacheReader.java New: Interface for cache reading operations.
src/main/java/life/mosu/mosuserver/infra/persistence/redis/CacheWriter.java New: Interface for cache write/update/increment/decrement/delete operations.
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheEvictor.java New: Implements cache eviction for Redis keys (empty method body).
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheLoader.java New: Implements cache loader interface for Redis.
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheReader.java New: Implements cache reader interface for Redis.
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheWriter.java New: Implements cache writer interface for Redis.
src/main/java/life/mosu/mosuserver/infra/persistence/redis/KeyValueCacheManager.java New: Abstract base class for cache managers with init contract.
src/main/java/life/mosu/mosuserver/presentation/exam/ExamQuotaEventListener.java New: Listens for quota events and delegates to service.
src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/ApplicationStatus.java New: Enum for application status events.
src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/ExamQuotaEvent.java New: DTO for exam quota events with factory methods.
src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/ExamQuotaEventType.java New: Enum for exam quota event types with status validation.
src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/ExamQuotaStatus.java New: Marker interface for quota event statuses.
src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/MaxCapacityStatus.java New: Enum for max capacity status events.

Sequence Diagram(s)

sequenceDiagram
    participant ExamService
    participant EventPublisher
    participant ExamQuotaEventListener
    participant ExamQuotaService
    participant ExamQuotaEventResolver
    participant ExamQuotaCacheManager

    ExamService->>EventPublisher: publishEvent(ExamQuotaEvent)
    EventPublisher-->>ExamQuotaEventListener: ExamQuotaEvent
    ExamQuotaEventListener->>ExamQuotaService: handleExamQuotaEvent(event)
    ExamQuotaService->>ExamQuotaEventResolver: resolve(event)
    ExamQuotaEventResolver-->>ExamQuotaService: Consumer<ExamQuotaEvent>
    ExamQuotaService->>ExamQuotaCacheManager: consumer.accept(event)
    ExamQuotaCacheManager->>CacheLoader/CacheWriter/CacheReader: perform cache operation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

In the warren of code, where the caches once sprawled,
Now events hop along, and the old ways are called.
With listeners new and enums in tow,
The quota’s refreshed—watch those numbers grow!
Redis is abstracted, the logic is neat—
This rabbit approves: the change is a treat!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/cache

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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

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

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @polyglot-k, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the application's caching and student quota management systems. It introduces a standardized approach to cache operations through new interfaces and an abstract manager, enhancing modularity. Concurrently, it implements an event-driven architecture for handling student exam quotas, which decouples business logic and improves scalability. Additionally, the persistence layer has been reorganized for better code structure.

Highlights

  • Standardized Cache Operations: Introduced a new set of interfaces (CacheLoader, CacheReader, CacheWriter, CacheEvictor) and an abstract KeyValueCacheManager to standardize cache interactions, promoting a more modular and extensible caching layer.
  • Event-Driven Quota Management: Implemented an event-driven architecture for managing student exam quotas (max capacity and current applications). This involves new ExamQuotaEvent DTOs, an ExamQuotaEventResolver for dispatching actions, and an ExamQuotaEventListener for consuming events.
  • Refactored Exam Quota Logic: The ExamQuotaCacheManager was refactored to extend the new KeyValueCacheManager and utilize the standardized cache interfaces. Its initialization logic was moved to the initCache method of the base class, leading to the removal of the ApplicationSchoolPreWarmRunner.
  • Decoupled Exam Service: The ExamService now publishes ExamQuotaEvents when registering new exams, decoupling it from direct manipulation of the ExamQuotaCacheManager and aligning with the new event-driven design.
  • Persistence Layer Restructuring: QueryDSL repository implementations (ApplicationQueryRepositoryImpl, RefundQueryRepositoryImpl, StudentQueryRepositoryImpl) were moved from the domain.admin package to infra.persistence.jpa, improving the organization and separation of concerns within the persistence layer.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

The code changes introduce a standardized object for cache processing and adds logic for managing student quotas. The caching mechanism has been refactored into a more robust, standardized, and event-driven architecture. The introduction of CacheReader, CacheWriter, and CacheLoader interfaces, along with the ExamQuotaEventResolver, greatly improves modularity and testability. There were a few critical issues identified in the new caching implementation that could lead to runtime errors or incorrect behavior, a potential business logic regression regarding application count decrements, and inconsistencies in the event type definitions. Additionally, suggestions were made to improve code clarity and robustness in the new event handling logic.

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

🔭 Outside diff range comments (4)
src/main/java/life/mosu/mosuserver/infra/persistence/jpa/ApplicationQueryRepositoryImpl.java (1)

54-77: Total-count for paging is wrong – returns page size, not overall rows

new PageImpl<>(content, pageable, content.size()) sets totalElements to the current page length, so Page.getTotalPages() will always be 1 and client UIs will behave incorrectly.
Run a separate count query (or use fetchResults() equivalent) to obtain the real total.

-        List<ApplicationListResponse> content = query.fetch().stream()
+        // Fetch paged content
+        List<ApplicationListResponse> content = query.fetch().stream()
                 ...
-        return new PageImpl<>(content, pageable, content.size());
+        // Fetch total count without pagination
+        Long total = queryFactory
+                .select(application.count())
+                .from(examApplication)
+                .where(
+                        buildNameCondition(filter.name()),
+                        buildPhoneCondition(filter.phone())
+                )
+                .fetchOne();
+
+        return new PageImpl<>(content, pageable, total);

Failing to fix this will break any consumer relying on correct pagination.

src/main/java/life/mosu/mosuserver/infra/persistence/jpa/RefundQueryRepositoryImpl.java (3)

58-60: Leaking PII & payment data in INFO logs
tuple contains paymentKey, phone numbers, and user names. Persisting these values at INFO breaches privacy and may violate compliance requirements. Reduce the level and/or mask sensitive fields:

-log.info("tuple log : {} ", tuple);
+log.debug("Refund export row – id={}, status={}, user={}",
+          tuple.get(refund.id),
+          tuple.get(refund.refundStatus),
+          tuple.get(profile.userName));

If detailed inspection is still needed, consider temporarily enabling TRACE in non-prod environments.


65-82: Add deterministic orderBy for stable pagination
baseQuery() lacks an orderBy, meaning row order depends on the database execution plan. Paging without a stable sort can return duplicate / skipped records across pages. Add an explicit key, e.g.:

 .join(profile).on(profile.userId.eq(application.userId))
+ .orderBy(refund.createdAt.desc())   // or refund.id.desc()

Make sure the chosen column is indexed for performance.


38-50: Use a dedicated count query instead of fetch().size()
Calling baseQuery().fetch().size() will load every matching row into memory before counting, which is inefficient for large tables. Replace it with a single COUNT(*) query:

Files to update:

  • src/main/java/life/mosu/mosuserver/infra/persistence/jpa/RefundQueryRepositoryImpl.java, around line 40

Suggested diff:

-    long total = baseQuery().fetch().size();
+    long total = queryFactory
+        .select(refund.count())
+        .from(refund)
+        .join(examApplication).on(refund.examApplicationId.eq(examApplication.id))
+        .join(application).on(examApplication.applicationId.eq(application.id))
+        .join(payment).on(examApplication.id.eq(payment.examApplicationId))
+        .join(profile).on(profile.userId.eq(application.userId))
+        .fetchOne();

This issues a single SELECT COUNT(*) … and avoids loading all rows into the JVM.

🧹 Nitpick comments (15)
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheEvictor.java (1)

7-8: Consider simplifying the generic design.

The generic type parameter <T> appears unnecessary since cache eviction only requires the key, not the value type. The CacheEvictor<String> interface only uses String keys.

Consider simplifying to:

-public class DefaultRedisCacheEvictor<T> implements CacheEvictor<String> {
-    protected final RedisTemplate<String, T> redisTemplate;
+public class DefaultRedisCacheEvictor implements CacheEvictor<String> {
+    protected final RedisTemplate<String, ?> redisTemplate;

Unless the generic type is needed for consistency with other cache components in your architecture.

src/main/java/life/mosu/mosuserver/infra/config/SwaggerConfig.java (1)

12-19: Prefer static final & constant-style name for immutable configuration data

servers is immutable, environment-agnostic configuration. Declaring it static final (and naming it SERVERS) makes this intent explicit, avoids creating the list on each SwaggerConfig instantiation (even though Spring builds it as a singleton), and aligns with Java constant-naming conventions.

-    private final List<Server> servers = List.of(
+    private static final List<Server> SERVERS = List.of(
         new Server().url("https://api.mosuedu.com/api/v1")
             .description("MOSU SERVER"),
         new Server().url("http://localhost:8080/api/v1")
             .description("Local Development Server"),
         new Server().url("http://192.168.35.174:8080/api/v1")
             .description("Custom Development Server")
     );

Don’t forget to update the usage below:

-                ).servers(servers);
+                ).servers(SERVERS);
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheWriter.java (1)

7-31: Consider adding error handling and consistency with other cache implementations.

While the implementation is functionally correct, comparing with the other cache implementations in the relevant code snippets reveals some inconsistencies:

  1. DefaultRedisCacheReader wraps the result in Optional.of() which could cause NPE if the value is null
  2. DefaultRedisCacheEvictor has an empty evict method implementation
  3. No error handling is present in any of the implementations

Consider adding error handling and logging for better observability:

 @Override
 public void writeOrUpdate(String key, T value) {
+    try {
         redisTemplate.opsForValue().set(key, value);
+    } catch (Exception e) {
+        log.error("Failed to write cache entry for key: {}", key, e);
+        throw e;
+    }
 }

 @Override
 public void increase(String key) {
+    try {
         redisTemplate.opsForValue().increment(key, 1);
+    } catch (Exception e) {
+        log.error("Failed to increment cache value for key: {}", key, e);
+        throw e;
+    }
 }

Also consider adding validation for the increase/decrease operations to ensure they're only used with appropriate numeric types.

src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/ExamQuotaStatus.java (1)

3-5: Consider adding documentation and reviewing package placement.

The marker interface design is appropriate, but consider adding JavaDoc to explain its purpose in the event-driven system. Also, since this interface is used across multiple layers (presentation, application), consider moving it to a more neutral package like domain or common.

+/**
+ * Marker interface for exam quota event status types.
+ * Implemented by enums that represent different status values
+ * in the event-driven quota management system.
+ */
 public interface ExamQuotaStatus {
 
 }
src/main/java/life/mosu/mosuserver/infra/persistence/redis/CacheEvictor.java (1)

3-5: Add documentation and consider return type.

The interface design is clean, but it needs JavaDoc to specify the contract. Consider whether the method should return a boolean to indicate if eviction was successful or if the key existed.

+/**
+ * Interface for evicting entries from cache.
+ * @param <K> the type of cache keys
+ */
 public interface CacheEvictor<K> {
+    /**
+     * Evicts the cache entry associated with the given key.
+     * @param key the key whose entry is to be evicted
+     */
     void evict(K key);
 }
src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/ApplicationStatus.java (1)

3-5: Clarify semantic differences between similar status values.

The enum mixes entity lifecycle operations (CREATE/DELETE) with quantity modifications (INCREASE/DECREASE). This could lead to confusion about when to use each status. Consider adding JavaDoc to clarify the specific use cases for each value, or consider separating lifecycle and modification operations.

+/**
+ * Status values for application count events in the exam quota system.
+ * <ul>
+ *   <li>CREATE - Initial creation of application count entry</li>
+ *   <li>INCREASE - Increment existing application count</li>
+ *   <li>DECREASE - Decrement existing application count</li>
+ *   <li>DELETE - Remove application count entry entirely</li>
+ * </ul>
+ */
 public enum ApplicationStatus implements ExamQuotaStatus {
     INCREASE, CREATE, DECREASE, DELETE
 }
src/main/java/life/mosu/mosuserver/infra/persistence/redis/KeyValueCacheManager.java (1)

12-16: Consider making init() method protected and adding error handling.

The private init() method calling an abstract method could limit subclass control over initialization timing. Consider making it protected to allow subclasses to override if needed.

-    @PostConstruct
-    private void init(){
-        initCache();
-    }
+    @PostConstruct
+    protected void init(){
+        try {
+            initCache();
+        } catch (Exception e) {
+            // Log error and potentially rethrow as configuration exception
+            throw new IllegalStateException("Failed to initialize cache", e);
+        }
+    }
src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/ExamQuotaEvent.java (2)

7-22: Clarify factory method naming and consider consistent approach.

The factory method names "ofSingleCommand" and "ofMultipleCommand" don't clearly convey their purpose. Consider more descriptive names based on the presence/absence of status.

-@RequiredArgsConstructor(staticName = "ofSingleCommand")
+@RequiredArgsConstructor(staticName = "withStatus")
 public class ExamQuotaEvent {
     // ... fields ...
     
-    public static ExamQuotaEvent ofMultipleCommand(ExamQuotaEventType type, String schoolName, Long value) {
+    public static ExamQuotaEvent withoutStatus(ExamQuotaEventType type, String schoolName, Long value) {
         return new ExamQuotaEvent(type, schoolName, value);
     }

9-12: Add field validation to prevent invalid events.

Consider adding validation for required fields to fail fast on invalid event creation.

private ExamQuotaEvent(ExamQuotaEventType type, String schoolName, Long value, ExamQuotaStatus status) {
    this.type = Objects.requireNonNull(type, "Event type cannot be null");
    this.schoolName = Objects.requireNonNull(schoolName, "School name cannot be null");
    this.value = Objects.requireNonNull(value, "Value cannot be null");
    this.status = status; // nullable by design
}
src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/MaxCapacityStatus.java (1)

3-5: LGTM! Clean enum implementation with good CRUD semantics.

The enum constants clearly represent the lifecycle operations for maximum capacity management. Consider adding JavaDoc to document when each status should be used.

+/**
+ * Status values for maximum capacity events in the exam quota system.
+ */
 public enum MaxCapacityStatus implements ExamQuotaStatus {
+    /** Create a new maximum capacity entry */
     CREATE, 
+    /** Update an existing maximum capacity entry */
     UPDATE, 
+    /** Delete a maximum capacity entry */
     DELETE
 }
src/main/java/life/mosu/mosuserver/infra/persistence/redis/CacheReader.java (1)

5-7: LGTM! Well-designed interface following best practices.

The interface correctly uses Optional<V> for null safety and has clear generic type parameters. Consider adding JavaDoc for better API documentation.

+/**
+ * Interface for reading values from cache by key.
+ *
+ * @param <K> the type of cache keys
+ * @param <V> the type of cached values
+ */
 public interface CacheReader<K,V> {
+    /**
+     * Reads a value from cache by key.
+     *
+     * @param key the cache key
+     * @return an Optional containing the cached value, or empty if not found
+     */
     Optional<V> read(K key);
 }
src/main/java/life/mosu/mosuserver/presentation/exam/ExamQuotaEventListener.java (1)

14-17: Consider adding error handling and logging for better observability.

The event listener delegates directly without error handling. If the service throws an exception, it could disrupt event processing.

 @ReactiveEventListener
 public void handleExamQuotaEvent(ExamQuotaEvent event) {
-    examQuotaService.handleExamQuotaEvent(event);
+    try {
+        log.debug("Handling exam quota event: type={}, school={}", event.getType(), event.getSchoolName());
+        examQuotaService.handleExamQuotaEvent(event);
+    } catch (Exception e) {
+        log.error("Failed to handle exam quota event: {}", event, e);
+        // Consider whether to rethrow or handle gracefully
+        throw e;
+    }
 }

Also add logger field:

private static final Logger log = LoggerFactory.getLogger(ExamQuotaEventListener.class);
src/main/java/life/mosu/mosuserver/application/exam/resolver/ExamQuotaEventResolver.java (2)

26-31: Minor formatting: add space after comma

-            examQuotaCacheManager.setCurrentApplications(e.getSchoolName(),0L);
+            examQuotaCacheManager.setCurrentApplications(e.getSchoolName(), 0L);

32-37: Minor formatting: add space before opening brace

-    private Consumer<ExamQuotaEvent> resolveDeleteAll(ExamQuotaEvent event){
+    private Consumer<ExamQuotaEvent> resolveDeleteAll(ExamQuotaEvent event) {
src/main/java/life/mosu/mosuserver/infra/persistence/jpa/ApplicationQueryRepositoryImpl.java (1)

140-143: Remove unused local variable

EnumPath<Subject> subject = Expressions.enumPath(Subject.class, "subject"); is never referenced and can be deleted to reduce noise.

-        EnumPath<Subject> subject = Expressions.enumPath(Subject.class, "subject");
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3fba787 and f5ff372.

📒 Files selected for processing (24)
  • src/main/java/life/mosu/mosuserver/application/exam/ExamQuotaCacheManager.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/application/exam/ExamQuotaService.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/application/exam/ExamService.java (2 hunks)
  • src/main/java/life/mosu/mosuserver/application/exam/resolver/ExamQuotaEventResolver.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/global/runner/ApplicationSchoolPreWarmRunner.java (0 hunks)
  • src/main/java/life/mosu/mosuserver/infra/config/SwaggerConfig.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/infra/persistence/jpa/ApplicationQueryRepositoryImpl.java (2 hunks)
  • src/main/java/life/mosu/mosuserver/infra/persistence/jpa/RefundQueryRepositoryImpl.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/infra/persistence/jpa/StudentQueryRepositoryImpl.java (2 hunks)
  • src/main/java/life/mosu/mosuserver/infra/persistence/redis/CacheEvictor.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/infra/persistence/redis/CacheLoader.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/infra/persistence/redis/CacheReader.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/infra/persistence/redis/CacheWriter.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheEvictor.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheLoader.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheReader.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheWriter.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/infra/persistence/redis/KeyValueCacheManager.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/exam/ExamQuotaEventListener.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/ApplicationStatus.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/ExamQuotaEvent.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/ExamQuotaEventType.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/ExamQuotaStatus.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/MaxCapacityStatus.java (1 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/life/mosu/mosuserver/global/runner/ApplicationSchoolPreWarmRunner.java
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheLoader.java (3)
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheEvictor.java (1)
  • RequiredArgsConstructor (6-13)
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheReader.java (1)
  • Component (8-18)
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheWriter.java (1)
  • Component (7-31)
src/main/java/life/mosu/mosuserver/application/exam/ExamQuotaService.java (1)
src/main/java/life/mosu/mosuserver/application/exam/ExamService.java (1)
  • Service (15-50)
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheReader.java (3)
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheEvictor.java (1)
  • RequiredArgsConstructor (6-13)
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheLoader.java (1)
  • Component (8-29)
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheWriter.java (1)
  • Component (7-31)
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheWriter.java (3)
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheEvictor.java (1)
  • RequiredArgsConstructor (6-13)
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheReader.java (1)
  • Component (8-18)
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheLoader.java (1)
  • Component (8-29)
🔇 Additional comments (20)
src/main/java/life/mosu/mosuserver/infra/persistence/jpa/StudentQueryRepositoryImpl.java (1)

1-10: Package & import realignment looks correct

Moving the implementation under infra.persistence.jpa and importing the domain‐level StudentQueryRepository cleanly separates interface (domain) from implementation (infrastructure) and keeps Spring scanning intact. No further action required.

src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheWriter.java (5)

7-10: LGTM! Clean class structure with proper dependency injection.

The class declaration follows Spring conventions with @Component for automatic discovery and Lombok's @RequiredArgsConstructor for clean dependency injection. The generic design allows for type-safe operations.


12-15: LGTM! Standard cache write operation.

The writeOrUpdate method properly delegates to Redis's set operation, which handles both write and update scenarios correctly.


27-30: LGTM! Proper cache deletion implementation.

The delete method correctly delegates to Redis's delete operation for removing cache entries.


1-5: Missing import for CacheWriter interface.

The class implements CacheWriter<String, T> but the import for this interface is missing from the imports section.

Add the missing import:

 package life.mosu.mosuserver.infra.persistence.redis;

 import lombok.RequiredArgsConstructor;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Component;
+import life.mosu.mosuserver.infra.persistence.redis.CacheWriter;

Likely an incorrect or invalid review comment.


17-25: No action needed: increment/decrement only applied to Long
All uses of DefaultRedisCacheWriter in the codebase occur via ExamQuotaCacheManager with T = Long. The increase/decrease calls therefore always target numeric values, and no runtime‐type errors can occur. No additional type constraints or validation are required at this time.

src/main/java/life/mosu/mosuserver/presentation/exam/ExamQuotaEventListener.java (2)

14-14: Verify reactive event listener usage aligns with processing requirements.

Using @ReactiveEventListener suggests asynchronous processing, but the method implementation is synchronous. Ensure this aligns with your requirements for quota event processing.

If quota events need to be processed synchronously (e.g., for immediate consistency), consider using @EventListener instead. If async processing is desired, ensure the service layer is designed to handle concurrent events properly.


9-18: LGTM! Clean event listener implementation with good separation of concerns.

The listener correctly delegates to the service layer and follows Spring best practices with constructor injection.

src/main/java/life/mosu/mosuserver/application/exam/ExamService.java (1)

27-31: Well-designed transition to event-driven architecture.

The change from direct cache manipulation to event publishing is a good architectural improvement that:

  • Decouples the service from cache implementation details
  • Enables better separation of concerns
  • Allows for more flexible event handling and potential future extensions

The use of the factory method ofMultipleCommand with LOAD event type appropriately initializes both current application count and max capacity for the school.

src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/ExamQuotaEventType.java (1)

5-22: Well-designed enum with clear separation of concerns.

The enum effectively combines event types with their valid statuses, providing a clean way to validate event-status combinations. The distinction between single and multiple commands is clear and helpful.

The use of bounded wildcards (Set<? extends ExamQuotaStatus>) is appropriate and type-safe, allowing different status enum types while maintaining the common interface.

src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheLoader.java (1)

8-29: Clean and well-implemented cache loader.

The implementation follows good practices:

  • Proper delegation in loadAll() method
  • Correct null handling in exists() method with defensive programming
  • Appropriate use of Redis operations for each method
  • Clean generic typing and Spring integration

The separation of concerns is well-maintained with each method having a single responsibility.

src/main/java/life/mosu/mosuserver/application/exam/resolver/ExamQuotaEventResolver.java (3)

17-24: Good use of exhaustive switch expression!

The switch expression without a default case ensures compile-time safety when new event types are added to the enum.


38-49: Excellent validation and error handling!

Good use of pattern matching with instanceof to validate the status type before processing. The IllegalArgumentException with a clear message helps with debugging.


51-59: Consistent validation pattern!

Good consistency with the validation approach used in resolveCurrentApplication. The combined handling of CREATE and UPDATE cases is appropriate since both set the value.

src/main/java/life/mosu/mosuserver/application/exam/ExamQuotaCacheManager.java (3)

15-28: Excellent refactoring to use cache abstractions!

The refactoring to extend KeyValueCacheManager and use injected cache interfaces significantly improves testability and separation of concerns.


30-38: Good use of Optional for null safety!

Returning Optional<Long> instead of nullable Long makes the API clearer and forces callers to handle the absence of values explicitly.


84-95: Clean key prefix management!

The inner enum pattern effectively encapsulates Redis key generation logic and ensures consistency across all cache operations.

src/main/java/life/mosu/mosuserver/infra/persistence/jpa/ApplicationQueryRepositoryImpl.java (2)

1-1: Confirm that component-scan still picks up this repository after the move

package life.mosu.mosuserver.infra.persistence.jpa;

Moving the class out of domain.admin means any Spring @ComponentScan / @MapperScan filters that were narrowly scoped to the previous package must be updated; otherwise the @Repository bean may not be registered and autowiring will fail at runtime.
Please double-check the root package settings in @SpringBootApplication or explicit @ComponentScan declarations.


14-14: Import addition looks correct

The explicit import for ApplicationQueryRepository is required after the package move and resolves compilation. 👍

src/main/java/life/mosu/mosuserver/infra/persistence/jpa/RefundQueryRepositoryImpl.java (1)

1-9: Package & import relocation looks correct
The move from domain.admin to infra.persistence.jpa cleanly separates persistence concerns from the domain layer; the additional interface import keeps compilation intact.

Comment on lines +10 to +12
@Override
public void evict(String key) {
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Implement the cache eviction logic.

The evict(String key) method is empty, which means cache entries won't actually be removed from Redis. This breaks the core functionality of a cache evictor and could lead to stale data and memory leaks.

Apply this diff to implement proper cache eviction:

 @Override
 public void evict(String key) {
+    redisTemplate.delete(key);
 }
🤖 Prompt for AI Agents
In
src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheEvictor.java
at lines 10 to 12, the evict(String key) method is currently empty, so it does
not remove cache entries from Redis. Implement this method to connect to the
Redis client and call the appropriate command to delete the cache entry
associated with the given key, ensuring proper cache eviction and preventing
stale data.

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

♻️ Duplicate comments (1)
src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/ExamQuotaEventType.java (1)

7-7: Add missing DECREASE status to maintain consistency with resolver.

Based on the past review comment, the CURRENT_APPLICATION event type is missing the DECREASE status that is handled in ExamQuotaEventResolver. This inconsistency could lead to validation failures.

-    CURRENT_APPLICATION(Set.of(ApplicationStatus.INCREASE, ApplicationStatus.CREATE, ApplicationStatus.DELETE)),
+    CURRENT_APPLICATION(Set.of(ApplicationStatus.INCREASE, ApplicationStatus.CREATE, ApplicationStatus.DECREASE, ApplicationStatus.DELETE)),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f5ff372 and 6a26113.

📒 Files selected for processing (2)
  • src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheReader.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/ExamQuotaEventType.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/life/mosu/mosuserver/infra/persistence/redis/DefaultRedisCacheReader.java
🔇 Additional comments (2)
src/main/java/life/mosu/mosuserver/presentation/exam/dto/event/ExamQuotaEventType.java (2)

11-12: LGTM: Empty status sets are appropriate for batch operations.

The LOAD and DELETE_ALL event types correctly use empty status sets since they represent batch operations that don't require specific status validation.


19-21: LGTM: Validation logic is correctly implemented.

The isValidStatus method properly validates whether a given status is valid for the event type using the predefined valid status sets.

@polyglot-k polyglot-k merged commit 2fc1269 into develop Jul 25, 2025
2 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.

1 participant