A SaaS advertising network dashboard showing financial KPIs

Legacy SaaS Refactoring and Migration to Multi-Tenant Architecture

Industry
IT, Healthcare, Marketing & Advertising, Software Products
Technologies
AWS, Python, Cloud

Summary

We re-engineer legacy applications that were written for one organisation and then copied for each new account into genuinely multi-tenant platforms running one codebase over a shared pool of compute. This kind of engagement covers an application audit, a prioritised modernisation roadmap, refactoring for testability, the tenancy model across data and identity, cloud deployment, and migration of existing deployments onto the shared platform.

The Challenge

A per-organisation deployment model becomes expensive in a predictable way. Every instance needs its own upgrade, each accumulates its own configuration drift, and support effort scales with the number of instances. The fix is structural rather than functional: multi-tenancy changes how identity, data access and deployment work across the whole application, and almost none of that is visible as a feature.

Two things make it genuinely hard. First, platforms of this age have usually passed through several teams, so nobody holds a complete picture of the code's health - where business logic is duplicated, where data access bypasses any layer, which components have no tests, which libraries are unsupported. Tenancy cannot be introduced safely into code that cannot be tested. Second, the failure that matters most is cross-tenant data exposure, and it almost always arrives through a path that skipped the data access layer: a report query, an export, an admin screen, a cached object keyed without tenant.

The Solution

Audit before anything else

The starting point is an application audit producing a written report on code quality, architecture and dependency risk. That report becomes a prioritised backlog and a modernisation roadmap, so sequencing is driven by risk rather than by whichever module is most annoying to work in.

Refactoring for testability

  • Decomposition - splitting large modules into smaller components with defined responsibilities, so behaviour can be pinned by unit tests before it is changed.
  • Characterisation tests - capturing current behaviour, odd parts included, so refactoring is verifiable rather than hopeful.
  • Consistency - one coding standard and the removal of duplicate implementations, which is what stops a later change cascading into unrelated functionality.
  • Dependency isolation - pulling configuration, credentials and environment assumptions out of code so the same artefact runs anywhere.

A staging environment mirroring production, deployed through a managed platform such as AWS Elastic Beanstalk, gives the team somewhere to verify changes before they reach live traffic.

The tenancy model

The central decision is the isolation level, and it is a trade-off rather than a best practice. A shared schema with a tenant discriminator column is cheapest to operate and easiest to leak from; schema-per-tenant isolates more strongly at the cost of migration complexity; database-per-tenant isolates hardest and costs most. Whichever is chosen, isolation is enforced structurally, at the data access layer or through row-level security, never by remembering to add a filter in each query. Identity is separated in parallel, with each tenant holding its own authentication configuration, user directory and role definitions, and tenant context resolved once at request entry then carried through the call chain. Background jobs, caches, file storage paths and log records all need the same scoping, and caches are the most commonly forgotten.

How the work runs

We take ownership of the whole modernisation and produce the technical documentation the platform never had, because that absence is usually part of the original problem. Work proceeds as audit, refactor, tenancy, then migration of existing deployments onto the shared platform, with each stage releasable. Advertising workloads bring their own shape - campaign management, scheduled delivery jobs, and reporting queries that must now aggregate per tenant without exposing cross-tenant volumes. Automated tests assert isolation rather than only functionality, and per-tenant rate limits contain noisy neighbours.

What This Delivers

One codebase serves every tenant, so an upgrade happens once instead of repeatedly, and configuration drift stops accumulating. Onboarding a new organisation becomes provisioning rather than deployment, and support works against a single known-good version. Isolation is enforced by structure and verified by tests, which is what makes the platform defensible to security review, and migration runs tenant by tenant rather than as one irreversible cutover.

Technologies and Tools

Python application code on AWS, with deployment through a managed platform such as Elastic Beanstalk, mirrored staging and production environments, relational storage with tenant isolation enforced at the data access layer or through row-level security, automated unit and characterisation test suites, and per-tenant rate limiting and quota controls.