Fitness & Wellbeing · Multi-Tenant SaaS Case study
Confidential Fitness & Wellbeing Platform
A mature multi-tenant platform that runs fitness and wellbeing businesses end to end: class and appointment scheduling, reservations, memberships and credits, retail, revenue and reporting across many locations, with a web administrative application and consumer and staff mobile apps on the same APIs.
Fitness & Wellbeing · Multi-Tenant SaaS
Confidential Fitness & Wellbeing Platform Confidential Fitness & Wellbeing Platform
Backend & Platform Engineering
System map · Operational ecosystem
Engineering scope
- One consistent model Scheduling, entitlements, commerce and staff operations
- Scoped to places Staff permissions evaluated per object, not held globally
Who operates, and on what
Who operates
Members
Staff
Operations
Shared platform
One model of a booking, across many businesses
What it holds
Entitlements
Scheduling
Commerce
Attendance
Check-in and reporting
Cancellation releases the place, promotes the waitlist
Off the request path
- Multi-channel notifications
- Waitlist promotion
- Marketing automation
- Reporting aggregation
Engineering
Python · Django · Django REST Framework · Celery · PostgreSQL · Redis
Client and product name intentionally withheld · Described by feature area
- Role
- Backend & Platform Engineering
- Disclosure
- Client and product name intentionally withheld · Described by feature area
The problem
What the system had to solve.
Running a fitness business is not one workflow but several that must agree at all times: a schedule, the people booked onto it, what each member is entitled to book, what they were charged, and what the staff at each location can see and change. In a mature platform serving many businesses at once, a change to any one of those has to stay correct for all of the others.
What I owned
The responsibility behind the work.
Worked across member-facing and operational workflows in a large Django platform: scheduling and reservations, membership and credit entitlements, appointments, pricing and revenue records, reporting, marketing automation, integrations and the administrative application operators work in.
Built and extended product features inside a large Django platform: booking and reservation flows, membership and credit entitlements, appointment scheduling, pricing and revenue records, reporting, marketing automation and third-party integrations, alongside the administrative application operators work in. The interesting part is rarely one feature — entitlements are scoped by location, region, site and class type, so almost every change has to hold those rules while staying correct for every tenant.
Engineering approach
How it was built.
The difficulty is rarely a single feature. Entitlements are scoped by location, region, site and class type, so a booking has to resolve which membership or credit may pay for it before it can be taken. A late cancellation has to release the spot, promote the next person off a weighted waitlist and notify them, across email, SMS and push. Staff permissions are scoped to the locations they work at. Most of that work happens on background queues rather than in the request, and all of it runs multi-tenant.
Booking lifecycle
- Entitlement
- Reservation
- Cancellation
- Waitlist
- Notification
What may pay for this booking
The question underneath every reservation.
Taking a booking looks like writing a row. It is not. Before a place can be held, the platform has to work out what the member is entitled to use for it: which membership covers this class type at this location, whether a credit pack applies, whether an allowance has been consumed, and what should be charged if none of it applies.
Entitlements are scoped by location, region, site and class type, so the answer is specific to this member, this class and this place. Getting it wrong either gives away revenue or charges someone who had already paid.
Reservation path
- Member requests a place
- Resolve applicable entitlements Membership, credits, allowances
- Determine what pays
- Hold the place against capacity
- Record the commercial consequence
This is why the platform cannot be understood as a calendar with payments attached. The commercial model and the schedule are the same system.
Cancellation, waitlists and promotion
One action with consequences in four places.
A late cancellation is the most demanding operation in the product. It has to release the place, decide whether the entitlement is returned or forfeited under the applicable policy, find the next person on a weighted waitlist, promote them, and tell them across whichever channels they have — and none of that can leave the schedule in an inconsistent state.
What a cancellation triggers
- Member cancels
- Policy evaluated
- Place released
- Entitlement returned or forfeited
- Waitlist promotion Weighted, not first-come
- Notification Email, SMS and push
- Revenue record updated
Staff permissions are scoped to places
Role alone is not enough.
A front-desk employee at one studio is not a front-desk employee at every studio in the business. Permission has to be evaluated against the specific object being acted on — this location, this schedule, this member record — rather than against a role held globally.
Two models of staff access
Insufficient
- Role held globally
- Access to every location
- Cannot express regional management
- Over-permissions most staff
Implemented
- Permission evaluated per object
- Scoped to the locations someone works at
- Regional and site scopes expressible
- Least privilege by default
Work that happens off the request
And why it must happen exactly once.
Most of what the platform does in response to an action does not belong in the request that caused it: notifications across three channels, marketing automation, reporting aggregation, integration synchronisation. All of it runs on background queues.
Background work in a commercial platform has a specific hazard. A retried or double-scheduled task that charges a card, promotes a member off a waitlist or sends a campaign has done something that cannot be undone by retrying it correctly. Task execution is therefore guarded so a job that is already running is not started again.
What runs in the background
- Multi-channel notifications
- Waitlist promotion
- Marketing automation
- Reporting aggregation
- Integration synchronisation
- Scheduled recurring work
Once-only execution is not an optimisation here. It is the difference between a duplicate job being wasted work and a duplicate job being a second charge.
Many businesses on one platform
Every query is a tenant question.
The platform serves many independent businesses, each with their own locations, staff, pricing, schedules and members. Tenancy is not a feature; it is a property every request, every query and every background job has to carry correctly, resolved at the edge of the request rather than remembered by each piece of feature code.
What tenancy touches
Per tenant
- Locations and regions
- Staff and permissions
- Pricing and products
- Schedules and class types
- Configuration
Consequences
- Every query is scoped
- Background jobs carry tenant context
- Configuration is dynamic, not deployed
- One bug is every customer’s bug
The last point is the one that governs how change is made. On a single-tenant product a bad deploy affects one business; here it affects all of them at once.
Changing a mature schema safely
Migrations on a platform that cannot stop.
A long-lived platform accumulates a large schema and a long migration history, and the businesses on it operate during opening hours in several time zones. A migration that locks a large table is an outage for every tenant simultaneously.
What that discipline requires
- Migrations checked for unsafe operations before merge
- Schema changes separated from data backfills
- Defaults added without rewriting whole tables
- Changes reversible where possible
- Tested against realistic data volumes
Automated migration checking exists because reviewing every migration by eye for lock behaviour does not survive contact with a team shipping continuously.
The applications on top
Members, staff and operators are separate products.
Members book on their phones. Staff run the desk and the schedule. Operators configure the business. Those are different products with different rhythms, delivered as native mobile applications for members, a separate business application for staff, and web surfaces for operations and administration — all against the same API and the same domain model.
Client applications
- Platform API
- Member mobile apps iOS and Android
- Business application Staff operations
- Operations and administration Web
Moving outward from the monolith
Extracting a service without a rewrite.
A mature platform of this size does not get replaced. It gets extended at the edges, where a capability is separable enough to stand on its own. Newer capabilities are built as services against a shared client-to-service template, so an extraction follows an established pattern rather than each one inventing its own conventions.
What stays and what moves
Stays in the core
- Entitlements and reservations
- Scheduling and capacity
- Pricing and revenue records
- Anything transactional with the above
Extractable
- Onboarding workflows
- Integration surfaces
- Periodic and derived products
- Capabilities with their own lifecycle
Engineering scope
What I worked on.
- Class scheduling: sessions, types, classrooms and spot layouts
- Reservations, booking windows, booking limits and late-cancel rules
- Memberships, credits and entitlements scoped by location, region and class type
- Appointment scheduling and instructor assignment
- Pricing, revenue records and retail ordering
- Reporting, business metrics and marketing automation
- Third-party integrations, webhooks, identity and single sign-on
- Multi-tenant configuration and the operator-facing admin application
Why these decisions mattered
The trade-offs behind the platform.
Entitlement resolution before booking
Object-level staff permissions
Guarded background execution
Tenancy resolved at the edge
Automated migration safety checks
Extend at the edges, not rewrite
Product surface
Who uses it, against the same model.
- Member
- Booking, cancellation, waitlist position, membership and credit balance
- Instructor
- Assignment to sessions and to recurring schedule templates
- Front desk
- Check-in and attendance against the day’s sessions
- Operations
- Schedules, capacity, pricing, retail orders and reporting
- Administration
- Tenant configuration, staff permissions and integrations
Impact
What the work contributes to.
Contributed to a production platform that keeps scheduling, membership, commerce and studio operations in one connected workflow, across many businesses and many locations at once.
Technology
The technical surface.
- Python
- Django
- Django REST Framework
- Celery
- PostgreSQL
- Redis
- Ember.js
- Multi-tenant systems