← Tenancy

TENANCY FEATURE

Tenant Context Resolution

See how Semitexa resolves the active tenant from subdomain, header, path, or query input before the rest of the platform runs.

Resolution Story

Resolve the tenant once, then the rest of the stack can trust it

This is the moment where a request stops being generic traffic and becomes tenant-scoped execution. Once that choice is made, configuration, data isolation, and background work can all stay deterministic.

HeaderStrategy Winning strategy in this example
ACME Resolved tenant context
4 Supported resolution paths

SubdomainStrategy

Fallback

Extracts tenant from the subdomain.

Best for: White-label apps and branded customer entrypoints

Tradeoff: Requires DNS and routing setup for each tenant-facing host.

acme.demo.semitexa.dev

HeaderStrategy

Wins now

Reads X-Tenant-ID request header.

Best for: API gateways and internal service calls

Tradeoff: Only works when upstream systems reliably forward the tenant header.

X-Tenant-ID: acme

PathStrategy

Fallback

Reads the first path segment as tenant ID.

Best for: Admin consoles and shared hosts where subdomains are not practical

Tradeoff: URLs stay explicit, but tenant identity becomes part of every visible path.

/acme/products

QueryParamStrategy

Fallback

Reads the ?tenant= query parameter.

Best for: Testing, debugging, and temporary operator tools

Tradeoff: Useful for diagnostics, but usually too weak as the primary production entrypoint.

?tenant=acme

Resolver Chain

Priority order stays explicit

The first strategy that confidently resolves a tenant stops the chain. That keeps the behavior reviewable instead of scattering tenant guessing across controllers or middleware fragments.

1
SubdomainStrategy
acme.demo.semitexa.dev
↓
2
HeaderStrategy
X-Tenant-ID: acme
resolved
↓
3
PathStrategy
/acme/products
↓
4
QueryParamStrategy
?tenant=acme
TenantContext: acme resolved by HeaderStrategy

Priority Design

Why the order is opinionated

Higher-signal entrypoints should usually run first. In this demo, subdomain beats header, then path, then query param.

That means branded tenant hosts win over weaker, mostly diagnostic inputs like ?tenant=....

$chain = new TenantResolverChain([
    new SubdomainStrategy(baseDomain: 'demo.semitexa.dev'),
    new HeaderStrategy(header: 'X-Tenant-ID'),
    new PathStrategy(PathStrategy::allowlist(['acme', 'globex', 'initech'])),
    new QueryParamStrategy(param: 'tenant'),
]);

What this boundary protects

Each strategy implements TenantResolverInterface::resolve(Request $request): ?TenantContext. Returning null means "not matched, let the next strategy try".

Once the chain resolves a tenant, that TenantContext becomes the foundation for configuration, data isolation, and background propagation. In a Swoole runtime the context is coroutine-scoped, so concurrent executions in different tenants do not bleed into each other.

The real message is simple: multi-tenancy is not a decorative mode. It is an execution boundary.