Skip to content

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?

BenefitDetail
True isolationA bug or runaway query in Tenant A cannot corrupt or slow Tenant B
Independent scalingHigh-volume tenants can be moved to dedicated database hosts without affecting others
Simpler backupsEach tenant's database is backed up and restored independently
ComplianceEasier to satisfy data residency requirements on a per-tenant basis
Easier offboardingDeleting a tenant is DROP DATABASE blindstrader_brand_{slug}

Database Naming Conventions

Tenant typePatternExample
Brandblindstrader_brand_{slug}blindstrader_brand_louvolite
Supplierblindstrader_supplier_{slug}blindstrader_supplier_cassidy
Retailerblindstrader_retailer_{slug}blindstrader_retailer_newblinds

Service-level operational databases (not per-tenant):

ServiceDatabase
Identityblindstrader_identity
Supply Chainblindstrader_supply_chain
Paymentblindstrader_payment
Platform Opsblindstrader_platform
Notificationblindstrader_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:

  1. Incoming request to brand.blindstrader.com/api/brands/louvolite/fabrics
  2. Middleware extracts louvolite from the route.
  3. Connection is switched to blindstrader_brand_louvolite.
  4. Query executes against the correct tenant database.
  5. 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.comblindstrader_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:

  1. The Identity Service creates the organisation record.
  2. A OrganisationCreated Kafka event is emitted.
  3. The relevant service (Brand/Supplier/Retailer) consumes the event.
  4. A new database is created: CREATE DATABASE blindstrader_{type}_{slug}.
  5. Migrations are run against the new database.
  6. 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:

  1. Platform Ops admin terminates the account in the management panel.
  2. A OrganisationTerminated event is emitted.
  3. The relevant service drops the tenant database after a 30-day grace period.
  4. 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.

Blindstrader Platform Documentation