Files
mdm-ordinis/ordinis-migrations/src/main/resources/db/changelog
Andrei Zimin ec0c74afdf feat(user-display): persistent DB cache + Keycloak resolver hook (Phase 1+2 wiring)
Closes "ток мой UUID отображается" pain reported on staging today: any
reviewer / maker / publisher / actor whose JWT sub never landed in the
JVM cache shows up as a short UUID in audit / reviews / changelog /
events / history / record drawer / webhook detail / record version
history. Restart the pod and even captured users disappear.

Three-tier resolve, replacing the in-memory-only cache that was the
"Альтернатива (на будущее)" TODO in the original UserDisplayService:

  1. JVM ConcurrentHashMap hot cache (~µs, capped at 10k).
  2. user_display_cache Postgres table (~ms, persistent across restart).
     New migration 0023 — sub PK + preferred_username + name + email +
     source enum + synced_at + updated_at, plus an index on synced_at
     for the scheduled bulk sync that lands later.
  3. Keycloak Admin API on-demand (Phase 2) — dispatched to optional
     KeycloakUserResolver bean. When the bean isn't wired (no Keycloak
     admin creds yet), the third tier is a no-op and UserCell falls
     back to short UUID exactly like before.

Source enum on every row — JWT_CAPTURE / KEYCLOAK_SYNC /
KEYCLOAK_ON_DEMAND — so the eventual scheduled sync can prefer JWT-
captured rows (richest claims as the user actually saw them) and the
on-demand fallback can be distinguished from bulk sync in audits.

JwtUserCaptureFilter and UserDisplayController are unchanged — they
already used UserDisplayService.put / find with the same signatures.
The behavior change is invisible at the API layer: same /admin/users/
{sub}/display endpoint, same 404 user_not_cached on miss; persistence
just survives pod restart now.

mergeFrom on UserDisplayCacheEntry deliberately keeps the existing
non-blank field when an update brings null — Keycloak sync may have a
richer email than JWT capture; don't blank it out.

Phase 2 will land a KeycloakAdminUserResolver impl + vault secret
plumbing on this same branch. Phase 3 (scheduled bulk sync via
@Scheduled) is a separate sprint — it needs the Keycloak admin client
already in place.
2026-05-14 14:45:14 +03:00
..