ORM da utilizzare in GO: GORM, sqlc, Ent o Bun?
GORM vs sqlc vs Ent vs Bun ```
L’ecosistema di Go offre una serie di strumenti ORM (Object-Relational Mapping) e librerie per database, ciascuno con la propria filosofia. Ecco un confronto completo di quattro soluzioni principali per l’uso di PostgreSQL in Go: GORM, sqlc, Ent e Bun.
Valutiamo queste soluzioni in base alle prestazioni, all’esperienza dello sviluppatore, alla popolarità e alle funzionalità/estensibilità, con esempi di codice che illustrano le operazioni CRUD di base su un modello User
. Gli sviluppatori intermedi e avanzati di Go otterranno informazioni sui compromessi di ciascun strumento e su quale potrebbe adattarsi meglio alle loro esigenze.
Panoramica degli ORM
-
GORM – Un ORM ricco di funzionalità, nello stile Active Record. GORM ti permette di definire strutture Go come modelli e fornisce un’API estesa per interrogare e manipolare i dati utilizzando la catena di metodi. È in circolazione da anni e uno degli ORM più utilizzati in Go (con quasi 39k stelle su GitHub). L’appeal di GORM è il suo insieme robusto di funzionalità (migrazioni automatiche, gestione delle relazioni, caricamento rapido, transazioni, hook, ecc.), che consente di creare un codice Go pulito con un minimo di SQL grezzo. Tuttavia, introduce i propri modelli e ha un overhead di runtime a causa della riflessione e dell’uso delle interfacce, che può influenzare le prestazioni su operazioni di grandi dimensioni.
-
sqlc – Non è un ORM tradizionale, ma un generatore di codice SQL. Con sqlc, scrivi query SQL semplici (in file
.sql
), e lo strumento genera codice Go tipo-sicuro (funzioni DAO) per eseguire quelle query. Questo significa che interagisci con il database chiamando funzioni Go generate (es.CreateUser
,GetUser
) e ottieni risultati fortemente tipizzati senza dover scansionare manualmente le righe. sqlc ti permette di utilizzare SQL grezzo con la sicurezza in fase di compilazione — non c’è uno strato di costruzione delle query o riflessione in fase di esecuzione. È diventato rapidamente popolare (~16k stelle) per la semplicità e le prestazioni: evita completamente l’overhead in fase di esecuzione sfruttando SQL preparato, rendendolo altrettanto veloce dell’uso diretto didatabase/sql
. Il compromesso è che devi scrivere (e mantenere) le istruzioni SQL per le tue query, e la logica delle query dinamiche può essere meno diretta rispetto agli ORM che costruiscono SQL per te. -
Ent (Entgo) – Un ORM code-first che utilizza la generazione del codice per creare un API tipo-sicura per il tuo modello dati. Definisci il tuo schema in Go (utilizzando il DSL di Ent per i campi, gli edge/relazioni, i vincoli, ecc.), quindi Ent genera i pacchetti Go per i modelli e i metodi di query. Il codice generato utilizza un API fluida per costruire le query, con un controllo completo in fase di compilazione. Ent è relativamente nuovo (sostenuto dalla Linux Foundation e sviluppato da Ariga). Si concentra sull’esperienza dello sviluppatore e sulla correttezza — le query vengono costruite tramite metodi concatenabili invece di stringhe grezze, il che le rende più facili da comporre e meno error-prone. L’approccio di Ent produce query SQL altamente ottimizzate e anche memorizza i risultati delle query per ridurre i viaggi duplicati al database. È stato progettato con la scalabilità in mente, quindi gestisce schemi complessi e relazioni in modo efficiente. Tuttavia, l’uso di Ent aggiunge un passo di costruzione (eseguire codegen) e ti lega all’ecosistema. Al momento supporta PostgreSQL, MySQL, SQLite (e supporto sperimentale per altri database), mentre GORM supporta una gamma più ampia di database (inclusi SQL Server e ClickHouse).
-
Bun – Un ORM più recente con un approccio “SQL-first”. Bun è stato ispirato da Go’s
database/sql
e dalla vecchia libreria go-pg, mirando a essere leggero e veloce con un focus sulle funzionalità di PostgreSQL. Invece del modello Active Record, Bun fornisce un costruttore di query fluido: inizi una query condb.NewSelect()
/NewInsert()
/ ecc., e la costruisci con metodi che assomigliano molto all’SQL (puoi anche inserire frammenti di SQL grezzo). Mappa i risultati delle query ai struct Go utilizzando tag simili a quelli di GORM. Bun favorisce l’esplicitità e ti permette di tornare facilmente all’SQL grezzo quando necessario. Non esegue automaticamente le migrazioni per te (scrivi le migrazioni o utilizza il pacchetto migrate di Bun manualmente), cosa che alcuni considerano un vantaggio per il controllo. Bun è lodato per gestire query complesse in modo elegante e supportare i tipi avanzati di Postgres (array, JSON, ecc.) senza problemi. Nella pratica, Bun impone molto meno overhead in fase di esecuzione rispetto a GORM (nessuna riflessione pesante su ogni query), traducendosi in un miglior throughput in scenari ad alta carica. La sua comunità è più piccola (~4k stelle) e il suo insieme di funzionalità, sebbene solido, non è altrettanto esteso come quello di GORM. Bun potrebbe richiedere un po’ più di conoscenza SQL per essere utilizzato efficacemente, ma lo premia con prestazioni e flessibilità.
Benchmark delle Prestazioni
Quando si parla di prestazioni (latenza delle query e throughput), ci sono differenze significative nel comportamento di questi strumenti, soprattutto con l’aumentare del carico. I benchmark recenti e le esperienze degli utenti evidenziano alcuni punti chiave:
-
GORM: La convenienza di GORM ha un costo in termini di prestazioni. Per operazioni semplici o set di risultati piccoli, GORM può essere molto veloce — in effetti, un benchmark ha mostrato che GORM aveva il tempo di esecuzione più rapido per query molto piccole (es. recuperare 1 o 10 record). Tuttavia, con l’aumentare del numero di record, l’overhead di GORM lo fa rimanere significativamente indietro. In un test che recuperava 15.000 righe, GORM era circa 2 volte più lento rispetto a sqlc o addirittura a
database/sql
grezzo (59,3 ms per GORM vs ~31,7 ms per sqlc e 32,0 ms per database/sql in quel scenario). La progettazione basata sulla riflessione e le astrazioni per la costruzione delle query aggiungono latenza, e GORM potrebbe emettere più query per carichi complessi (es. una query per ogni entità correlata quando si utilizzaPreload
di default), il che può amplificare il ritardo. In sintesi: GORM è generalmente accettabile per carichi di lavoro moderati, ma in scenari ad alta capacità o operazioni in grandi quantità, il suo overhead può diventare un collo di bottiglia. -
sqlc: Poiché le chiamate sqlc sono essenzialmente SQL scritto a mano, le sue prestazioni sono allineate con l’uso diretto della libreria standard. I benchmark posizionano sqlc tra le opzioni più veloci, spesso solo leggermente più lente o addirittura leggermente più veloci rispetto a
database/sql
grezzo per operazioni simili. Ad esempio, a 10k+ record, sqlc è stato osservato per superare leggermentedatabase/sql
in termini di throughput (probabilmente a causa dell’evitare di alcuni boilerplate di scansione e dell’uso di codice generato efficiente). Con sqlc, non c’è nessun costruttore di query in fase di esecuzione — la tua query esegue come un’istruzione preparata con un overhead minimo. La chiave è che hai già fatto il lavoro di scrivere un’ottima query SQL; sqlc ti risparmia solo l’effort di scansionare le righe in tipi Go. Nella pratica, questo significa scalabilità eccellente — sqlc gestirà carichi di dati grandi quanto lo farebbe l’SQL grezzo, rendendolo una scelta top quando la velocità grezza è critica. -
Ent: Le prestazioni di Ent si collocano tra gli approcci SQL grezzo e gli ORM tradizionali. Poiché Ent genera codice tipo-sicuro, evita la riflessione e la maggior parte dei costi di composizione delle query in fase di esecuzione. L’SQL che produce è molto ottimizzato (hai il controllo per scrivere query efficienti tramite l’API di Ent), e Ent include uno strato interno di caching per riutilizzare i piani di esecuzione delle query/results in alcuni casi. Questo può ridurre i colpi ripetuti al database in flussi di lavoro complessi. Sebbene i benchmark specifici di Ent rispetto ad altri variino, molti sviluppatori riferiscono che Ent supera GORM per operazioni equivalenti, soprattutto con l’aumentare della complessità. Una ragione è che GORM potrebbe generare query subottimali (es. N+1 selects se non si utilizzano correttamente i join/preload), mentre Ent incoraggia il caricamento esplicito di relazioni e unirà i dati in meno query. Ent tende anche ad allocare meno memoria per operazione rispetto a GORM, il che può migliorare il throughput riducendo la pressione sul garbage collector di Go. Complessivamente, Ent è costruito per alte prestazioni e schemi grandi — il suo overhead è basso e può gestire query complesse in modo efficiente — ma potrebbe non raggiungere il throughput grezzo di SQL scritto a mano (sqlc) in ogni scenario. È una forte contendente se si desidera entrambe la velocità e la sicurezza di uno strato ORM.
-
Bun: Bun è stato creato con le prestazioni in mente e spesso citato come un’alternativa più veloce a GORM. Utilizza un API fluida per costruire le query SQL, ma questi costruttori sono leggeri. Bun non nasconde l’SQL da te — è più di uno strato sottile su
database/sql
, il che significa che hai un overhead molto minimo oltre a quello che la libreria standard Go fa. Gli utenti hanno notato miglioramenti significativi passando da GORM a Bun in progetti di grandi dimensioni: ad esempio, un rapporto menzionava che GORM era “super lento” per la loro scala, e sostituendolo con Bun (e alcuni SQL grezzi) ha risolto i loro problemi di prestazioni. In benchmark come go-orm-benchmarks, Bun tende ad essere tra i primi in velocità per la maggior parte delle operazioni (spesso entro 1,5× di sqlx/sql grezzo) e ben avanti rispetto a GORM in throughput. Supporta anche inserimenti in batch, il controllo manuale dei join rispetto alle query separate e altre ottimizzazioni che gli sviluppatori possono sfruttare. In sintesi: Bun fornisce prestazioni vicine al metallo nudo, e è una scelta eccellente quando si desidera la convenienza di un ORM senza sacrificare la velocità. La sua natura SQL-first garantisce che puoi sempre ottimizzare le query se quelle generate non sono ottimali.
Riepilogo delle Prestazioni: Se l’obiettivo è la massima prestazione (es. applicazioni dati intensive, microservizi sotto alta carica), sqlc o persino le chiamate dirette a database/sql
scritte a mano sono le vincitrici. Hanno quasi nessun costo di astrazione e si scalano linearmente. Ent e Bun si prestano molto bene e possono gestire query complesse in modo efficiente — trovano un equilibrio tra velocità e astrazione. GORM offre le maggiori funzionalità ma al costo dell’overhead; è perfettamente accettabile per molte applicazioni, ma devi essere consapevole del suo impatto se prevedi di gestire grandi volumi di dati o necessiti di latenza ultra-bassa. (In tali casi, potresti mitigare il costo di GORM utilizzando attentamente Joins
invece di Preload
per ridurre il numero di query, o mescolando SQL grezzo per i percorsi critici, ma ciò aggiunge complessità.)
Esperienza dello Sviluppatore e Facilità d’uso
L’esperienza dello sviluppatore può essere soggettiva, ma include la curva di apprendimento, la chiarezza dell’API e quanto si possa essere produttivi nel coding quotidiano. Ecco come i nostri quattro contendenti si confrontano in termini di facilità d’uso e flusso di lavoro di sviluppo:
-
GORM – Ricco di funzionalità ma con una curva di apprendimento: GORM è spesso il primo ORM che i nuovi sviluppatori Go provano perché promette un’esperienza completamente automatizzata (definisci le strutture e puoi immediatamente Creare/ Trovare senza scrivere SQL). La documentazione è molto completa con molte guide, il che aiuta. Tuttavia, GORM ha il proprio modo idiomatico di fare le cose (“approccio basato sul codice” per le interazioni con il database) e può sembrare imbarazzante all’inizio se sei abituato all’SQL grezzo. Molte operazioni comuni sono semplici (es.
db.Find(&objs)
), ma quando ti avventuri in associazioni, relazioni polimorfe o query avanzate, devi imparare le convenzioni di GORM (tag delle strutture,Preload
vsJoins
, ecc.). Questo è il motivo per cui spesso si parla di una curva di apprendimento iniziale ripida. Una volta superata questa curva, GORM può essere molto produttivo — spendi meno tempo a scrivere SQL ripetitivo e più tempo nel logica Go. In effetti, una volta padroneggiato, molti trovano che possono sviluppare funzionalità più velocemente con GORM rispetto a librerie di livello inferiore. In breve, l’esperienza utente di GORM: alta complessità iniziale ma si smorza con l’esperienza. La grande comunità significa molti esempi e risposte su StackOverflow disponibili, e un ecosistema di plugin ricco può semplificare ulteriormente le attività (es. plugin per cancellazioni soft, auditing, ecc.). L’avvertimento principale è che devi rimanere consapevole di ciò che GORM sta facendo sotto il cofano per evitare trappole (come query N+1 accidentali). Ma per uno sviluppatore che investe tempo in GORM, effettivamente “si sente” come una soluzione potente e integrata che gestisce molto per te. -
Ent – Schema-first e tipo-sicuro: Ent è stato progettato esplicitamente per migliorare l’esperienza dello sviluppatore degli ORM. Descrivi il tuo schema in un unico posto (codice Go) e ottieni un’API fortemente tipizzata per lavorarci — questo significa nessun query stringly-typed, e molti errori vengono catturati in fase di compilazione. Ad esempio, se provi a fare riferimento a un campo o a un edge che non esiste, il tuo codice semplicemente non compilerà. Questa rete di sicurezza è un grande vantaggio per i progetti di grandi dimensioni. L’API di Ent per costruire le query è fluida e intuitiva: cateni metodi come
.Where(user.EmailEQ("alice@example.com"))
e legge quasi come inglese. Gli sviluppatori provenienti da ORM in altri linguaggi spesso trovano l’approccio di Ent naturale (è simile a Entity Framework o Prisma, ma in Go). Imparare Ent richiede di comprendere il suo flusso di lavoro di generazione del codice. Dovrai eseguireentc generate
(o utilizzarego generate
) ogni volta che modifichi lo schema. Questo è un passo aggiuntivo, ma di solito parte del processo di costruzione. La curva di apprendimento per Ent è moderata — se conosci Go e alcuni fondamenti di SQL, i concetti di Ent (Campi, Edge, ecc.) sono semplici. In effetti, molti trovano Ent più facile da comprendere rispetto a GORM, perché non devi chiederti cosa SQL sta producendo; spesso puoi prevederlo dai nomi dei metodi, e puoi outputtare la query per il debug se necessario. La documentazione e gli esempi di Ent sono molto buoni, e il fatto di essere sostenuto da un’azienda garantisce che venga mantenuto attivamente. In sintesi, l’esperienza utente con Ent: molto amichevole per coloro che desiderano chiarezza e sicurezza. Il codice che scrivi è più verboso rispetto all’SQL grezzo, ma è auto-documented. Inoltre, il refactoring è più sicuro (rinominare un campo nello schema e rigenerare aggiorna tutte le uscite nelle query). Il lato negativo potrebbe essere che sei un po’ vincolato da ciò che Ent codegen fornisce — se hai bisogno di una query molto personalizzata, potresti scrivere con l’API di Ent (che gestisce join complessi, ma occasionalmente potresti trovare un caso più facile da scrivere in SQL grezzo). Ent permette frammenti di SQL grezzo quando necessario, ma se ti trovi spesso a farlo, potresti chiederti se un ORM è la scelta giusta. In sintesi, Ent fornisce un’esperienza utente dello sviluppatore liscia per la maggior parte della logica CRUD e delle query con pochi sorprese, purché tu sia d’accordo a eseguire un passo di generazione del codice e ad attenersi ai pattern che Ent impone. -
Bun – Più vicino all’SQL, meno magia: L’uso di Bun si sente diverso rispetto all’uso di GORM o Ent. La filosofia di Bun è di non nascondere l’SQL — se conosci l’SQL, già sai come usare Bun in gran parte. Ad esempio, per interrogare gli utenti potresti scrivere:
db.NewSelect().Model(&users).Where("name = ?", name).Scan(ctx)
. Questo è un API fluida ma molto vicino alla struttura effettiva di un’istruzione SELECT. La curva di apprendimento per Bun è generalmente bassa se sei a tuo agio con l’SQL stesso. C’è meno “magia ORM” da imparare; impari principalmente i nomi dei metodi per costruire le query e le convenzioni dei tag delle strutture. Questo rende Bun molto accessibile per sviluppatori esperti che hanno utilizzatodatabase/sql
o altri costruttori di query comesqlx
. Al contrario, un principiante che non è sicuro di scrivere SQL potrebbe trovare Bun un po’ meno utile rispetto a GORM — Bun non genererà automaticamente query complesse per te tramite relazioni a meno che non le specifichi. Tuttavia, Bun supporta le relazioni e il caricamento rapido (Relation()
metodo per unire le tabelle) — lo fa in modo esplicito, cosa che alcuni sviluppatori preferiscono per la chiarezza. L’esperienza utente con Bun può essere descritta come leggera e prevedibile. Scrivi un po’ più di codice rispetto a GORM per alcune operazioni (poiché Bun spesso ti chiede di specificare esplicitamente le colonne o i join), ma in cambio hai più controllo e visibilità. C’è poco magia interna, quindi il debug è più facile (puoi normalmente registrare la stringa della query che Bun ha costruito). La documentazione di Bun (la guida uptrace.dev) è completa e include pattern per le migrazioni, le transazioni, ecc., anche se la comunità essendo più piccola significa meno tutorial di terze parti. Un altro aspetto dell’esperienza utente è lo strumento disponibile: Bun essendo un’estensione didatabase/sql
significa che qualsiasi strumento che funziona consql.DB
(come proxy di debug, registri di query) funziona facilmente con Bun. In sintesi, Bun offre un’esperienza senza fronzoli: si sente come scrivere SQL strutturato in Go. Questo è fantastico per sviluppatori che valutano controllo e prestazioni, ma potrebbe non aiutare molto sviluppatori meno esperti rispetto a qualcosa come GORM. Il consenso è che Bun rende cose semplici facili e cose complesse possibili (molto come l’SQL grezzo), senza imporre molto framework su di te. -
sqlc – Scrivi SQL, ottieni codice Go: L’approccio di sqlc rovescia il racconto tipico degli ORM. Invece di scrivere codice Go per produrre SQL, scrivi SQL e ottieni codice Go. Per gli sviluppatori che amano l’SQL, questo è fantastico — puoi utilizzare tutta la potenza dell’SQL (join complessi, CTE, funzioni finestra, ecc.) con nessun limite degli ORM. La curva di apprendimento per sqlc stesso è molto piccola. Se sai come scrivere un SELECT/INSERT/UPDATE in SQL, hai già fatto la parte difficile. Devi imparare come annotare le query con commenti
-- name: Name :one/many/exec
e impostare il file di configurazione, ma è triviale. Il codice generato sarà semplici definizioni di funzioni, che chiama come qualsiasi funzione Go. Vantaggi dell’esperienza utente: non c’è un API ORM da imparare, nessuna sorpresa — le query vengono eseguite esattamente come scritte. Eviti un intero insieme di problemi degli ORM (come capire perché un ORM ha generato un certo JOIN o come modificare una query). Inoltre, le tue revisioni del codice possono includere la revisione dell’SQL stesso, che è spesso più chiara per la logica complessa. Un altro grande vantaggio per l’esperienza utente: sicurezza dei tipi e supporto IDE — i metodi e le strutture generati possono essere saltati nell’editor, gli strumenti di refactoring funzionano su di essi, ecc., rispetto alle query di stringa grezze che sono opache per gli IDE. Sul lato negativo, sqlc richiede che tu gestisca gli script SQL. Questo significa che se il tuo schema o i requisiti cambiano, devi manualmente aggiornare o aggiungere le query SQL pertinenti e rilanciare codegen. Non è difficile, ma è un lavoro manuale maggiore rispetto a chiamare semplicemente un metodo ORM. Inoltre, query dinamiche (dove parti dell’SQL sono condizionali) possono essere onerose — devi scrivere molte varianti SQL o utilizzare trucchi sintattici SQL. Alcuni sviluppatori menzionano che è un limite dell’approccio di sqlc. Nella pratica, puoi spesso strutturare l’accesso ai dati in modo che non necessiti di SQL dinamico troppo complesso, o puoi chiamaredatabase/sql
grezzo per quei casi limite. Ma è una considerazione: sqlc è eccezionale per query ben definite, meno adatto per la costruzione di query ad-hoc. In sintesi, per uno sviluppatore che è competente con l’SQL, utilizzare sqlc si sente naturale e altamente efficiente. C’è molto poco da imparare, e rimuove la ripetitiva boilerplate Go. Per uno sviluppatore non così abile con l’SQL, sqlc potrebbe essere inizialmente più lento da utilizzare (rispetto a un ORM che, ad esempio, genera automaticamente le query per il CRUD base). Tuttavia, molti sviluppatori Go considerano sqlc un must-have perché colpisce un punto dolce: controllo manuale con alta sicurezza e nessun costo in fase di esecuzione.
Popolarità e supporto dell’ecosistema
L’adozione e il supporto della comunità possono influenzare la tua scelta: una libreria popolare significa più contributi della comunità, un miglior mantenimento e più risorse da cui imparare.
-
GORM: Essendo la più vecchia e matura tra le quattro, GORM ha di gran lunga la base utenti più grande e l’ecosistema più sviluppato. È attualmente la libreria ORM Go con il maggior numero di stelle su GitHub (oltre 38.000 stelle) e viene utilizzata in innumerevoli progetti produttivi. I mantainer sono attivi e il progetto viene aggiornato regolarmente (GORM v2 è un importante rinnovo che migliora le prestazioni e l’architettura). Un grande vantaggio della popolarità di GORM è la ricchezza di estensioni e integrazioni. Esistono plugin ufficiali e di terze parti per cose come i driver per il database (ad esempio per PostgreSQL, MySQL, SQLite, SQL Server, ClickHouse), disponibili di serie. GORM supporta anche un ampio insieme di casi d’uso: migrazioni, generazione automatica dello schema, cancellazioni soft, campi JSON (con
gorm.io/datatypes
), ricerca full-text, ecc., spesso tramite funzionalità integrate o add-on. La comunità ha prodotto vari strumenti comegormt
(per generare le definizioni struct da un database esistente), e molti tutorial e esempi. Se incontri un problema, una rapida ricerca probabilmente troverà un problema o una domanda su Stack Overflow posta da qualcun altro. Riepilogo dell’ecosistema: GORM è estremamente ben supportato. È la scelta predefinita dell’ORM per molti, il che significa che lo troverai in framework e template. L’aspetto negativo è che la sua dimensione può farlo sembrare pesante, ma per molti è un compromesso accettabile per la profondità della comunità. -
Ent: Nonostante sia più recente (open-sourced intorno al 2019), Ent ha guadagnato una forte comunità. Con ~16.000 stelle e il supporto del Linux Foundation, non è un progetto marginale. Grandi aziende hanno iniziato a utilizzare Ent per i suoi vantaggi orientati allo schema, e i mantainer (Ariga) forniscono supporto enterprise, che è un segno di fiducia per l’uso critico per le aziende. L’ecosistema intorno a Ent sta crescendo: esistono estensioni di Ent (ent/go) per cose come l’integrazione OpenAPI/GraphQL, l’integrazione gRPC e anche un strumento di migrazione SQL che funziona con gli schemi Ent. Poiché Ent genera codice, alcuni pattern dell’ecosistema sono diversi: ad esempio, se vuoi integrarti con GraphQL, potresti utilizzare la generazione del codice di Ent per produrre risolutori GraphQL. Le risorse di apprendimento per Ent sono buone (documentazione ufficiale e un progetto di esempio che copre un’app semplice). I forum della comunità e le discussioni su GitHub sono attivi con domande e suggerimenti sul design dello schema. In termini di supporto della comunità, Ent è certamente passato dalla fase di “early adopter” e viene considerato pronto per la produzione e affidabile. Potrebbe non avere tante risposte su Stack Overflow quanto GORM, ma sta rapidamente recuperando. Una cosa da notare: poiché Ent è un po’ più opinato (ad esempio, vuole gestire lo schema), probabilmente lo userai da solo, non insieme ad un altro ORM. Non ha “plugin” nello stesso modo di GORM, ma puoi scrivere template personalizzati per estendere la generazione del codice o collegarti agli eventi del ciclo di vita (Ent ha supporto per hook/middleware per i client generati). Il supporto da parte di una fondazione indica un supporto a lungo termine, quindi scegliere Ent è una buona opzione se il modello si adatta alle tue esigenze.
-
Bun: Bun (parte della suite open-source Uptrace) sta guadagnando popolarità, specialmente tra coloro che erano fan della libreria
go-pg
ora non più mantenuta. Con ~4.300 stelle, è la comunità più piccola in questo confronto, ma è un progetto molto attivo. Il mantainer è reattivo e sta rapidamente aggiungendo funzionalità. La comunità di Bun è entusiasta delle sue prestazioni. Troverai discussioni sui forum Go e su Reddit da sviluppatori che hanno passato a Bun per la velocità. Tuttavia, a causa della base utenti più piccola, potresti non trovare facilmente risposte a domande specifiche - a volte dovrai leggere le documentazioni o le fonti o chiedere su Bun’s GitHub o Discord (Uptrace fornisce un chat di comunità). L’ecosistema delle estensioni è più limitato: Bun include la sua libreria di migrazione, un caricatore di fixture e si integra con gli strumenti di osservabilità di Uptrace, ma non troverai l’ampia varietà di plugin che ha GORM. Detto questo, Bun è compatibile con l’uso disql.DB
, quindi puoi mescolare e abbinare - ad esempio, utilizzaregithub.com/jackc/pgx
come driver sottostante o integrarti con altri pacchetti che aspettano un*sql.DB
. Bun non ti blocca. In termini di supporto, essendo più recente, la documentazione è aggiornata e gli esempi sono moderni (spesso mostrano l’uso con context e così via). Le documentazioni ufficiali confrontano direttamente Bun con GORM e Ent, il che è utile. Se la dimensione della comunità è una preoccupazione, una strategia potrebbe essere adottare Bun per i suoi vantaggi ma utilizzarlo in modo relativamente superficiale (ad esempio, potresti sostituirlo con un’altra soluzione se necessario, poiché non impone un’astrazione pesante). In ogni caso, la traiettoria di Bun è in crescita, e riempie un nicchia specifica (ORM orientato alle prestazioni) che gli dà potere di permanenza. -
sqlc: sqlc è molto popolare nella comunità Go, come dimostrato da ~15.900 stelle e da molti sostenitori, specialmente in cerchi preoccupati per le prestazioni. Viene spesso raccomandato in discussioni su “evitare gli ORM” perché trova un equilibrio piacevole. Lo strumento è mantenuto da contributori (incluso l’autore originale, che è attivo nel migliorarlo). Essendo più un compilatore che una libreria di runtime, il suo ecosistema ruota intorno alle integrazioni: ad esempio, gli editor/IDE possono avere evidenziazione della sintassi per i file
.sql
e si eseguesqlc generate
come parte del tuo build o pipeline CI. La comunità ha creato template e esempi di repo base su come organizzare il codice con sqlc (spesso abbinandolo a un tool di migrazione come Flyway o Golang-Migrate per la versioning dello schema, poiché sqlc non gestisce lo schema). C’è un canale ufficiale Slack/Discord per sqlc dove puoi porre domande, e i problemi su GitHub tendono a ricevere attenzione. Molti dei pattern comuni (come gestire i valori nulli o i campi JSON) sono documentati nelle guide di sqlc o hanno post di blog della comunità. Una cosa da sottolineare: sqlc non è specifico per Go - può generare codice in altre lingue (come TypeScript, Python). Questo allarga la sua comunità al di là di Go, ma in particolare per Go, è ampiamente rispettato. Se scegli sqlc, sei in buona compagnia: viene utilizzato in produzione da molte startup e grandi aziende (secondo le dimostrazioni della comunità e i sostenitori elencati nel repo). L’considerazione chiave dell’ecosistema è che, poiché sqlc non fornisce funzionalità di runtime come un ORM, potresti dover richiamare altre librerie per cose come le transazioni (sebbene puoi utilizzare facilmentesql.Tx
con sqlc) o forse un wrapper leggero per la DAL. Nella pratica, la maggior parte utilizza sqlc insieme alla libreria standard (per le transazioni, l’annullamento del contesto, ecc., si utilizzano direttamente gli idiomi didatabase/sql
nel tuo codice). Questo significa meno lock-in del fornitore - allontanarsi da sqlc significherebbe semplicemente scrivere il tuo strato dati, che è altrettanto difficile da scrivere quanto l’SQL (che hai già fatto). Complessivamente, la comunità e il supporto per sqlc sono robusti, con molti che lo raccomandano come “must-use” per i progetti Go che interagiscono con database SQL a causa della sua semplicità e affidabilità.
Set di funzionalità e estensibilità
Ogni uno di questi strumenti offre un insieme diverso di funzionalità. Ecco un confronto delle loro capacità e quanto sono estensibili per casi d’uso avanzati:
- Funzionalità di GORM: GORM mira a essere un ORM completo. La sua lista di funzionalità è estesa:
- Database multipli: Supporto di prima classe per PostgreSQL, MySQL, SQLite, SQL Server e altro. Cambiare il DB è spesso così semplice come cambiare il driver di connessione.
- Migrazioni: Puoi migrare automaticamente lo schema dai tuoi modelli (
db.AutoMigrate(&User{})
creerà o altererà la tabellausers
). Sebbene conveniente, fai attenzione nell’utilizzare la migrazione automatica in produzione - molti la usano per lo sviluppo e hanno migrazioni più controllate per la produzione. - Relazioni: Le etichette struct di GORM (
gorm:"foreignKey:...,references:..."
) ti permettono di definire relazioni one-to-many, many-to-many, ecc. Può gestire le tabelle di collegamento per le many-to-many e haPreload
per caricare relazioni in modo rapido. GORM predefinisce il caricamento lazy (ovvero query separate) quando accedi a campi correlati, ma puoi utilizzarePreload
oJoins
per personalizzarlo. Le versioni più recenti hanno anche un API basata su generics per query di associazione più semplici. - Transazioni: GORM ha un wrapper per transazioni facile da usare (
db.Transaction(func(tx *gorm.DB) error { ... })
) e anche supporto per transazioni annidate (savepoints). - Hook e callback: Puoi definire metodi come
BeforeCreate
,AfterUpdate
sui tuoi struct modello, o registrare callback globali che GORM chiamerà in determinati eventi del ciclo di vita. Questo è fantastico per cose come impostare automaticamente i timestamp o il comportamento di cancellazione soft. - Estensibilità: Il sistema di plugin di GORM (
gorm.Plugin
interface) permette di estendere la sua funzionalità. Esempi: il pacchettogorm-gen
genera metodi di query type-safe (se preferisci controlli di query in fase di compilazione), o plugin della comunità per l’audit, la multi-tenancy, ecc. Puoi anche tornare al SQL grezzo in qualsiasi momento tramitedb.Raw("SELECT ...", params).Scan(&result)
. - Altri vantaggi: GORM supporta chiavi primarie composite, embedding di modelli, associazioni polimorfe e anche un risolutore schema per utilizzare più database (per replica di lettura, sharding, ecc.).
Complessivamente, GORM è molto estensibile e ricco di funzionalità. Virtualmente qualsiasi funzionalità ORM che potresti volere ha un meccanismo integrato o un modello documentato in GORM. Il costo di questa ampiezza è la complessità e una certa rigidità (spesso devi conformarti al modo in cui GORM fa le cose). Ma se hai bisogno di una soluzione completa, GORM la fornisce.
- Funzionalità di Ent: La filosofia di Ent è centrata su schema come codice. Le funzionalità principali includono:
- Definizione dello schema: Puoi definire campi con vincoli (unico, valori predefiniti, enum, ecc.), e bordi (relazioni) con cardinalità (one-to-many, ecc.). Ent utilizza questo per generare codice e può anche generare SQL per le migrazioni (c’è un componente
ent/migrate
che può produrre SQL diff tra il tuo schema corrente e lo schema desiderato). - Type safety e validazione: Poiché i campi sono fortemente tipizzati, non puoi, ad esempio, accidentalmente impostare un campo intero a una stringa. Ent consente anche tipi di campo personalizzati (ad esempio, puoi integrarti con
sql.Scanner
/driver.Valuer
per campi JSONB o altri tipi complessi). - Costruttore di query: L’API di query generata da Ent copre la maggior parte delle costruzioni SQL: puoi eseguire selezioni, filtri, ordinamenti, limiti, aggregazioni, join su bordi e anche sottoselezioni. È espressiva - ad esempio, puoi scrivere
client.User.Query().WithOrders(func(q *ent.OrderQuery) { q.Limit(5) }).Where(user.StatusEQ(user.StatusActive)).All(ctx)
per ottenere utenti attivi con i loro primi 5 ordini ciascuno, in un solo passaggio. - Transazioni: Il client di Ent supporta le transazioni esponendo una variante transazionale del client (tramite
tx, err := client.Tx(ctx)
che restituisce unent.Tx
che puoi utilizzare per eseguire più operazioni e quindi commit o rollback). - Hook e middleware: Ent consente di registrare hook su operazioni create/update/delete - sono come intercettori dove puoi, ad esempio, auto-compilare un campo o applicare regole personalizzate. C’è anche middleware per il client per avvolgere le operazioni (utile per il logging, l’implementazione).
- Estensibilità: Sebbene Ent non abbia “plugin” propriamente detti, la sua generazione del codice è basata su template e puoi scrivere template personalizzati se necessario per estendere il codice generato. Molti avanzati funzionalità sono state implementate in questo modo: ad esempio, l’integrazione con OpenTelemetry per il tracciamento delle chiamate al database, o la generazione di risolutori GraphQL dall’ent schema, ecc. Ent consente anche di utilizzare SQL grezzo quando necessario tramite il pacchetto
entsql
o ottenendo il driver sottostante. La capacità di generare codice aggiuntivo significa che i team possono utilizzare Ent come base e aggiungere i propri pattern se necessario. - Integrazione GraphQL/REST: Un grande punto di forza dell’approccio graph-based di Ent è che si adatta bene a GraphQL - il tuo schema ent può essere quasi direttamente mappato a un schema GraphQL. Esistono strumenti per automatizzare molto di questo. Questo può essere un vantaggio di produttività se stai costruendo un server API.
- Tuning delle prestazioni: Ad esempio, Ent può caricare in modo batch le bordi correlate per evitare le query N+1 (ha un’API di caricamento rapido
.With<EdgeName>()
). Utilizzerà JOIN o query aggiuntive in modo ottimizzato. Inoltre, il caching dei risultati delle query (in memoria) può essere abilitato per evitare di colpire il database per query identiche in breve tempo.
In sintesi, l’insieme di funzionalità di Ent è orientato a progetti di grandi dimensioni e mantenibili. Potrebbe mancare alcune delle funzionalità predefinite di GORM (ad esempio, le migrazioni automatiche di GORM rispetto agli script di migrazione generati da Ent - Ent sceglie di separare quel problema), ma compensa con potenti strumenti di sviluppo e type safety. L’estensibilità in Ent è riguardo a generare ciò di cui hai bisogno - è molto flessibile se sei disposto a immergerti in come funziona entc (il generatore di codice).
- Funzionalità di Bun: Bun si concentra su essere uno strumento potente per chi conosce SQL. Alcune funzionalità e punti di estensibilità:
- Costruttore di query: Al centro di Bun c’è un costruttore di query fluido che supporta la maggior parte delle costruzioni SQL (SELECT con join, CTE, sottoselezioni, così come INSERT, UPDATE con supporto bulk). Puoi iniziare una query e personalizzarla in profondità, anche inserendo SQL grezzo (
.Where("some_condition (?)", value)
). - Funzionalità specifiche per PostgreSQL: Bun ha supporto integrato per i tipi di array di Postgres, JSON/JSONB (mappati a
[]<type>
omap[string]interface{}
o tipi personalizzati), e supporta lo scanning in tipi composti. Ad esempio, se hai una colonna JSONB, puoi mapparla a una struct Go con tagjson:
e Bun gestirà il marshaling per te. Questo è un vantaggio rispetto ad alcuni ORM che trattano JSONB come opaco. - Relazioni/Caricamento rapido: Come mostrato prima, Bun ti permette di definire etichette struct per le relazioni e quindi puoi utilizzare
.Relation("FieldName")
nelle query per unire e caricare entità correlate. Di default,.Relation
utilizza LEFT JOIN (puoi simulare inner join o altri tipi aggiungendo condizioni). Questo ti dà un controllo fine su come i dati correlati vengono recuperati (in una query vs multiple). Se preferisci query manuali, puoi sempre scrivere un join direttamente nel costruttore SQL di Bun. A differenza di GORM, Bun non caricherà mai automaticamente le relazioni senza che tu lo richieda, evitando così problemi N+1 involontari. - Migrazioni: Bun fornisce un toolkit separato per le migrazioni (in
github.com/uptrace/bun/migrate
). Le migrazioni sono definite in Go (o in stringhe SQL) e versionate. Non è così automatico come l’auto-migrazione di GORM, ma è robusto e si integra bene con la libreria. Questo è fantastico per mantenere esplicite le modifiche allo schema. - Estensibilità: Bun è costruito su interfacce simili a quelle di database/sql - anzi, avvolge anche un
*sql.DB
. Puoi quindi utilizzare qualsiasi strumento a livello inferiore con esso (ad esempio, puoi eseguire una query*pgx.Conn
se necessario e comunque scansionare in modelli Bun). Bun consente scanner di valori personalizzati, quindi se hai un tipo complesso che vuoi archiviare, implementisql.Scanner
/driver.Valuer
e Bun lo utilizzerà. Per estendere la funzionalità di Bun, poiché è relativamente semplice, molte persone scrivono semplicemente funzioni helper aggiuntive sulla API di Bun nei loro progetti, piuttosto che plugin distinti. La libreria stessa è in evoluzione, quindi nuove funzionalità (come aiuti aggiuntivi per le query o integrazioni) vengono aggiunte dai mantainer. - Altre funzionalità: Bun supporta l’annullamento del contesto (tutte le query accettano
ctx
), ha un pool di connessioni tramitedatabase/sql
sottostante (configurabile lì), e supporta il caching delle istruzioni preparate (tramite il driver se si utilizzapgx
). C’è anche una bella funzionalità in cui Bun può scansionare facilmente in puntatori a struct o slice primitive (ad esempio, selezionando una colonna in una[]string
direttamente). Sono piccole convenienze come questa che rendono Bun piacevole per chi odia il codice di scansione ripetitivo.
In sintesi, l’insieme di funzionalità di Bun copre il 90% delle esigenze ORM ma con un’enfasi su trasparenza e prestazioni. Potrebbe non avere ogni optional (ad esempio, non fa aggiornamenti automatici dello schema o non ha un modello active record), ma fornisce i blocchi per implementare ciò di cui hai bisogno. Poiché è giovane, aspettati che l’insieme di funzionalità continui ad espandersi, guidato da casi d’uso reali (i mantainer spesso aggiungono funzionalità come richiesto dagli utenti).
- Funzionalità di sqlc: Le “funzionalità” di sqlc sono molto diverse poiché è un generatore:
- Supporto completo per SQL: La funzionalità più grande è semplicemente che stai utilizzando le funzionalità native di PostgreSQL. Se Postgres le supporta, puoi usarle in sqlc. Questo include CTE, funzioni finestra, operatori JSON, query spaziali (PostGIS), ecc. Non c’è bisogno che la libreria stessa implementi qualcosa di speciale per usarle.
- Mapping dei tipi: sqlc è intelligente nel mappare i tipi SQL ai tipi Go. Gestisce i tipi standard e ti permette di configurare o estendere i mapping per tipi personalizzati o enum. Ad esempio, un
UUID
di Postgres può mapparsi a un tipogithub.com/google/uuid
se lo desideri, o un tipo di dominio può mapparsi al tipo Go sottostante. - Gestione dei valori nulli: Può generare
sql.NullString
o puntatori per colonne nullable, a seconda delle tue preferenze, quindi non devi lottare con lo scanning dei valori nulli. - Operazioni in batch: Sebbene sqlc non fornisca un’API di alto livello per le operazioni in batch, puoi certamente scrivere un’istruzione SQL di inserimento bulk e generare codice per essa. Oppure chiamare una stored procedure - è un’altra funzionalità: poiché è SQL, puoi sfruttare stored procedure o funzioni del database, e sqlc genera un wrapper Go.
- Query multi-statement: Puoi mettere più istruzioni SQL in una singola query nominata e sqlc le eseguirà in una transazione (se il driver lo supporta), restituendo i risultati dell’ultima query o quelli specificati. Questo è un modo per fare cose come “crea e poi seleziona” in un’unica chiamata.
- Estensibilità: Essendo un compilatore, l’estensibilità arriva sotto forma di plugin per nuove lingue o contributi della comunità per supportare nuove costruzioni SQL. Ad esempio, se esce un nuovo tipo di dati PostgreSQL, sqlc può essere aggiornato per supportare il mapping. Nel tuo applicativo, potresti estendere sqlc scrivendo funzioni wrapper. Poiché il codice generato è sotto il tuo controllo, potresti modificarlo - sebbene normalmente non lo faresti, ma invece aggiusteresti l’SQL e rigenereresti. Se necessario, puoi sempre mescolare chiamate raw
database/sql
con sqlc nel tuo codicebase (non c’è conflitto, poiché sqlc genera semplicemente alcuni codici Go). - Dipendenze runtime minimali: Il codice generato da sqlc dipende tipicamente solo dal
database/sql
standard (e da un driver specifico come pgx). Non c’è una libreria runtime pesante; è solo alcuni tipi helper. Questo significa zero overhead in produzione da parte della libreria - tutto il lavoro è in fase di compilazione. Questo significa anche che non otterrai funzionalità come un cache degli oggetti o una mappa di identità (come alcune librerie ORM), se hai bisogno di un cache, lo implementeresti nel tuo strato di servizio o utilizzando una libreria separata.
In sintesi, la “funzionalità” di sqlc è che riduce la distanza tra il tuo database SQL e il tuo codice Go senza aggiungere niente in mezzo. È uno strumento specializzato - non fa migrazioni, non traccia lo stato degli oggetti, ecc., per design. Questi problemi vengono lasciati ad altri strumenti o allo sviluppatore. Questo è attraente se desideri uno strato dati leggero, ma se stai cercando una soluzione completa che gestisca tutto, da schema a query a relazioni nel codice, sqlc da solo non lo è - lo abbineresti ad altri pattern o strumenti.
Per riepilogare questa sezione, GORM è il motore di funzionalità e una scelta chiara se hai bisogno di un ORM maturo, estensibile che può essere adattato tramite plugin. Ent offre un approccio moderno e type-safe alle funzionalità, privilegiando correttezza e manutenibilità (con cose come codegen, hook e integrazioni negli strati API). Bun fornisce gli elementi essenziali e si basa sulla competenza in SQL, rendendolo facile da estendere scrivendo più SQL o wrapper leggeri. sqlc riduce le funzionalità al metallo nudo - è essenzialmente altrettanto ricco di funzionalità quanto SQL stesso, ma tutto ciò che va oltre (caching, ecc.) è su di te a strato.
Esempio di codice: operazioni CRUD in ciascun ORM
Niente illustra meglio le differenze che vedere come ogni strumento gestisce le operazioni CRUD di base per un modello semplice. Supponiamo di avere un modello User
con i campi ID
, Name
e Email
. Di seguito sono riportati frammenti di codice a confronto per creare, leggere, aggiornare e eliminare un utente in ciascuna libreria, utilizzando PostgreSQL come database.
Nota: In tutti i casi, si assume che esista già una connessione al database (ad esempio, db
o client
), e che la gestione degli errori sia omessa per brevità. Lo scopo è confrontare l’API e la verbosità di ciascun approccio.
GORM (stile Active Record)
// Definizione del modello
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string
}
// Creare un nuovo utente
user := User{Name: "Alice", Email: "alice@example.com"}
db.Create(&user) // INSERT INTO users (name,email) VALUES ('Alice','alice@example.com')
// Leggere (trovare per chiave primaria)
var u User
db.First(&u, user.ID) // SELECT * FROM users WHERE id = X LIMIT 1
// Aggiornare (singolo campo)
db.Model(&u).Update("Email", "alice_new@example.com")
// (genera: UPDATE users SET email='alice_new@example.com' WHERE id = X)
// Eliminare
db.Delete(&User{}, u.ID) // DELETE FROM users WHERE id = X
Ent (Codegen, API fluida)
// (Lo schema Ent è definito altrove e il codice è generato. Si assume che il client sia ent.Client)
// Creare un nuovo utente
u, err := client.User.
Create().
SetName("Alice").
SetEmail("alice@example.com").
Save(ctx)
// SQL: INSERT INTO users (name, email) VALUES ('Alice','alice@example.com') RETURNING id, name, email
// Leggere (per ID)
u2, err := client.User.Get(ctx, u.ID)
// SQL: SELECT * FROM users WHERE id = X
// Aggiornare (singolo campo)
u3, err := client.User.
UpdateOneID(u.ID).
SetEmail("alice_new@example.com").
Save(ctx)
// SQL: UPDATE users SET email='alice_new@example.com' WHERE id = X RETURNING id, name, email
// Eliminare
err = client.User.DeleteOneID(u.ID).Exec(ctx)
// SQL: DELETE FROM users WHERE id = X
Bun
// Definizione del modello
type User struct {
bun.BaseModel `bun:"table:users"`
ID int64 `bun:",pk,autoincrement"`
Name string `bun:",notnull"`
Email string `bun:",unique,notnull"`
}
// Creare un nuovo utente
user := &User{Name: "Alice", Email: "alice@example.com"}
_, err := db.NewInsert().Model(user).Exec(ctx)
// INSERT INTO users (name, email) VALUES ('Alice','alice@example.com')
// Leggere (trovare per ID)
var u User
err = db.NewSelect().Model(&u).
Where("id = ?", user.ID).
Scan(ctx)
// SELECT * FROM users WHERE id = X
// Aggiornare (singolo campo)
_, err = db.NewUpdate().Model(&u).
Set("email = ?", "alice_new@example.com").
Where("id = ?", u.ID).
Exec(ctx)
// UPDATE users SET email='alice_new@example.com' WHERE id = X
// Eliminare
_, err = db.NewDelete().Model((*User)(nil)).
Where("id = ?", u.ID).
Exec(ctx)
// DELETE FROM users WHERE id = X
sqlc
// Si suppone di aver scritto SQL in file e che sqlc abbia generato una struttura Queries con metodi.
queries := New(db) // New prende un *sql.DB (o pgx.Conn) e restituisce le Queries generate
// Creare un nuovo utente (il metodo generato esegue l'INSERT e scansiona il risultato)
newUser, err := queries.CreateUser(ctx, "Alice", "alice@example.com")
// SQL in queries.sql -> INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email
// Leggere (per ID)
user, err := queries.GetUser(ctx, newUser.ID)
// SQL -> SELECT id, name, email FROM users WHERE id = $1
// Aggiornare (email per ID)
updatedUser, err := queries.UpdateUserEmail(ctx, newUser.ID, "alice_new@example.com")
// SQL -> UPDATE users SET email=$2 WHERE id = $1 RETURNING id, name, email
// Eliminare
err = queries.DeleteUser(ctx, newUser.ID)
// SQL -> DELETE FROM users WHERE id = $1
Come mostra il codice sopra, ciascun approccio ha un diverso “feel”:
-
GORM utilizza metodi su struct e catena fluida su
db.Model(&obj)
o direttamente sull’oggettodb
. Popola la struct con i valori restituiti (ad esempio, dopoCreate
,user.ID
è impostato). Nasconde anche i dettagli SQL per design — in genere non vedi la query a meno che non stai debuggando. -
Ent utilizza un’API fluida generata. Nota come i metodi come
Create().SetX().Save(ctx)
oUpdateOneID(id).SetX().Save(ctx)
separano chiaramente la fase di costruzione da quella di esecuzione. Ent restituisce oggetti ent.Type (che corrispondono alle righe) e errori, simili a come un’interrogazione SQL restituirebbe risultati o un errore. -
Bun richiede di specificare esplicitamente (ad esempio, utilizzando
Set("email = ?", ...)
per gli aggiornamenti), che è molto simile a scrivere SQL ma con la sintassi Go. Dopo un’inserimento, la structuser
non è automaticamente riempita con l’ID nuovo a meno che non aggiungi una clausolaRETURNING
(Bun supporta.Returning()
se necessario). L’esempio sopra mantiene le cose semplici. -
sqlc sembra chiamare qualsiasi funzione Go. Chiamiamo
queries.CreateUser
, ecc., e sotto le coperture quelle eseguono istruzioni preparate. L’SQL è scritto in file esterni, quindi non lo vedi nel codice Go, ma hai il pieno controllo su di esso. Gli oggetti restituiti (ad esempio,newUser
) sono struct Go normali generati da sqlc per modellare i dati.
Si può osservare differenze di verbosità (GORM è molto conciso; Bun e sqlc richiedono un po’ più di digitazione nel codice o nell’SQL) e differenze di stile (Ent e GORM offrono astrazioni a livello più alto, mentre Bun e sqlc sono più vicini alle query grezze). A seconda delle tue preferenze, potresti preferire l’esplicitità rispetto alla brevità o viceversa.
TL;DR
Scegliere l’ORM o la libreria del database “giusta” in Go dipende dalle esigenze dell’applicazione e dalle preferenze del team:
-
GORM è una scelta eccellente se si desidera un ORM collaudato che gestisce molte cose per te. Risplende nello sviluppo rapido di applicazioni CRUD dove la convenienza e un insieme ricco di funzionalità sono più importanti che ottenere ogni goccia di prestazioni. Il supporto della comunità e la documentazione sono eccellenti, che possono lisciare i bordi appuntiti della sua curva di apprendimento. Si tenga presente di utilizzare le sue funzionalità in modo appropriato (ad esempio, utilizzare
Preload
oJoins
per evitare problemi di caricamento lazy) e si aspetti un po’ di overhead. In cambio, si ottiene produttività e una soluzione completa per la maggior parte dei problemi. Se si hanno bisogno di cose come il supporto per database multipli o un ecosistema di plugin esteso, GORM vi copre. -
Ent attrae coloro che prioritizzano sicurezza dei tipi, chiarezza e manutenibilità. È adatto per grandi codici base dove i cambiamenti nello schema sono frequenti e si desidera che il compilatore sia dalla propria parte per catturare errori. Ent potrebbe richiedere un po’ più di progettazione iniziale (definire gli schemi, eseguire la generazione), ma si ripaga quando il progetto cresce — il codice rimane robusto e amichevole per il refactoring. Dal punto di vista delle prestazioni, può gestire carichi pesanti e query complesse in modo efficiente, spesso meglio degli ORM a stile active-record, grazie alla generazione ottimizzata di SQL e al caching. Ent è leggermente meno “plug-and-play” per gli script rapidi, ma per i servizi a lungo termine fornisce una base solida e scalabile. Il suo approccio moderno (e lo sviluppo attivo) lo rendono una scelta proiettata al futuro.
-
Bun è ideale per gli sviluppatori che dicono “conosco l’SQL e voglio solo un aiuto leggero intorno ad esso”. Sacrifica alcuni magici per darti controllo e velocità. Se si sta costruendo un servizio sensibile alle prestazioni e non si ha paura di essere espliciti nel codice di accesso ai dati, Bun è un’opzione convincente. È anche un buon compromesso se si sta migrando da
database/sql
+sqlx
e si desidera aggiungere una struttura senza sacrificare molto efficienza. Il compromesso è una comunità più piccola e poche astrazioni a livello alto — si scriverà un po’ più di codice a mano. Ma come dicono i documenti di Bun, non si mette in mezzo. Utilizza Bun quando si desidera un ORM che si sente come utilizzare l’SQL direttamente, specialmente per progetti centrati su PostgreSQL dove si può sfruttare le funzionalità specifiche del database. -
sqlc è in una categoria a sé. È perfetto per il team Go che dice “non vogliamo un ORM, vogliamo garanzie a tempo di compilazione per il nostro SQL”. Se si hanno forti competenze in SQL e si preferisce gestire le query e lo schema in SQL (forse si hanno DBA o si ama creare SQL efficienti), sqlc probabilmente aumenterà la produttività e la fiducia. Le prestazioni sono essenzialmente ottimali, poiché nulla sta tra la tua query e il database. L’unica ragione per non usare sqlc sarebbe se si odia davvero scrivere SQL o se le query sono così dinamiche che scrivere molte varianti diventa oneroso. Anche in quel caso, si potrebbe usare sqlc per la maggior parte delle query statiche e gestire pochi casi dinamici con un altro approccio. Sqlc si integra bene con gli altri — non esclude l’uso di un ORM in parti del progetto (alcuni progetti usano GORM per cose semplici e sqlc per i percorsi critici, ad esempio). In breve, scegli sqlc se si valuta esplicitità, zero overhead e SQL sicuro per i tipi — è uno strumento potente da avere nel proprio arsenale.
Infine, è importante notare che questi strumenti non sono esclusivi nell’ecosistema. Ognuno ha i propri pro e contro, e nello spirito pragmatico di Go, molti team valutano i compromessi caso per caso. Non è raro iniziare con un ORM come GORM o Ent per la velocità di sviluppo, e poi usare sqlc o Bun per specifici percorsi caldi che necessitano di massima prestazione. Tutte e quattro le soluzioni sono attivamente mantenute e ampiamente utilizzate, quindi non c’è una “scelta sbagliata” complessiva — si tratta della scelta giusta per il proprio contesto. Spero che questo confronto ti abbia dato un’immagine più chiara di come GORM, Ent, Bun e sqlc si confrontino, e ti aiuti a prendere una decisione informata per il tuo prossimo progetto in Go.