Skip to main content

Cell residency — How it works

Overview

A cell is one deployment of the platform that holds the data of the tenants assigned to it, and no others. The cell engine answers one question for the instance it runs in: does this cell serve the tenant this request claims? When the answer is no and the instance is enforcing, the request is refused with 421 Misdirected Request rather than served, proxied or quietly emptied.

The engine is four classes under core/src/main/java/com/zhenus/uhp/api/core/engines/cell/:

ClassResponsibility
CellResidencyPropertiesBinds platform.cell.* — this cell's code and whether it enforces residency.
CellResidencyGuardserves(tenantId) — whether the tenant belongs here.
CellResidencyFilterA OncePerRequestFilter that refuses a misdirected request with 421.
CellResidencySecurityCustomizerPlaces the filter in the security chain.

Configuration

KeyDefaultMeaning
platform.cell.code""This cell's code, e.g. NG-1. Empty on a facility instance.
platform.cell.enforce-residencyfalseWhether a request for a tenant this cell does not serve is refused.

enforce-residency defaults to false deliberately. Every deployment today is a single facility instance serving its own tenants, where the question does not arise. Enforcement is a cloud decision taken per cell; a default of true would start refusing requests on installations that have no cell model at all.

Key rules & invariants

  • The check is local, and that is the point. A cell holds only its own tenants, so a tenant with no row in this cell's database is a tenant this cell does not serve. No call to the global directory is needed — which matters for more than latency: the directory being unreachable must not take every cell down with it. The directory decides where a tenant lives; a cell only needs to know whether that is here.
  • There is deliberately no forwarding. A "helpful" fallback that proxied the request to the cell which does hold the tenant would move that country's patient data across a border on demand — the single thing the cell model exists to prevent. The refusal is the feature.
  • 421, not 404 and not 403. The request reached a server that cannot produce a response for it, and a client able to re-resolve should retry elsewhere. A 404 would repeat the silent-empty failure in a different costume; a 403 would claim the caller lacks permission, which is untrue and would send an operator hunting through roles.
  • The refusal names no other cell. Where a tenant lives is the directory's answer to give. Confirming "not here, try NG-2" from an unauthenticated edge would turn this into a lookup service.
  • An unscoped request passes. A request that has not claimed a tenant yet — logging in, listing the tenants to choose from — reads no tenant-scoped data and must keep working.
  • The Global tenant passes. The platform catalogue exists in every cell by design; it is shared, not misdirected. The short circuit also spares Global a lookup it would always pass.

What this replaces

Without the filter a misdirected request does not fail — it succeeds and returns nothing. Every service scopes its queries by the session tenant, so a request that reached the wrong cell reads an empty result set, and the caller sees "this patient has no records" rather than "you are asking the wrong country". That is the worst possible answer to give a clinician about a patient who does exist.

  • Cell directory — the global tenant→cell assignment this engine deliberately does not consult.