when we started the school ERP we were on firebase. one school, one database, everything in one place. it was fine until it was not.
then marketing kicked in, real clients were about to come on board, and we decided to move from firebase to postgres.
but the backend had no real layering yet. so before the migration Jagan sir made us do separation of concerns properly first, splitting everything into routes, controllers, services, repositories and adapters. i spent days on it, making that flow concrete across the whole backend.
the payoff was the entire reason we did it:
good layering is not about tidy folders. it is so that the thing you will swap one day lives in exactly one place. we swapped the adapter, moved to postgres, and shipped the ERP.
then the real shape of the problem showed up. school switching. one school might run several branches, another has none, and lots of different schools were about to share the same portal. on a single-tenant design that is a data nightmare waiting to happen. one wrong query and school A sees school B's students.
this is where Jagan sir and his friend Surendra, a backend dev, sat down and designed our way out. go multi-tenant.
register a new school, a new tenant gets created, and it gets its own world. that one decision turned a scary shared database into every school behaving like it is the only one on the platform. it was a big revamp and it was worth every minute.
then came the part nobody warns you about. keeping every tenant's schema in sync. each new school needs all the tables. every existing school needs every future change. so we split migrations into two:
new school, run the base schema, it is born with everything. existing schools need a new column, write a numbered migration and run it across them.
and here is the rule that bites you the moment you forget it. every schema change has to land in both places. the numbered migration so existing schools get it, and the base schema so the next new school is born with it. update only one and your tenants quietly drift apart, new schools missing a column or old schools missing it.
this is exactly where AI trips. ask claude to add a column to the teachers table and it writes you a clean migration and forgets the base schema entirely. now every newly created school is missing that column, and you do not find out until one of them breaks.
so now, every time claude touches the schema, the question is automatic. did you update both, the versioned migration and the base template. i do not merge until it says yes and i have seen both diffs.
a migration that updates one of two sources of truth is not a migration. it is a future bug with a version number.
firebase to postgres, single-tenant to multi-tenant, one loose migration to a disciplined two-folder system. none of it was the feature the client asked for. all of it is why the feature keeps working as we onboard the next school.
#buildinpublic #softwareengineering #postgres #multitenancy #databasemigrations #maahitatechnologies