Keycloak OIDC, Kafka, Postgres per service, Redis — running live on this page. Built around three defects this architecture produces by default, each reproduced, measured and fixed.
This fetches a real token from Keycloak, seeds orders into tenant-a, then
requests the same 20-row page three ways. The endpoint reports its own cost
through Hibernate statistics — these numbers are measured on this server, now.
| Strategy | JDBC statements | Rows materialised | ms | Returned |
|---|
Adding join fetch to a Pageable query is the fix most
people ship. It drops the statement count — and quietly loads the
entire table to return twenty rows, because Hibernate cannot apply
LIMIT to a joined collection. Paging the ids first is flat at any size.
Tenancy is a Hibernate @TenantId discriminator resolved from the validated
JWT, never a method parameter. Hand-written JPQL is filtered too, and a thread with no
tenant bound reads nothing rather than everything.
Redis sits outside Hibernate, so @TenantId cannot protect it. The tenant is
part of the cache key, and the read shares one key expression with its eviction —
otherwise the evict deletes a key that was never written.
alice / alice (tenant-a) or
bob / bob (tenant-b).GET /api/orders as each. Same tables, same query — neither sees the
other's rows.readonly / readonly holds orders:read only, so
POST /api/orders returns 403.
Placing an order publishes to Kafka, inventory reserves stock and replies, and the order
moves to RESERVED — usually within a second.
Shared demo, so requests are rate limited and the seed endpoint is capped. The admin console is disabled. For the unthrottled version, clone the repo and run make up && make demo.
Source, architecture notes and the full test suite: github.com/YuvarajAravindan-AI-Agent/spring-boot-keycloak-kafka-multitenant