Blog
Architecture
6 ottobre 20257 min

3 schemi PostgreSQL per un'app pulita — strutturare Supabase fin dall'inizio

Quando si avvia un progetto, la tentazione è grande di mettere tutto nello schema public di PostgreSQL. Una tabella qui, un'altra lì, e in tre settimane ci si ritrova con 40 tabelle mescolate senza alcuna logica.

Ho deciso molto presto di strutturare TAMSIV con tre schemi separati.

I tre schemi

  • privat — Tutti i dati personali: attività, memo, eventi del calendario, profili utente, allegati
  • collaborative — Tutto ciò che riguarda i gruppi: membri, ruoli, attività condivise, checklist di gruppo
  • gamification — Le statistiche, badge, streak, sfide quotidiane, cronologia dei punti

Perché privat e non private? Perché private è una parola riservata in SQL. L'ho imparato a mie spese dopo una migrazione fallita.

Perché separare?

1. La leggibilità. Quando faccio SELECT * FROM privat.tasks, so immediatamente che si tratta di un dato personale.

2. La sicurezza. Le politiche RLS sono più facili da ragionare quando le tabelle sono raggruppate per dominio. Le regole dello schema privat sono semplici: Lei vede solo i Suoi dati. Quelle dello schema collaborative sono più complesse: Lei vede i dati dei gruppi di cui è membro, in base al Suo ruolo.

3. L'evoluzione. Quando ho aggiunto la gamification tre mesi dopo l'inizio del progetto, ho creato un nuovo schema senza toccare gli altri due. Zero rischio di rompere l'esistente.

L'incubo delle politiche RLS

Supabase utilizza RLS per proteggere l'accesso ai dati. Il principio è semplice: ogni tabella ha delle regole che determinano chi può leggere, scrivere, modificare, eliminare. In pratica, è un labirinto.

In totale, il progetto conta più di 30 politiche RLS. Ognuna testata individualmente.

Le convenzioni di denominazione

Ho adottato convenzioni rigorose: tabelle in snake_case al plurale, colonne in snake_case, parametri RPC con prefisso p_. Questo prefisso p_ evita le collisioni con i nomi delle colonne nelle query. Il giorno in cui il Suo parametro user_id entra in conflitto con la colonna user_id, capirà perché.

Questa struttura mi ha fatto risparmiare un tempo considerevole in seguito. Ogni nuova funzionalità trova il suo posto naturalmente.