Skip to content

feat(menu): wire cache-ttl property to a CachingMenuProvider decorator#23

Merged
jlc488 merged 1 commit into
mainfrom
feat/menu-cache-ttl-wiring
May 30, 2026
Merged

feat(menu): wire cache-ttl property to a CachingMenuProvider decorator#23
jlc488 merged 1 commit into
mainfrom
feat/menu-cache-ttl-wiring

Conversation

@jlc488

@jlc488 jlc488 commented May 27, 2026

Copy link
Copy Markdown
Contributor

Follow-up #5/5 (and last of the backend wiring). #18 exposed devslab.kit.menu.cache-ttl-seconds on the Settings endpoint but the property wasn't actually wired to anything. This PR makes it real.

Property

DevslabKitProperties.Menu.cacheTtl (Duration, default PT5M). A zero or negative duration disables the cache decorator entirely — the right behaviour for tests that mutate menus or permissions and need the next read to see the change immediately.

CachingMenuProvider

New menu-core decorator wrapping any MenuProvider:

  • Per-user keying (user.id()) — the delegate's MenuTree is already permission-filtered, so the result varies per user even inside the same tenant. Per-user keys keep the cache correct without invalidation on tenant-wide menu edits; the TTL handles that.
  • ConcurrentHashMap with per-entry expiry timestamps. Lazy eviction on read, no background thread, no LRU bound. Plenty for "a few dozen admins per tenant, menus loaded once per session" — swap in Caffeine if the access pattern outgrows it.
  • Manual invalidate(userId) and invalidateAll() exposed for admin tooling that mutates menus and wants the next read to skip the cache.
  • Inject Clock so tests can advance time without sleeping.

Wiring (autoconfigure)

MenuAutoConfiguration.menuProvider:

  • Builds the database-backed DefaultMenuProvider as before.
  • Wraps it in CachingMenuProvider when devslab.kit.menu.cache-ttl is positive (default 5m).
  • Skips the decorator when ttl <= 0 — returns the bare DefaultMenuProvider.
  • @EnableConfigurationProperties added so the new Duration field binds correctly from configuration sources.

Test plan

  • ./gradlew build green (55 tasks; sample-app context loads, all tests pass)
  • Manual: set devslab.kit.menu.cache-ttl=PT0S, hit /menus admin endpoint, change a menu, hit /menus/tree from the UI → see the change immediately. Then set PT5M, repeat, confirm the change is delayed up to 5m or until invalidate*() is called.

Series wrap

This was the last of the five backend follow-ups. The full back-half of the contract-gap series:

# Topic PR
1 outcome / ip / userAgent first-class on AuditEvent #19
2 PolicyEvaluation richer decision (reason + matchedRules) #20
3 PermissionGrant path on Diagnostics matchedVia #21
4 async-queue-capacity wired to a bounded executor #22
5 cache-ttl wired to a per-user caching decorator this PR

The admin-ui's 9 CRUD pages still ship with hardcoded English labels — i18n migration of those pages is the last open item; landing it on the UI repo separately.

Follow-up #5/5 to the contract-gap series. #18 exposed
`devslab.kit.menu.cache-ttl-seconds` on the Settings endpoint but
the property wasn't actually wired to anything. This PR makes it
real.

Property
--------
- DevslabKitProperties.Menu gains cacheTtl Duration (default
  PT5M). A zero or negative duration disables the cache decorator
  entirely — the right behaviour for tests that mutate menus or
  permissions and need the next read to see the change immediately.

CachingMenuProvider
-------------------
- New menu-core CachingMenuProvider decorator wrapping any
  MenuProvider:
    * Per-user keying (user.id) — the delegate's MenuTree is
      already permission-filtered, so the result varies per user
      even inside the same tenant. Per-user keys keep the cache
      correct without invalidation on tenant-wide menu edits;
      the TTL handles that.
    * ConcurrentHashMap with per-entry expiry timestamps. Lazy
      eviction on read, no background thread, no LRU bound.
      Plenty for "a few dozen admins per tenant, menus loaded
      once per session" — swap in Caffeine if the access pattern
      outgrows it.
    * Manual invalidate(userId) and invalidateAll() exposed for
      admin tooling that mutates menus and wants the next read
      to skip the cache.
    * Inject Clock so tests can advance time without sleeping.

Wiring (autoconfigure)
----------------------
- MenuAutoConfiguration.menuProvider:
    * Builds the database-backed DefaultMenuProvider as before.
    * Wraps it in CachingMenuProvider when devslab.kit.menu.cache-ttl
      is positive (default 5m).
    * Skips the decorator when ttl <= 0 — returns the bare
      DefaultMenuProvider.
- @EnableConfigurationProperties added so the new Duration field
  binds correctly from configuration sources.
@jlc488
jlc488 merged commit 860fada into main May 30, 2026
1 check passed
@jlc488
jlc488 deleted the feat/menu-cache-ttl-wiring branch May 30, 2026 06:37
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