Appearance
Multi-Tenancy
Blindstrader uses a database-per-tenant model. Every Brand, Supplier, and Retailer organisation gets a fully isolated MySQL database. This is distinct from row-level or schema-level multi-tenancy.
Why Database-per-Tenant?
| Benefit | Detail |
|---|---|
| True isolation | A bug or runaway query in Tenant A cannot corrupt or slow Tenant B |
| Independent scaling | High-volume tenants can be moved to dedicated database hosts without affecting others |
| Simpler backups | Each tenant's database is backed up and restored independently |
| Compliance | Easier to satisfy data residency requirements on a per-tenant basis |
| Easier offboarding | Deleting a tenant is DROP DATABASE blindstrader_brand_{slug} |
Database Naming Conventions
| Tenant type | Pattern | Example |
|---|---|---|
| Brand | blindstrader_brand_{slug} | blindstrader_brand_louvolite |
| Supplier | blindstrader_supplier_{slug} | blindstrader_supplier_cassidy |
| Retailer | blindstrader_retailer_{slug} | blindstrader_retailer_newblinds |
Service-level operational databases (not per-tenant):
| Service | Database |
|---|---|
| Identity | blindstrader_identity |
| Supply Chain | blindstrader_supply_chain |
| Payment | blindstrader_payment |
| Platform Ops | blindstrader_platform |
| Notification | blindstrader_notification |
Tenant Resolution
Brand Service (stateless federation)
The Brand Service resolves the tenant from the incoming request. There is no central tenant registry in the Brand Service — it uses the HTTP_HOST header or an explicit brand_slug URL parameter.
Flow:
- Incoming request to
brand.blindstrader.com/api/brands/louvolite/fabrics - Middleware extracts
louvolitefrom the route. - Connection is switched to
blindstrader_brand_louvolite. - Query executes against the correct tenant database.
- Connection is returned to the pool.
Retailer Service (stancl/tenancy)
The Retailer Service uses the stancl/tenancy package for automatic tenant resolution:
- Domain-based resolution:
newblinds.blindstrader.com→blindstrader_retailer_newblinds - Redis caches the domain → tenant ID mapping for fast lookups.
Cross-Tenant Access
Cross-tenant queries are the primary challenge in a database-per-tenant model. Blindstrader handles this via:
Event Bus (Kafka)
Services emit events rather than querying each other's databases. A Supplier does not query the Brand's database directly — it subscribes to BrandCatalogUpdated events and maintains a local copy of the data it needs.
Brand Service Federation
The Brand Service acts as a controlled gateway. When a Supplier needs Brand catalog data, it goes through the Brand Service API, which enforces permissions before routing to the Brand's tenant database.
Permission Cache (Redis)
Permission checks between tenants are cached in Redis:
supplier:{supplier_id}:brand_access:{brand_id} → {permissions_json}TTL: 5 minutes. Cache is invalidated immediately on permission changes.
Tenant Provisioning
When a new Brand, Supplier, or Retailer account is created:
- The Identity Service creates the organisation record.
- A
OrganisationCreatedKafka event is emitted. - The relevant service (Brand/Supplier/Retailer) consumes the event.
- A new database is created:
CREATE DATABASE blindstrader_{type}_{slug}. - Migrations are run against the new database.
- The tenant is ready to use.
This is handled automatically — administrators do not need to run migrations manually for new tenants.
Tenant Offboarding
To remove a tenant:
- Platform Ops admin terminates the account in the management panel.
- A
OrganisationTerminatedevent is emitted. - The relevant service drops the tenant database after a 30-day grace period.
- All Kafka events referencing the tenant are ignored (idempotent consumers check if the tenant exists).
Data retention
The grace period ensures accidental terminations can be reversed. The database is archived (not immediately dropped) and can be restored within 30 days.