Skip to content

feat: add pre-write interceptors for external caches#1006

Open
youjie23 wants to merge 7 commits intoalibaba:masterfrom
youjie23:feature-remote-write-interceptor
Open

feat: add pre-write interceptors for external caches#1006
youjie23 wants to merge 7 commits intoalibaba:masterfrom
youjie23:feature-remote-write-interceptor

Conversation

@youjie23
Copy link
Copy Markdown

Background

JetCache currently lacks a unified extension point before remote cache writes.

When users want to add logging, auditing, metrics, big-key detection, or write rejection before cache writes, they usually have to rely on one of these approaches:

  • AOP around business methods.
  • Wrappers around specific Redis clients or cache implementations.
  • Custom logic embedded into value encoders / decoders.

Each of these approaches has clear limitations:

  • Business-layer AOP only covers part of the call chain. It cannot reliably cover all JetCache write paths such as @Cached, @CreateCache, and
    CacheManager.getOrCreateCache(QuickConfig), and it cannot observe the final encoded payload size.
  • Backend-specific wrappers are tightly coupled to Jedis, Lettuce, Spring Data Redis, or Redisson, which makes the behavior non-uniform across supported backends.
  • Pushing auditing or validation into the serialization layer mixes concerns and does not provide a clean way to reject a write while preserving JetCache's base API semantics.

Because of that, JetCache needs a unified pre-write interception capability at the external cache abstraction layer.

What this PR changes

This PR introduces ExternalCacheWriteInterceptor and executes it before external cache write attempts.

The change includes:

  • A unified pre-write hook for external caches.
  • Coverage for PUT, PUT_ALL, and PUT_IF_ABSENT.
  • Configuration entry points via @Cached, @CreateCache, QuickConfig, and starter-level global configuration.
  • Spring-based interceptor bean resolution.
  • Integration for Redis/Jedis, Lettuce, Spring Data Redis, Redisson, and MockRemoteCache.
  • Documentation, tests, and sample updates.

Why this is valuable

  • It provides a backend-agnostic place to implement logging, auditing, metrics, big-key detection, and write guardrails.
  • It avoids scattering the same cross-cutting concern across business code or backend-specific wrappers.
  • Interceptors can either allow the write or reject it.
  • Rejections and interceptor failures are reported through CacheResult instead of being thrown directly from the base write methods, which keeps the existing JetCache API style
    consistent.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 27, 2026

CLA assistant check
All committers have signed the CLA.

@areyouok
Copy link
Copy Markdown
Collaborator

This change modifies too many files just to implement one feature, and I think this requirement can be achieved using condition/postCondition instead.

@youjie23
Copy link
Copy Markdown
Author

This change modifies too many files just to implement one feature, and I think this requirement can be achieved using condition/postCondition instead.

Thanks for the review.

I agree, the PR is relatively large, but I do not think condition/postConditioncan replace this feature.

They are method-level controls for @Cached, while this change adds a reusable cache-layer hook before remote writes are dispatched. This distinction matters because the feature is intended to cover not only @Cached, but also @CreateCache, QuickConfig, direct cache API usage, and global starter configuration.

From a global cache governance/observability perspective, putting the same generic logic on every annotation would be a maintenance burden. For use cases like auditing, metrics, write validation, or big-key detection, duplicating expressions across many methods is hard to keep consistent and easy to miss.

There is also a data gap: postCondition can inspect method context and result, but it does not naturally expose the remote-write metadata, such as the encoded size. If we try to derive that in postCondition, we would need to encode again in user code, which introduces additional serialization cost and duplicates work.

Therefore, I think condition/postCondition are suitable for method-specific business rules, but not for a reusable remote-write governance mechanism.

If you think the current PR is too large to review comfortably, I can split it into smaller ones to make the review easier.

@areyouok
Copy link
Copy Markdown
Collaborator

JetCache itself is highly customizable, allowing users to handle many things on their own.

For example, to implement this feature, users can simply customize the Cache creation process and wrap it with a ProxyCache before returning the Cache instance. There’s no need for such a complicated approach.

This change is far too extensive, so I cannot accept it.

@youjie23
Copy link
Copy Markdown
Author

JetCache itself is highly customizable, allowing users to handle many things on their own.

For example, to implement this feature, users can simply customize the Cache creation process and wrap it with a ProxyCache before returning the Cache instance. There’s no need for such a complicated approach.

This change is far too extensive, so I cannot accept it.

Thanks for your feedback, understood.

I agree that JetCache is customizable and that a user-side solution can be built by customizing cache creation and wrapping the returned Cache with a ProxyCache. If the project preference is to keep this kind of capability out of the core, I respect that.

The main reason I proposed a built-in hook was not that this is impossible on the user side, but that the key part of the change lies inside the concrete remote cache implementations. That is where the extension point would ideally exist to enable this capability both reliably and efficiently.​ Most of the additional changes in this PR are there to make that hook usable through configuration, annotations, QuickConfig, starter integration, documentation, and tests.

A ProxyCache-based solution operates at the Cache API layer rather than inside the concrete remote write path. Because of that, it does not naturally have access to some final write metadata produced by the backend implementation, especially post-encoding data such as the final encoded key/value size.

This point matters for scenarios like big-key detection and interception. In a ProxyCache approach, obtaining the encoded size would typically require re-encoding in user code, which adds extra serialization cost and may also deviate from the actual encoder/configuration used by the remote cache implementation.

Therefore, I still believe there is a technical difference between a user-side ProxyCache wrapper and a built-in remote-write hook.​ However, I understand your concern about the size and invasiveness of this PR.

May I kindly ask for your guidance on how you'd prefer to proceed with this PR? For example:

  • Would you like me to split it into a series of smaller, more focused changes for easier step-by-step review and integration?
  • Or, would it be helpful to explore a more minimal implementation first, perhaps focusing only on the core extension point?

I'm happy to adapt my next steps based on your feedback. Thank you for considering it.

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.

3 participants