Skip to content

feat(cache): unified cache-aside abstraction with tag-based invalidation - #2166

Merged
RUKAYAT-CODER merged 2 commits into
EarnQuestOne:mainfrom
Nimatstar:feat/2159-cache-aside-tag-invalidation
Aug 18, 2026
Merged

feat(cache): unified cache-aside abstraction with tag-based invalidation#2166
RUKAYAT-CODER merged 2 commits into
EarnQuestOne:mainfrom
Nimatstar:feat/2159-cache-aside-tag-invalidation

Conversation

@Nimatstar

Copy link
Copy Markdown
Contributor

Closes #2159

Summary

Caching was applied ad-hoc per service with no shared key scheme or coordinated invalidation, making stale reads and cache/DB divergence easy to introduce. This PR adds a single cache-aside layer with namespaced keys and tag-based invalidation across the hot read paths.

Changes

  • CacheService primitives (src/modules/cache/cache.service.ts) — getOrSet(key, ttl, tags, loader) and invalidateTag(tag), layered over the service's existing tag registry (set(..., tags) / invalidateByTag), so read/populate and coordinated invalidation share one flow.
  • Key & tag conventions (src/modules/cache/cache-tags.ts, new) — CacheKeys, CacheTags, and CacheTtl define namespaced keys and the tags (quest:<id>, quest:list, user:<id>, analytics:platform) that group derived entries.
  • Declarative opt-in (src/common/interceptors/cacheable.interceptor.ts, new) — CacheableInterceptor + @Cacheable() decorator back a handler with getOrSet; handlers without the metadata pass through untouched. Registered and exported from CacheModule.
  • Migrated read paths onto getOrSet with tags, emitting invalidateTag on the matching writes:
    • QuestsServicefindAll/findOne cached; create/update/remove invalidate the quest/list tags.
    • UsersService.findById — cached per-user; update invalidates user:<id>.
    • PlatformAnalyticsService.getPlatformStats — served through the unified cache tagged analytics:platform (the existing snapshot path is preserved in resolvePlatformStats).

Notes

  • CacheModule is @Global, so the new injections resolve everywhere without extra module wiring; backward compatible (reads fall back to the loader on a miss, exactly as before).
  • cache, users, and analytics module CHANGELOG.md files updated under [Unreleased].

Verified locally: npm run build, npm run lint (0 errors), prettier --check, npm run openapi:generate (app bootstraps, spec generated), and the backend module changelog-discipline check all pass.

…ion (EarnQuestOne#2159)

Caching was applied ad-hoc per service with no shared key scheme or coordinated
invalidation. Add a single cache-aside layer with namespaced keys and tag-based
invalidation across the hot read paths:

- CacheService.getOrSet(key, ttl, tags, loader) and invalidateTag(tag) unify
  the read/populate/invalidate flow over the existing tag registry.
- New cache-tags.ts defines the namespaced key + tag conventions (CacheKeys,
  CacheTags, CacheTtl).
- New CacheableInterceptor + @Cacheable() decorator let read endpoints opt in
  declaratively; registered/exported from CacheModule.
- Quest listing, user profile (findById), and platform-analytics reads now go
  through getOrSet with tags, and the corresponding writes emit invalidateTag.
…-tag-invalidation

# Conflicts:
#	BackEnd/src/modules/analytics/CHANGELOG.md
#	BackEnd/src/modules/analytics/services/platform-analytics.service.ts
@RUKAYAT-CODER

Copy link
Copy Markdown
Contributor

Thank you for contributing to the project.

@RUKAYAT-CODER
RUKAYAT-CODER merged commit 248e8c6 into EarnQuestOne:main Aug 18, 2026
8 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.

Introduce a unified cache-aside abstraction with tag-based invalidation across quests, users, and analytics reads

2 participants