Tenant resolution
Each request needed to resolve which client context to use:
Request → Subdomain Parsing → Tenant Lookup → Context Injection
(client.benpal.co.uk) (Database) (Request Scope)
The tenant context was available throughout the request pipeline, influencing:
- Which brand assets to serve
- Which feature toggles to evaluate
- Which configuration values to apply
- Which integrations to invoke
Less JIT compilation strategy
Dynamic CSS generation required careful performance optimisation:
- First request: Compile Less with client variables, cache result
- Subsequent requests: Serve cached CSS directly
- Brand update: Invalidate cache for affected client
- Deployment: Optional pre-warming of caches for all clients
Cache keys incorporated both client identifier and stylesheet version, enabling safe deployments while maintaining cache efficiency.
Feature toggle patterns
Toggles were evaluated at multiple levels:
Platform Default → Client Override → User Group Override → User Override
This hierarchy allowed:
- Platform-wide feature flags for progressive rollout
- Client-specific feature enablement based on contracts
- Pilot groups within clients for testing
- Individual user overrides for support scenarios
Multi-tenant testing
The testing strategy addressed the combinatorial complexity:
- Unit tests: Core functionality tested without tenant context
- Integration tests: Parameterised tests run against multiple tenant configurations
- Visual regression: Screenshot comparison for each client's branded experience
- Contract tests: Verified integrations for each client's external systems
Lessons learned
- Cache invalidation is genuinely hard - invest in tooling to trace cache state
- Feature toggles accumulate technical debt - establish lifecycle management early
- Enterprise clients will request "just one small customisation" - have a clear framework for what is and is not configurable
- Multi-tenant bugs are subtle - reproduce issues in the specific client context, not just development defaults