Un trigger del database è un hook di codice che si esegue automaticamente sugli eventi dei dati: prima di un save per validare, dopo per reagire. L’idea ha due stirpi che condividono un unico principio: il trigger SQL classico, codice procedurale che vive dentro il motore del database, e l’hook applicativo moderno – beforeSave, afterSave, beforeDelete – JavaScript registrato per classe nel backend. Entrambi codificano la stessa promessa: la regola scatta per ogni scrittura, qualunque client, SDK o script l’abbia eseguita – che è esattamente ciò che la validazione lato client non potrà mai promettere.
Punti chiave
| Domanda | Risposta |
|---|---|
| Il principio | Codice legato agli eventi sui dati – automatico, per tabella/classe, su ogni percorso di scrittura |
| Before = | Validare · normalizzare · impostare valori predefiniti – può ancora modificare o annullare la scrittura |
| After = | Reagire – contare, notificare, sincronizzare; la scrittura è già avvenuta, quindi serve idempotenza |
| Le due forme | Trigger SQL dentro il motore · hook JS nel processo del backend |
| I bug classici | Logica nascosta · cicli infiniti di auto-attivazione · trigger lenti che bloccano ogni save |
Il modello a hook in azione
Una regola di validazione e la sua applicazione, viste da entrambi i lati: il server la definisce una volta sola, e ogni client, in qualunque punto, la eredita:
// JavaScript — Cloud Code (cloud/main.js)
// beforeSave: validate + normalize — can still change or ABORT the write
Parse.Cloud.beforeSave('Review', (req) => {
const stars = req.object.get('stars');
if (stars < 1 || stars > 5) throw 'Stars must be between 1 and 5';
const comment = req.object.get('comment');
if (comment && comment.length > 500) {
req.object.set('comment', comment.slice(0, 497) + '…');
}
});
// afterSave: side effects — the write already happened; be idempotent
Parse.Cloud.afterSave('Review', async (req) => {
if (req.object.existed()) return; // count only NEW reviews, once
await updateAverageStars(req.object.get('movie'));
}); // Flutter / Dart — Back4app Flutter SDK
// The hook fires for EVERY write path — this client included
final review = ParseObject('Review')
..set('movie', 'Arrival')
..set('stars', 9); // invalid — no client-side check needed
final response = await review.save();
print(response.error?.message); // "Stars must be between 1 and 5"
// The beforeSave hook rejected it server-side. A forged REST call,
// another SDK, an admin script — same rule, same rejection. // iOS / Swift — Back4app Swift SDK
// The hook fires for EVERY write path — this client included
var review = Review()
review.movie = "Arrival"
review.stars = 9 // invalid — no client-side check needed
do {
_ = try await review.save()
} catch {
print(error.localizedDescription) // "Stars must be between 1 and 5"
}
// The beforeSave hook rejected it server-side. A forged REST call,
// another SDK, an admin script — same rule, same rejection. // Android / Kotlin — Back4app Android SDK
// The hook fires for EVERY write path — this client included
val review = ParseObject("Review")
review.put("movie", "Arrival")
review.put("stars", 9) // invalid — no client-side check needed
try {
review.save()
} catch (e: ParseException) {
println(e.message) // "Stars must be between 1 and 5"
}
// The beforeSave hook rejected it server-side. A forged REST call,
// another SDK, an admin script — same rule, same rejection. La famiglia completa degli hook estende la stessa forma a tutto il ciclo di vita dei dati: beforeSave/afterSave, beforeDelete (impedire l’eliminazione di un Album che ha ancora delle Photo) e afterDelete (ripulire i suoi figli), più beforeFind/afterFind per riscrivere le query e togliere campi dai risultati – il CRUD, incapsulato.
La forma classica: i trigger SQL
CREATE TRIGGER audit_price_change
AFTER UPDATE ON products
FOR EACH ROW -- livello riga: scatta per ogni riga interessata
WHEN (OLD.price IS DISTINCT FROM NEW.price)
EXECUTE FUNCTION log_price_change(); -- OLD e NEW contengono le due versioni
La tassonomia che ogni database condivide, secondo i riferimenti PostgreSQL e MySQL: il momento – BEFORE (può modificare NEW o annullare), AFTER (reagisce alla riga committata), INSTEAD OF (sostituisce l’operazione, soprattutto per le viste); l’evento – insert, update, delete; la granularità – livello riga contro livello istruzione (un update di 10 righe attiva 10 volte un trigger di riga, una sola volta un trigger di istruzione). Due semantiche da incidere nella pietra: i trigger SQL girano nella stessa transazione della scrittura – un errore del trigger annulla l’intera operazione – e i trigger AFTER scattano solo per le scritture andate realmente a buon fine.
Before vs. after: la scelta si fa per compito
| Before hook | After hook | |
|---|---|---|
| Può modificare i dati | Sì – mutare, impostare un valore predefinito, troncare | No – è già scritto |
| Può annullare la scrittura | Sì – basta un throw e il save fallisce | No |
| Adatto a | Validazione, normalizzazione, campi calcolati | Contatori, notifiche, sincronizzazione, audit |
| Inadatto a | Gli effetti collaterali – se il save fallisce dopo, l’effetto è già avvenuto | Tutto ciò che avrebbe dovuto bloccare la scrittura |
| Comportamento in caso di errore | Rifiuta l’operazione, errore al client | Spesso fire-and-forget – gli errori finiscono nei log |
| Disciplina | Veloce – blocca ogni save | Idempotente – può girare di nuovo |
I corollari che nessuna spiegazione enuncia: un effetto collaterale in un before-hook è un bug per costruzione (l’e-mail parte, poi il save fallisce), e un after-hook non idempotente è un contatore doppio in attesa di un ritentativo. Nei sistemi di hook come quello di Back4app, afterSave si completa dopo che il client ha già ricevuto la risposta: le reazioni sono asincrone per progetto, quindi i loro errori devono essere tollerabili e finire nei log.
Hook vs. trigger SQL
| Hook applicativi (beforeSave/afterSave) | Trigger SQL | |
|---|---|---|
| Linguaggio | JavaScript più l’intero SDK e il suo ecosistema | SQL / dialetti SQL procedurali |
| Chiamate esterne | Sì – API, push, webhook | Praticamente no – e non è il loro ruolo |
| Vive in | Il codebase: versionato, testabile, distribuito | Lo schema: dentro il database |
| Transazione | I before-hook presidiano la scrittura; gli after-hook girano dopo la risposta | Stessa transazione – l’errore annulla tutto |
| Copre | Ogni richiesta che passa dall’API del backend | Ogni scrittura sulla tabella, da qualunque origine |
| Punto cieco | Le scritture dirette sul database lo scavalcano | Logica invisibile ai debugger applicativi |
L’ultima riga è la simmetria onesta: la garanzia di ciascuna forma si limita al proprio livello. Un trigger SQL intercetta anche una sessione psql fuori controllo, ma nasconde la logica agli strumenti applicativi; un hook a livello di API copre ogni percorso client che passa dal backend, ma non l’accesso diretto al database – ed è per questo che le piattaforme che possiedono il gateway delle API (tutto il traffico passa da lì) ottengono in pratica il meglio di entrambi. Qui ci sta anche la nota sugli ORM: gli hook degli ORM scattano solo attraverso l’ORM – le operazioni di massa e l’SQL grezzo gli passano accanto.
Le insidie, onestamente
La logica nascosta è il grande classico: si passano ore a fare il debug del proprio codice mentre un trigger riscrive valori in silenzio – i trigger sono invisibili nel punto di chiamata, quindi conviene documentarli e tenerne pochi. I cicli infiniti: un trigger che scrive sulla propria tabella (o un afterSave che salva il proprio oggetto) si riattiva da solo; serve verificare che cosa è cambiato, e dare alla ricorsione una condizione di arresto prima che se la trovi da sé. Il costo sincrono: i before-hook stanno dentro la latenza di ogni scrittura – un hook da 200 ms rende ogni save 200 ms più lento, e le scritture di massa moltiplicano gli scatti per riga per il numero di righe. Le catene a cascata: trigger che attivano trigger che attivano trigger trasformano un insert in un cantiere archeologico. La regola generale ereditata da decenni di pratica: i trigger applicano regole; nel momento in cui uno comincia a orchestrare un workflow, quel workflow va spostato nelle funzioni o nei job, e al trigger resta solo l’accodamento.
Casi d’uso tipici
- La validazione che ogni client rispetta – la regola delle stelle tra 1 e 5, applicata sia alle richieste falsificate sia ai futuri SDK.
- Le piste di audit – chi ha cambiato che cosa, e quando, scritto dall’evento stesso invece che da client collaborativi.
- Contatori denormalizzati e campi calcolati – medie, conteggi e duplicati utili alla ricerca, tenuti veri al momento della scrittura.
- L’integrità a cascata – rifiutare le eliminazioni finché restano dei figli, oppure ripulire i figli dopo.
- Gli effetti collaterali reattivi – un
afterSaveche fa partire una notifica push o un webhook in uscita: l’evento del database che diventa evento di integrazione.
Quale hook e dove? Matrice decisionale
| Il compito | L’hook |
|---|---|
| Rifiutare dati non validi | beforeSave – con un throw |
| Ripulire, impostare valori predefiniti, canonicalizzare | beforeSave – mutando l’oggetto |
| Aggiornare un contatore o un aggregato | afterSave – in modo idempotente |
| Notificare una persona o un sistema | afterSave → push / webhook / coda |
| Bloccare eliminazioni pericolose | beforeDelete |
| Ripulire dopo un’eliminazione | afterDelete |
| Limitare l’ambito delle query per utente | beforeFind |
| Reazione lunga o lenta | afterSave mette in coda un job – il lavoro non si fa mai in linea |
Limiti e trade-off
- L’invisibilità è il prezzo dell’automatismo. Una logica che nessuno chiama è una logica che nessuno ricorda; denominazione, documentazione e revisione del codice tengono verificabile la magia.
- Gli hook hanno l’ambito del proprio livello. Gli hook di API non vedono le scritture dirette sul database; i trigger SQL non si perdono niente ma si nascondono agli strumenti di sviluppo – va saputo quale punto cieco si è scelto.
- La latenza di scrittura è il budget. Ogni before-hook ne consuma una parte; gli hook si misurano come si misurano le query.
- Gli after-hook sono coerenti solo alla fine. I contatori restano indietro di qualche millisecondo e possono scattare due volte – letture e ritentativi vanno progettati di conseguenza.
- I trigger non sostituiscono i vincoli. Indici univoci, chiavi esterne e ACL si applicano prima e costano meno; i trigger subentrano dove si fermano le regole dichiarative.
I trigger su Back4app
Back4app è una piattaforma open-source di Backend as a Service (BaaS) che combina database gestito, API REST e GraphQL generate automaticamente, autenticazione, archiviazione di file e funzioni serverless con Cloud Code. I trigger qui sono la famiglia “eventi sui dati” di Cloud Code: si registra Parse.Cloud.beforeSave('Review', …) una volta sola – le schede di codice mostrano il pattern completo – e la regola si impone a ogni scrittura che arriva via REST, GraphQL, qualsiasi SDK o dalla dashboard, perché il gateway delle API è l’unico percorso verso i dati. Gli hook ricevono un contesto ricco (request.object, request.original, request.user, stato della master key), quindi la validazione può differire per gli amministratori, beforeFind può restringere le query per utente sopra le ACL, e gli after-hook hanno a disposizione tutto l’ecosistema JavaScript per le reazioni che i trigger SQL non raggiungono: notifiche push, webhook in uscita, accodamento di job. Distribuito insieme al codice, versionato in git, testabile come qualsiasi funzione: la garanzia del trigger, senza la sua archeologia.
Domande frequenti
Che cos'è un trigger del database?
Codice procedurale che si esegue automaticamente quando un evento sui dati – insert, update, delete – avviene su una tabella o una classe precisa. La forma classica vive dentro il database SQL; la forma moderna, applicativa, è una funzione hook come beforeSave o afterSave che il backend esegue intorno a ogni scrittura.
Qual è la differenza tra i trigger BEFORE e AFTER?
La capacità e il momento. BEFORE gira prima della scrittura e può validare, modificare i dati in ingresso o annullare del tutto l'operazione. AFTER gira quando la scrittura è già riuscita: non può più cambiare quello che è accaduto, solo reagire – registrare nei log, contare, notificare. AFTER non scatta mai per una scrittura fallita.
Qual è la differenza tra un trigger e una stored procedure?
L'invocazione. Una stored procedure viene chiamata esplicitamente, riceve parametri e restituisce risultati. Un trigger non viene mai chiamato: scatta automaticamente quando avviene il suo evento, senza parametri, agganciato a una tabella. La stessa meccanica procedurale, con il modello di attivazione opposto.
La logica deve vivere nei trigger o nel codice applicativo?
Il consenso è ibrido: i trigger (o gli hook) per le regole che devono valere su ogni percorso di scrittura – validazione, integrità, audit – e i servizi applicativi per i workflow complessi. Gli hook applicativi sono la via di mezzo moderna: le garanzie del trigger, un vero linguaggio di programmazione, il controllo di versione.
I trigger peggiorano le prestazioni?
Girano in modo sincrono dentro il percorso di scrittura: un trigger lento rallenta ogni save, e i trigger a livello di riga si moltiplicano sulle operazioni di massa – un update di centomila righe produce centomila esecuzioni. Conviene tenere leggeri i before-hook e spostare le reazioni lente negli after-hook o nei job in background.
Un trigger può provocare un ciclo infinito?
È il grande classico: un trigger che scrive sulla propria tabella si riattiva da solo, e un afterSave che salva l'oggetto appena gestito ricorre finché qualcosa non si rompe. La protezione sta nel verificare che cosa è davvero cambiato prima di scrivere, e nel non risalvare mai dal suo stesso after-hook l'oggetto che lo ha attivato, se non con una condizione di arresto.
I trigger possono chiamare servizi esterni?
I trigger SQL praticamente non possono – e non dovrebbero – uscire dal database. È il vantaggio principale degli hook applicativi: un afterSave in JavaScript può inviare una notifica push, chiamare qualsiasi API o far partire un webhook in uscita, perché gira nel processo del backend con tutto l'ecosistema a disposizione.
A che cosa serve beforeSave?
Ai tre compiti che richiedono di girare prima della scrittura: la validazione (basta lanciare un errore e il save fallisce, per ogni client), la normalizzazione (trim, troncamento, canonicalizzazione dei campi) e i valori predefiniti o calcolati. Gli effetti collaterali non ci stanno: se il save fallisce dopo, l'effetto è già avvenuto.