Platform Architecture

ShopSTAR3 is a multi-tenant platform. Every design decision at the platform layer — store isolation, identity, API gateway, and access control — flows from a single requirement: a single deployment must serve many stores securely and independently, with no data or privilege leaking across store boundaries.

Store Isolation#

All stores share a single PostgreSQL cluster per service. Isolation is enforced at the column level using Hibernate’s DISCRIMINATOR strategy: every table carries a store_id discriminator column, and every query is automatically scoped to the requesting store.

This is a deliberate choice over schema-per-store (one PostgreSQL schema per tenant). Schema-per-store appears attractive for isolation but does not scale — connection pool overhead grows linearly with the number of stores, and DDL migrations must be executed once per schema. The column-based approach keeps the database footprint constant regardless of store count.

PropertyColumn-based (store_id)Schema-per-store
Isolation mechanismHibernate DISCRIMINATOR + query filterPostgreSQL schema boundary
Connection pool growthConstantLinear with store count
DDL migrationsSingle migration per serviceOne migration execution per store
Cross-store queryImpossible at ORM layerImpossible at schema layer

Identity Service#

The identity-service is the platform-wide principal store. It owns authentication and JWT issuance for all principals across all stores.

Principal Categories#

CategoryScopeNotes
StaffPlatform-wideA staff member can hold roles on multiple stores simultaneously.
CustomersStore-scopedA customer account belongs to exactly one store.

Credential Sources#

Each store can independently configure how its principals authenticate. The identity service supports two modes, and they coexist on the same store:

  • In-database credentials — the default; username + hashed password stored directly in the identity service’s own database.
  • Federated identity — SAML 2.0 or OIDC; configured per store. The identity service acts as the service provider (SAML) or relying party (OIDC) and delegates authentication to the external IdP.

Regardless of the credential source, the identity service always issues a normalized platform JWT. Application code downstream never needs to know whether a principal authenticated via password, SAML assertion, or OIDC token. SAML attributes are preserved in the JWT payload so downstream services can access them without hitting the identity service again.

Token Storage#

Refresh tokens are stored in Redis. This allows instant revocation by deleting the token from Redis without waiting for the JWT expiry window.

flowchart LR
    A([Browser / Client]) -->|Credentials or SAML/OIDC redirect| IS[identity-service]
    IS -->|Validates against| DB[(PostgreSQL\ncredentials)]
    IS -->|Or delegates to| IDP([External IdP\nSAML / OIDC])
    IDP -->|Assertion / token| IS
    IS -->|Stores refresh token| RD[(Redis)]
    IS -->|Issues normalized| JWT([Platform JWT])

API Gateway#

The gateway-service is a custom Quarkus service that sits behind the AWS Application Load Balancer. It is the single entry point for all inbound traffic to the platform.

Responsibilities#

ResponsibilityDetail
JWT validationVerifies the platform JWT signature against the JWKS published by identity-service.
Header injectionEnriches every forwarded request with X-User-Id, X-Principal-Type, X-Store-Id, and X-Roles.
Coarse auth enforcementEnforces path-level access rules (e.g. admin paths require a Staff principal).
RoutingForwards the enriched request to the correct upstream service.

What the Gateway Does Not Do#

The gateway enforces coarse, path-level authorization only. It does not enforce fine-grained resource-level rules — that is the responsibility of each downstream service using the injected headers.

flowchart LR
    ALB([AWS ALB]) --> GW[gateway-service\nQuarkus]
    GW -->|Validate JWT via JWKS| IS[identity-service]
    GW -->|X-User-Id\nX-Principal-Type\nX-Store-Id\nX-Roles| SVC1[service-a]
    GW --> SVC2[service-b]
    GW --> SVC3[service-n]

RBAC#

Role-based access control is per-store. A principal can hold a different role on each store it has access to — for example, admin on Store A and viewer on Store B at the same time.

There are no global roles that implicitly grant cross-store access. A role granted on one store never provides any privilege on another store, regardless of how broad the role appears. This is enforced by keeping store_id in the role grant record and including it in the X-Roles header so every downstream service can apply resource-level checks.

Cloud Strategy#

ConcernDecision
Primary cloudAWS — infrastructure defaults, managed service selection, and tooling are AWS-first.
PortabilityArchitecture must remain deployable on Azure and GCP without fundamental rework. Istio is used as the service mesh precisely because it runs identically on all three clouds.
CI/CDTBD — likely Bitbucket + Jenkins + ArgoCD.
Kubernetes distributionTBD — Amazon EKS is the likely initial choice.