The SaaS problem
A SaaS product runs one codebase for many customers, each of whom must see only their own data and their own configuration — with strong isolation and no per-customer branches. The naive approaches (a deployment per tenant, or if (tenant == …) sprinkled through the code) don’t scale.
Chenile’s answer follows the pattern you’ve seen all along: resolve the tenant once, put it in the context, and let the framework route everything else by configuration. Your business logic never mentions the tenant.
One resolution, everywhere
The tenant arrives on the request — typically the header x-chenile-tenant-id — and is resolved into the ContextContainer, the request-scoped context that Owiz propagates through the whole pipeline (and onward through proxies and messaging). From that single fact, three things route themselves. Pick a tenant:
The three routings
1 · Writes — multi-datasource-utils. This chenile-core module registers a routing DataSource that selects the physical datasource from ContextContainer.getTenant(). You declare each tenant’s connection under chenile.multids:
chenile:
multids:
defaultTenantId: tenant1
datasources:
tenant1: { jdbcUrl: "jdbc:…/tenant1", username: sa, maximumPoolSize: 5 }
tenant2: { jdbcUrl: "jdbc:…/tenant2", username: sa, maximumPoolSize: 5 }
JPA writes for a request automatically land in the right tenant’s database — database-per-tenant isolation with zero routing code in your service.
2 · Reads — Chenile Query. The query side is independently tenant-aware: a QueryTenantResolver plus QueryDatasourcesProperties point each tenant’s searches at its own (often read-replica) query datasource. Same endpoint, isolated result sets.
3 · Configuration — cconfig. Per-tenant settings are overrides keyed by a custom attribute = the tenant ID. One tenant can turn a feature on, change a limit, or rebrand a value while the shared base config stays clean.
And per-tenant behaviour, too
Isolation isn’t only about data. The convention-based resolver can select a tenant-specific bean for an action or hook (via a context prefix like x-tenant), so one customer can have bespoke logic on a transition without forking the service. Security policies, run at the gateway and the last mile, enforce that a caller can only ever act within its own tenant.
ContextContainer; then writes route through multi-datasource-utils, reads through Chenile Query's tenant datasources, config through cconfig overrides, and behaviour through convention-resolved per-tenant beans — all while your business code stays completely tenant-agnostic.Why teams choose Chenile for SaaS
✅ You get
- Database-per-tenant isolation with no routing code
- Tenant-scoped reads, writes, config and behaviour
- One deployment, one codebase, many customers
- Tenant context that flows across HTTP, proxies and messages
🧩 The pieces
multi-datasource-utils— write routingchenile-query— read routingcconfig— per-tenant configurationContextContainer+ security — the tenant boundary