ORM à utiliser en GO : GORM, sqlc, Ent ou Bun ?
GORM vs sqlc vs Ent vs Bun ```
L’écosystème de Go propose une gamme d’outils ORM (Object-Relational Mapping) et de bibliothèques de base de données, chacun avec sa propre philosophie. Voici une comparaison complète de quatre solutions majeures pour utiliser PostgreSQL en Go : GORM, sqlc, Ent et Bun.
Évaluons-les en termes de performance, d’expérience du développeur, de popularité et de fonctionnalités/extensibilité, avec des exemples de code illustrant les opérations CRUD de base sur un modèle User
. Les développeurs Go intermédiaires et avancés gagneront en insight sur les compromis de chaque outil et sur celui qui pourrait convenir le mieux à leurs besoins.
Aperçu des ORMs
-
GORM – Un ORM riche en fonctionnalités, de style Active Record. GORM vous permet de définir des structs Go comme des modèles et fournit une API étendue pour interroger et manipuler les données à l’aide de chaînes de méthodes. Il existe depuis plusieurs années et est l’un des ORMs Go les plus utilisés (avec près de 39 000 étoiles sur GitHub). L’attractivité de GORM réside dans son ensemble complet de fonctionnalités (migrations automatiques, gestion des relations, chargement anticipé, transactions, hooks, etc.), ce qui permet de créer un code Go propre avec un minimum de SQL brut. Cependant, il introduit ses propres modèles et entraîne un surcoût en temps d’exécution en raison de la réflexion et de l’utilisation d’interfaces, ce qui peut affecter la performance sur de grandes opérations.
-
sqlc – Ce n’est pas un ORM traditionnel mais un générateur de code SQL. Avec sqlc, vous écrivez des requêtes SQL brutes (dans des fichiers
.sql
), et l’outil génère du code Go typé (fonctions DAO) pour exécuter ces requêtes. Cela signifie que vous interagissez avec la base de données en appelant des fonctions Go générées (par exemple,CreateUser
,GetUser
) et obtenez des résultats fortement typés sans avoir à scanner manuellement les lignes. sqlc permet ainsi d’utiliser le SQL brut avec une sécurité à la compilation — il n’y a pas de couche de construction de requêtes ou de réflexion en temps d’exécution. Il est rapidement devenu populaire (~16 000 étoiles) pour sa simplicité et sa performance : il évite entièrement le surcoût en temps d’exécution en exploitant les requêtes préparées, ce qui le rend aussi rapide que l’utilisation directe dedatabase/sql
. Le compromis est que vous devez écrire (et maintenir) des instructions SQL pour vos requêtes, et la logique de requête dynamique peut être moins directe par rapport aux ORMs qui construisent le SQL pour vous. -
Ent (Entgo) – Un ORM code-first qui utilise la génération de code pour créer une API typée pour votre modèle de données. Vous définissez votre schéma en Go (en utilisant le DSL d’Ent pour les champs, les arêtes/relations, les contraintes, etc.), puis Ent génère des packages Go pour les modèles et les méthodes de requête. Le code généré utilise une API fluide pour construire les requêtes, avec une vérification de type complète à la compilation. Ent est relativement récent (soutenu par la Linux Foundation et développé par Ariga). Il se concentre sur l’expérience du développeur et la précision — les requêtes sont construites via des méthodes chaînables au lieu de chaînes brutes, ce qui les rend plus faciles à composer et moins sujettes aux erreurs. L’approche d’Ent produit des requêtes SQL très optimisées et même cache les résultats des requêtes pour réduire les allers-retours multiples vers la base de données. Il a été conçu avec l’évolutivité à l’esprit, donc il gère efficacement les schémas complexes et les relations. Cependant, l’utilisation d’Ent ajoute une étape de build (exécution de codegen) et vous lie à son écosystème. Il prend actuellement en charge PostgreSQL, MySQL, SQLite (et un support expérimental pour d’autres bases de données), alors que GORM prend en charge une gamme plus large de bases de données (y compris SQL Server et ClickHouse).
-
Bun – Un ORM plus récent avec une approche “SQL-first”. Bun a été inspiré par Go’s
database/sql
et par la bibliothèque go-pg plus ancienne, visant à être léger et rapide avec une attention particulière aux fonctionnalités de PostgreSQL. Au lieu du modèle Active Record, Bun propose un constructeur de requêtes fluide : vous commencez une requête avecdb.NewSelect()
/NewInsert()
/ etc., et vous la construisez avec des méthodes qui ressemblent beaucoup au SQL (vous pouvez même insérer des extraits de SQL brut). Il mappe les résultats des requêtes vers des structs Go à l’aide de balises de struct similaires à celles de GORM. Bun favorise l’explicititude et permet de revenir facilement au SQL brut lorsqu’il est nécessaire. Il ne lance pas automatiquement les migrations (vous écrivez les migrations ou utilisez le package migrate de Bun manuellement), ce que certains considèrent comme un avantage pour le contrôle. Bun est réputé pour gérer les requêtes complexes de manière élégante et pour supporter les types avancés de Postgres (tableaux, JSON, etc.) sans effort. En pratique, Bun impose beaucoup moins de surcoût en temps d’exécution que GORM (aucune réflexion lourde à chaque requête), ce qui se traduit par une meilleure capacité de traitement dans les scénarios à forte charge. Sa communauté est plus petite (~4 000 étoiles) et son ensemble de fonctionnalités, bien que solide, n’est pas aussi étendu que celui de GORM. Bun peut nécessiter un peu plus de connaissance en SQL pour être utilisé efficacement, mais il récompense cela par des performances et une flexibilité.
Benchmarks de performance
Lorsqu’il s’agit de performance (latence des requêtes et capacité de traitement), il y a des différences significatives dans le comportement de ces outils, surtout lorsque la charge augmente. Les benchmarks récents et les expériences utilisateur soulignent quelques points clés :
-
GORM : La commodité de GORM vient au prix de certaines performances. Pour les opérations simples ou les ensembles de résultats petits, GORM peut être assez rapide — en fait, un benchmark a montré que GORM avait le temps d’exécution le plus rapide pour des requêtes très petites (par exemple, pour récupérer 1 ou 10 enregistrements). Cependant, à mesure que le nombre d’enregistrements augmente, le surcoût de GORM le fait travailler nettement plus lentement. Dans un test de récupération de 15 000 lignes, GORM était environ 2 fois plus lent que sqlc ou même le
database/sql
brut (59,3 ms pour GORM contre ~31,7 ms pour sqlc et 32,0 ms pour database/sql dans ce scénario). La conception lourde de réflexion et les abstractions de construction de requêtes ajoutent de la latence, et GORM peut émettre plusieurs requêtes pour des charges complexes (par exemple, une requête par entité liée lors de l’utilisation dePreload
par défaut), ce qui peut amplifier le délai. En résumé : GORM est généralement acceptable pour les charges modérées, mais dans les scénarios à forte capacité de traitement ou les opérations en masse, son surcoût peut devenir un goulot d’étranglement. -
sqlc : Les appels sqlc sont essentiellement des SQL écrits à la main sous le capot, donc sa performance est comparable à l’utilisation directe de la bibliothèque standard. Les benchmarks consistent à placer sqlc parmi les options les plus rapides, souvent légèrement plus lentes ou même légèrement plus rapides que le
database/sql
brut pour des opérations comparables. Par exemple, à 10 000+ enregistrements, sqlc a été observé pour légèrement dépasser ledatabase/sql
brut en termes de débit (probablement en évitant certains des boilerplate de scan et en utilisant un codegen efficace). Avec sqlc, il n’y a aucun constructeur de requêtes en temps d’exécution — votre requête s’exécute comme une instruction préparée avec un minimum de surcoût. L’essentiel est que vous avez déjà fait le travail d’écrire une requête SQL optimale ; sqlc vous épargne simplement l’effort de scanner les lignes dans des types Go. En pratique, cela signifie une excellente scalabilité — sqlc gérera les charges de données importantes aussi bien que le SQL brut, ce qui en fait un choix de premier plan lorsque la vitesse brute est critique. -
Ent : La performance d’Ent se situe quelque part entre les approches SQL brutes et les ORMs traditionnels. Comme Ent génère du code typé, il évite la réflexion et la plupart des coûts de composition des requêtes en temps d’exécution. Le SQL qu’il produit est très optimisé (vous avez le contrôle pour écrire des requêtes efficaces via l’API Ent), et Ent inclut une couche de mise en cache interne pour réutiliser les plans d’exécution des requêtes et les résultats dans certains cas. Cela peut réduire les accès répétés à la base de données dans les workflows complexes. Bien que les benchmarks spécifiques d’Ent contre les autres varient, de nombreux développeurs rapportent que Ent dépasse GORM pour des opérations équivalentes, surtout à mesure que la complexité augmente. Une raison est que GORM peut générer des requêtes sous-optimales (par exemple, N+1 selects si les joints/préchargements ne sont pas soigneusement utilisés), alors qu’Ent encourage le chargement anticipé explicite des relations et joindra les données en moins de requêtes. Ent tend également à allouer moins de mémoire par opération que GORM, ce qui peut améliorer le débit en réduisant la pression sur le garbage collector de Go. En général, Ent est conçu pour une haute performance et de grands schémas — son surcoût est faible, et il peut gérer efficacement les requêtes complexes — mais il ne peut pas rivaliser avec la vitesse brute du SQL écrit à la main (sqlc) dans tous les scénarios. C’est un choix solide si vous souhaitez à la fois la vitesse et la sécurité d’une couche ORM.
-
Bun : Bun a été créé avec la performance à l’esprit et est souvent cité comme une alternative plus rapide à GORM. Il utilise une API fluide pour construire des requêtes SQL, mais ces constructeurs sont légers. Bun ne vous cache pas le SQL — c’est plus d’une couche mince sur
database/sql
, ce qui signifie que vous ne subissez qu’un surcoût minimal au-delà de ce que fait la bibliothèque standard Go. Les utilisateurs ont noté des améliorations significatives lors du passage de GORM à Bun dans de grands projets : par exemple, un rapport mentionnait que GORM était “très lent” à leur échelle, et remplacer GORM par Bun (et un peu de SQL brut) a résolu leurs problèmes de performance. Dans des benchmarks comme go-orm-benchmarks, Bun tend à être parmi les plus rapides pour la plupart des opérations (souvent à moins de 1,5 fois de la vitesse de sqlx/sql brut) et bien en avant de GORM en termes de débit. Il prend également en charge les insertions en lots, le contrôle manuel des joints vs des requêtes séparées, et d’autres optimisations que les développeurs peuvent exploiter. En résumé : Bun fournit une performance proche de celle du matériel brut, et c’est un excellent choix lorsque vous souhaitez la commodité d’un ORM sans sacrifier la vitesse. Sa nature SQL-first garantit que vous pouvez toujours optimiser les requêtes si celles générées ne sont pas optimales.
Résumé des performances : Si la performance maximale est l’objectif (par exemple, les applications intensives en données, les microservices sous forte charge), sqlc ou même les appels manuels à database/sql
sont les gagnants.
Ils ont pratiquement aucun coût d’abstraction et s’extendent linéairement. Ent et Bun performent également très bien et peuvent gérer efficacement les requêtes complexes — ils trouvent un équilibre entre vitesse et abstraction.
GORM offre le plus grand nombre de fonctionnalités mais au prix d’un surcoût ; il est parfaitement acceptable pour de nombreuses applications, mais vous devez être conscient de son impact si vous prévoyez de gérer de très grandes quantités de données ou si vous avez besoin d’une latence ultra-basse. (Dans ces cas, vous pourriez atténuer le coût de GORM en utilisant soigneusement Joins
au lieu de Preload
pour réduire le nombre de requêtes, ou en mélangeant du SQL brut pour les chemins critiques, mais cela ajoute de la complexité.)
Expérience du développeur et facilité d’utilisation
L’expérience du développeur peut être subjective, mais elle englobe la courbe d’apprentissage, la clarté de l’API et la productivité au quotidien. Voici comment nos quatre candidats se comparent en termes de facilité d’utilisation et de flux de développement :
-
GORM — Fonctionnel mais une courbe d’apprentissage : GORM est souvent le premier ORM que les nouveaux développeurs Go essaient car il promet une expérience entièrement automatisée (vous définissez des structs et pouvez immédiatement Créer/Rechercher sans écrire de SQL). La documentation est très complète avec de nombreuses guides, ce qui aide. Cependant, GORM a sa propre manière idiomatique de faire les choses (« approche basée sur le code » pour les interactions avec la base de données) et cela peut sembler gênant initialement si vous êtes habitué au SQL brut. Beaucoup d’opérations courantes sont simples (par exemple,
db.Find(&objs)
), mais lorsque vous entrez dans les associations, les relations polymorphes ou les requêtes avancées, vous devez apprendre les conventions de GORM (balises struct,Preload
vsJoins
, etc.). C’est pourquoi il y a souvent mention d’une courbe d’apprentissage initiale raide. Une fois que vous avez gravi cette courbe, GORM peut être très productif — vous passez moins de temps à écrire du SQL répétitif et plus de temps dans la logique Go. En fait, après l’avoir maîtrisé, beaucoup trouvent qu’ils peuvent développer des fonctionnalités plus rapidement avec GORM qu’avec des bibliothè食ques de niveau inférieur. En résumé, l’expérience utilisateur de GORM : élevée en complexité initiale mais s’améliore avec l’expérience. La communauté importante signifie que de nombreux exemples et réponses StackOverflow sont disponibles, et un écosystème de plugins riche peut simplifier davantage les tâches (par exemple, des plugins pour les suppressions douces, l’audit, etc.). La principale précaution est que vous devez rester conscient de ce que GORM fait sous le capot pour éviter les pièges (comme les requêtes N+1 accidentelles). Mais pour un développeur qui investit du temps dans GORM, il « semble » effectivement un solution puissante et intégrée qui gère beaucoup de choses pour vous. -
Ent — Schéma-first et typé : Ent a été explicitement conçu pour améliorer l’expérience du développeur des ORMs. Vous décrivez votre schéma en un seul endroit (code Go) et obtenez une API fortement typée pour travailler — cela signifie aucune requête typée par chaîne, et beaucoup d’erreurs sont détectées à la compilation. Par exemple, si vous essayez de faire référence à un champ ou une arête qui n’existe pas, votre code ne compilera simplement pas. Ce filet de sécurité est un grand gain d’expérience utilisateur pour les grands projets. L’API d’Ent pour construire des requêtes est fluide et intuitive : vous chaînez des méthodes comme
.Where(user.EmailEQ("alice@example.com"))
et cela lit presque comme de l’anglais. Les développeurs venant d’ORMs dans d’autres langages trouvent souvent l’approche d’Ent naturelle (c’est un peu similaire à Entity Framework ou Prisma, mais en Go). Apprendre Ent nécessite de comprendre son workflow de génération de code. Vous devrez exécuterentc generate
(ou utilisergo generate
) chaque fois que vous modifiez votre schéma. C’est une étape supplémentaire, mais généralement partie du processus de build. La courbe d’apprentissage pour Ent est modérée — si vous connaissez Go et quelques fondamentaux SQL, les concepts d’Ent (Champs, Arêtes, etc.) sont simples. En fait, beaucoup trouvent Ent plus facile à comprendre que GORM, car vous n’avez pas à vous demander quel SQL est produit ; vous pouvez souvent le prédire à partir des noms de méthodes, et vous pouvez afficher la requête pour le débogage si nécessaire. La documentation et les exemples d’Ent sont très bons, et le fait d’être soutenu par une entreprise garantit qu’il est activement maintenu. Expérience utilisateur globale avec Ent : très amicale pour ceux qui veulent clarté et sécurité. Le code que vous écrivez est plus verbeux que le SQL brut, mais il est auto-documenté. De plus, le refactoring est plus sûr (renommer un champ dans le schéma et régénérer mettra à jour toutes les utilisations dans les requêtes). L’inconvénient pourrait être que vous êtes quelque peu contraint par ce que fournit la génération de code d’Ent — si vous avez besoin d’une requête très personnalisée, vous devrez soit l’écrire avec l’API Ent (qui peut gérer les joints complexes, mais parfois vous trouverez un cas plus facile à écrire en SQL brut). Ent permet des extraits de SQL brut lorsqu’ils sont nécessaires, mais si vous vous retrouvez souvent à le faire, vous pourriez vous demander si un ORM est le bon choix. En résumé, Ent fournit une expérience utilisateur fluide pour la plupart des logiques CRUD et de requête avec peu de surprises, à condition que vous soyez d’accord pour exécuter une étape de génération de code et respecter les modèles que Ent impose. -
Bun — Plus proche du SQL, moins de magie : L’utilisation de Bun se sent différente de celle de GORM ou d’Ent. La philosophie de Bun est de ne pas cacher le SQL — si vous connaissez le SQL, vous connaissez déjà une grande partie de l’utilisation de Bun. Par exemple, pour interroger les utilisateurs, vous pourriez écrire :
db.NewSelect().Model(&users).Where("name = ?", name).Scan(ctx)
. C’est une API fluide mais très proche de la structure d’une instruction SELECT réelle. La courbe d’apprentissage pour Bun est généralement faible si vous êtes à l’aise avec le SQL lui-même. Il y a moins de « magie ORM » à apprendre ; vous apprenez principalement les noms de méthodes pour construire les requêtes et les conventions des balises de struct. Cela rend Bun très abordable pour les développeurs expérimentés qui ont utilisédatabase/sql
ou d’autres constructeurs de requêtes commesqlx
. À l’inverse, un débutant qui n’est pas confiant dans l’écriture de SQL pourrait trouver Bun un peu moins utile que GORM — Bun ne générera pas automatiquement des requêtes complexes via les relations à moins que vous ne les spécifiiez. Cependant, Bun prend en charge les relations et le chargement anticipé (méthode Relation()
pour joindre les tables) — il le fait simplement de manière explicite, ce que certains développeurs préfèrent pour la clarté. L’expérience utilisateur avec Bun peut être décrite comme légère et prévisible. Vous écrivez un peu plus de code que avec GORM pour certaines opérations (puisque Bun demande souvent de spécifier explicitement les colonnes ou les joints), mais en échange vous avez plus de contrôle et de visibilité. Il y a peu de magie interne, donc le débogage est plus facile (vous pouvez généralement logger la chaîne de requête que Bun a construite). La documentation de Bun (le guide uptrace.dev) est approfondie et inclut des modèles pour les migrations, les transactions, etc., bien que la communauté étant plus petite, il y ait moins de tutoriels tiers. Un autre aspect de l’expérience utilisateur est l’outiling disponible : Bun étant simplement une extension dedatabase/sql
signifie que tout outil qui fonctionne avecsql.DB
(comme des proxys de débogage, des loggers de requêtes) fonctionne facilement avec Bun. En résumé, Bun offre une expérience sans fioritures : il ressemble à écrire du SQL structuré en Go. C’est excellent pour les développeurs qui valorisent le contrôle et la performance, mais il ne tient pas autant la main aux développeurs moins expérimentés que quelque chose comme GORM. La consensus est que Bun rend les choses simples faciles et les choses complexes possibles (comme le SQL brut), sans imposer beaucoup de framework. -
sqlc — Écrivez du SQL, obtenez du code Go : L’approche de sqlc inverse le récit habituel des ORMs. Au lieu d’écrire du code Go pour produire du SQL, vous écrivez du SQL et obtenez du code Go. Pour les développeurs qui aiment le SQL, c’est fantastique — vous pouvez utiliser toute la puissance du SQL (joins complexes, CTE, fonctions de fenêtre, vous nommez-les) avec aucune limitation ORM. La courbe d’apprentissage pour sqlc elle-même est très faible. Si vous savez écrire un SELECT/INSERT/UPDATE en SQL, vous avez déjà fait la partie difficile. Vous devez apprendre à annoter les requêtes avec des commentaires
-- name: Name :one/many/exec
et à configurer le fichier de configuration, mais c’est trivial. Le code généré sera des définitions de fonctions simples, que vous appelez comme n’importe quelle fonction Go. Les avantages de l’expérience utilisateur : il n’y a pas d’API ORM à apprendre, pas de surprises — les requêtes s’exécutent exactement comme écrites. Vous évitez toute une classe de problèmes d’ORM (comme déterminer pourquoi un ORM a généré un certain JOIN ou comment ajuster une requête). De plus, vos revues de code peuvent inclure la revue du SQL lui-même, ce qui est souvent plus clair pour la logique complexe. Un autre grand avantage de l’expérience utilisateur : la sécurité de type et le support IDE — les méthodes et structs générés peuvent être sautés dans votre éditeur, les outils de refactorisation fonctionnent sur eux, etc., contrairement aux requêtes de chaîne brute qui sont opaques pour les IDE. En revanche, sqlc exige que vous gériez les scripts SQL. Cela signifie que si votre schéma ou vos exigences changent, vous devez manuellement mettre à jour ou ajouter les requêtes SQL pertinentes et relancer le codegen. Ce n’est pas difficile, mais c’est un travail plus manuel que simplement appeler une méthode ORM. De plus, les requêtes dynamiques (où des parties du SQL sont conditionnelles) peuvent être fastidieuses — vous écrivez soit plusieurs variantes de SQL soit utilisez des astuces de syntaxe SQL. Certains développeurs mentionnent cela comme une limite de l’approche de sqlc. En pratique, vous pouvez souvent structurer votre accès aux données de telle sorte que vous n’ayez pas besoin de SQL trop dynamique, ou vous pouvez appeler directementdatabase/sql
pour ces cas limites. Mais c’est une considération : sqlc est excellent pour les requêtes bien définies, moins adapté pour la construction de requêtes ad-hoc. En résumé, pour un développeur qui est compétent en SQL, utiliser sqlc semble naturel et extrêmement efficace. Il y a très peu de choses nouvelles à apprendre, et il élimine le code Go répétitif. Pour un développeur moins à l’aise avec le SQL, sqlc peut être plus lent à utiliser initialement (par rapport à un ORM qui, par exemple, génère automatiquement des requêtes pour les CRUD basiques). Cependant, de nombreux développeurs Go considèrent sqlc comme indispensable car il touche un point idéal : le contrôle manuel avec une grande sécurité et aucun coût en temps d’exécution.
Popularité et soutien de l’écosystème
L’adoption et le soutien de la communauté peuvent influencer votre choix — une bibliothèque populaire signifie plus de contributions de la communauté, un entretien plus efficace et plus de ressources pour apprendre.
-
GORM : En tant que la plus ancienne et la plus mûre des quatre, GORM a de loin la plus grande base d’utilisateurs et l’écosystème le plus développé. Il est actuellement la bibliothèque ORM Go la plus étoilée sur GitHub (plus de 38 000 étoiles) et est utilisé dans de nombreux projets en production. Les mainteneurs sont actifs, et le projet est régulièrement mis à jour (la version 2 de GORM a représenté une refonte majeure améliorant les performances et l’architecture). Un grand avantage de la popularité de GORM est la richesse des extensions et des intégrations. Il existe des plugins officiels et tiers pour des choses comme les pilotes de base de données (par exemple, pour PostgreSQL, MySQL, SQLite, SQL Server, ClickHouse), disponibles directement. GORM prend également en charge un large éventail d’utilisations : les migrations, la génération automatique de schémas, les suppressions douces, les champs JSON (avec
gorm.io/datatypes
), la recherche en texte complet, etc., souvent via des fonctionnalités intégrées ou des compléments. La communauté a produit divers outils commegormt
(pour générer des définitions de structures à partir d’une base de données existante), ainsi que de nombreux tutoriels et exemples. Si vous rencontrez un problème, une recherche rapide a de fortes chances de trouver un problème ou une question posée sur Stack Overflow par quelqu’un d’autre. Résumé de l’écosystème : GORM est extrêmement bien soutenu. Il est le choix par défaut de l’ORM pour beaucoup, ce qui signifie que vous le trouverez dans les frameworks et les modèles de départ. L’inconvénient est que sa taille peut le rendre lourd, mais pour beaucoup, c’est un compromis acceptable pour la profondeur de la communauté. -
Ent : Malgré son jeune âge (open-sourcé autour de 2019), Ent a développé une communauté solide. Avec environ 16 000 étoiles et le soutien de la Fondation Linux, ce n’est pas un projet marginal. De grandes entreprises ont commencé à utiliser Ent pour ses avantages centrés sur le schéma, et les mainteneurs (à Ariga) offrent un support d’entreprise, ce qui est un signe de confiance pour les usages critiques. L’écosystème autour d’Ent grandit : il existe des extensions Ent (ent/go) pour des choses comme l’intégration OpenAPI/GraphQL, l’intégration gRPC, et même un outil de migration SQL qui fonctionne avec les schémas Ent. Comme Ent génère du code, certains modèles d’écosystème diffèrent — par exemple, si vous souhaitez intégrer GraphQL, vous pourriez utiliser la génération de code d’Ent pour produire des résolveurs GraphQL. Les ressources d’apprentissage pour Ent sont bonnes (documentation officielle et un projet d’exemple couvrant une application simple). Les forums de la communauté et les discussions GitHub sont actifs avec des questions et des conseils sur la conception de schémas. En termes de soutien de la communauté, Ent est certainement passé de la phase d’« adopteur précoce » et est considéré comme prêt pour la production et fiable. Il n’a peut-être pas autant de réponses Stack Overflow que GORM, mais il rattrape rapidement. Une chose à noter : puisque Ent est un peu plus opiniâtre (par exemple, il souhaite gérer le schéma), vous l’utiliserez probablement seul plutôt qu’avec un autre ORM. Il n’a pas de « plugins » dans le même sens que GORM, mais vous pouvez écrire des modèles personnalisés pour étendre la génération de code ou vous connecter aux événements de cycle de vie (Ent a un support de hooks/middleware pour les clients générés). Le soutien de la fondation indique un soutien à long terme, donc choisir Ent est une bonne option si son modèle correspond à vos besoins.
-
Bun : Bun (partie de la suite open-source Uptrace) gagne en popularité, notamment parmi ceux qui étaient fans de la bibliothèque
go-pg
maintenant non entretenue. Avec environ 4 300 étoiles, c’est la communauté la plus petite dans cette comparaison, mais c’est un projet très actif. Le mainteneur est réactif et ajoute rapidement des fonctionnalités. La communauté de Bun est enthousiaste à son sujet pour ses performances. Vous trouverez des discussions sur les forums Go et Reddit de développeurs qui ont changé vers Bun pour la vitesse. Cependant, en raison de la base d’utilisateurs plus petite, vous ne trouverez peut-être pas toujours des réponses aux questions spécifiques facilement — parfois, vous devrez lire les documents/source ou poser la question sur le GitHub ou le Discord de Bun (Uptrace fournit un chat communautaire). L’écosystème des extensions est plus limité : Bun vient avec sa propre bibliothèque de migration, un chargeur de fixtures et des outils d’observabilité de Uptrace, mais vous ne trouverez pas la panoplie de plugins que GORM a. Cela dit, Bun est compatible avec l’utilisation desql.DB
, donc vous pouvez mélanger et associer — par exemple, utilisergithub.com/jackc/pgx
comme pilote sous-jacent ou intégrer d’autres packages qui s’attendent à un*sql.DB
. Bun ne vous enferme pas. En termes de soutien, étant plus récent, la documentation est à jour et les exemples sont modernes (souvent montrant l’utilisation avec contexte, etc.). Les documents officiels comparent directement Bun avec GORM et Ent, ce qui est utile. Si la taille de la communauté est une préoccupation, une stratégie pourrait être d’adopter Bun pour ses avantages mais d’utiliser celui-ci de manière relativement peu profonde (par exemple, vous pourriez le remplacer par une autre solution si nécessaire, car il ne impose pas une abstraction lourde). En tout cas, la trajectoire de Bun est ascendante, et il remplit un niche spécifique (ORM orienté performance) qui lui donne de la pérennité. -
sqlc : sqlc est très populaire dans la communauté Go, comme le montrent environ 15 900 étoiles et de nombreux défenseurs, notamment dans les cercles préoccupés par les performances. Il est souvent recommandé dans les discussions sur « l’évitement des ORMs » car il trouve un bon équilibre. L’outil est maintenu par des contributeurs (y compris l’auteur original, qui est actif pour l’améliorer). Étant plus un compilateur qu’une bibliothèque de runtime, son écosystème tourne autour des intégrations : par exemple, les éditeurs/IDE peuvent avoir une coloration syntaxique pour les fichiers
.sql
et vous exécutezsqlc generate
comme partie de votre build ou de votre pipeline CI. La communauté a créé des modèles et des exemples de répertoires de base sur la façon d’organiser le code avec sqlc (souvent en le combinant avec un outil de migration comme Flyway ou Golang-Migrate pour la version du schéma, car sqlc lui-même ne gère pas le schéma). Il y a un officiel Slack/Discord pour sqlc où vous pouvez poser des questions, et les problèmes sur GitHub tendent à attirer l’attention. Beaucoup des modèles courants (comme la gestion des valeurs nulles ou des champs JSON) sont documentés dans les docs de sqlc ou ont des articles de blog de la communauté. Une chose à souligner : sqlc n’est pas spécifique à Go — il peut générer du code dans d’autres langages (comme TypeScript, Python). Cela élargit sa communauté au-delà de Go, mais spécifiquement en Go, il est largement respecté. Si vous choisissez sqlc, vous êtes en bonne compagnie : il est utilisé en production par de nombreuses startups et grandes entreprises (selon les démonstrations de la communauté et les sponsors listés sur le dépôt). La considération clé de l’écosystème est que, puisque sqlc ne fournit pas de fonctionnalités de runtime comme un ORM, vous pourriez avoir besoin d’autres bibliothèques pour des choses comme les transactions (bien que vous puissiez utiliser facilementsql.Tx
avec sqlc) ou peut-être un wrapper DAL léger. En pratique, la plupart utilisent sqlc avec la bibliothèque standard (pour les transactions, l’annulation du contexte, etc., vous utilisez directement les idiomes dedatabase/sql
dans votre code). Cela signifie moins de verrouillage de fournisseur — passer à sqlc ne signifie que d’écrire votre propre couche de données, ce qui est aussi difficile que d’écrire le SQL (que vous avez déjà fait). En général, la communauté et le soutien pour sqlc sont solides, avec beaucoup qui le recommandent comme un « must-use » pour les projets Go qui interagissent avec des bases de données SQL en raison de sa simplicité et de sa fiabilité.
Ensemble de fonctionnalités et extensibilité
Chacun de ces outils propose un ensemble différent de fonctionnalités. Ici, nous comparons leurs capacités et leur extensibilité pour les cas d’utilisation avancés :
- Fonctionnalités de GORM : GORM vise à être un ORM complet. Sa liste de fonctionnalités est extensive :
- Plusieurs bases de données : Un support de premier plan pour PostgreSQL, MySQL, SQLite, SQL Server et plus. Changer de base de données est généralement aussi simple que de changer le pilote de connexion.
- Migrations : Vous pouvez migrer automatiquement votre schéma à partir de vos modèles (
db.AutoMigrate(&User{})
créera ou modifiera la tableusers
). Bien que pratique, faites attention à l’utilisation de la migration automatique en production — beaucoup l’utilisent pour le développement et ont des migrations plus contrôlées pour la production. - Relations : Les balises struct de GORM (
gorm:"foreignKey:...,references:..."
) vous permettent de définir des relations un-à-plusieurs, plusieurs-à-plusieurs, etc. Il peut gérer les tables de liaison pour les relations plusieurs-à-plusieurs et aPreload
pour charger les relations en avance. GORM utilise par défaut le chargement paresseux (c’est-à-dire des requêtes séparées) lors de l’accès aux champs liés, mais vous pouvez utiliserPreload
ouJoins
pour personnaliser cela. Les versions plus récentes ont également une API basée sur les générics pour des requêtes d’association plus faciles. - Transactions : GORM a un wrapper de transaction facile à utiliser (
db.Transaction(func(tx *gorm.DB) error { ... })
) et même un support pour les transactions imbriquées (points de sauvegarde). - Hooks et Callbacks : Vous pouvez définir des méthodes comme
BeforeCreate
,AfterUpdate
sur vos structs de modèles, ou enregistrer des callbacks globaux que GORM appellera à certains événements de cycle de vie. Cela est idéal pour des choses comme le réglage automatique des horodatages ou le comportement de suppression douce. - Extensibilité : Le système de plugin de GORM (
gorm.Plugin
interface) permet d’étendre ses fonctionnalités. Exemples : le packagegorm-gen
génère des méthodes de requête type-safe (si vous préférez des vérifications de requête à la compilation), ou des plugins communautaires pour l’audit, la multi-tenance, etc. Vous pouvez également revenir à SQL brut à tout moment viadb.Raw("SELECT ...", params).Scan(&result)
. - Autres commodités : GORM prend en charge les clés primaires composites, l’incrustation de modèles, les associations polymorphes et même un résolveur de schéma pour utiliser plusieurs bases de données (pour les réplicas de lecture, le sharding, etc.).
En résumé, GORM est très extensible et riche en fonctionnalités. Presque toute fonctionnalité ORM que vous pourriez vouloir a soit un mécanisme intégré, soit un modèle documenté dans GORM. Le coût de cette largeur est la complexité et certaine rigidité (vous avez souvent besoin de vous conformer à la manière dont GORM fait les choses). Mais si vous avez besoin d’une solution tout-en-un, GORM livre.
- Fonctionnalités d’Ent : La philosophie d’Ent est centrée sur le schéma comme code. Les fonctionnalités clés incluent :
- Définition de schéma : Vous pouvez définir des champs avec des contraintes (uniques, valeurs par défaut, énumérations, etc.), et des arêtes (relations) avec une cardinalité (un-à-plusieurs, etc.). Ent utilise cela pour générer du code et peut également générer du SQL de migration pour vous (il existe un composant
ent/migrate
qui peut produire du SQL de différence entre votre schéma actuel et le schéma souhaité). - Sécurité de type et validation : Puisque les champs sont fortement typés, vous ne pouvez pas, par exemple, accidentellement définir un champ entier sur une chaîne. Ent permet également des types de champ personnalisés (par exemple, vous pouvez intégrer avec
sql.Scanner
/driver.Valuer
pour les champs JSONB ou d’autres types complexes). - Constructeur de requête : L’API de requête générée par Ent couvre la plupart des constructions SQL : vous pouvez faire des sélections, des filtres, des ordonnements, des limites, des agrégats, des joints sur les arêtes, et même des sous-requêtes. C’est expressif — par exemple, vous pouvez écrire
client.User.Query().WithOrders(func(q *ent.OrderQuery) { q.Limit(5) }).Where(user.StatusEQ(user.StatusActive)).All(ctx)
pour obtenir des utilisateurs actifs avec leurs cinq premières commandes chacun, d’un seul coup. - Transactions : Le client d’Ent prend en charge les transactions en exposant une variante transactionnelle du client (via
tx, err := client.Tx(ctx)
qui produit unent.Tx
que vous pouvez utiliser pour effectuer plusieurs opérations et ensuite valider ou annuler). - Hooks et middleware : Ent permet d’enregistrer des hooks sur les opérations de création/mise à jour/suppression — ce sont comme des intercepteurs où vous pouvez, par exemple, remplir automatiquement un champ ou imposer des règles personnalisées. Il existe également un middleware pour le client pour envelopper les opérations (utile pour le journalisation, l’instrumentation).
- Extensibilité : Bien qu’Ent n’ait pas de « plugins » au sens propre, sa génération de code est modulée et vous pouvez écrire des modèles personnalisés si vous avez besoin d’étendre le code généré. Beaucoup de fonctionnalités avancées ont été implémentées ainsi : par exemple, l’intégration avec OpenTelemetry pour le traçage des appels de base de données, ou la génération de résolveurs GraphQL à partir du schéma Ent, etc. Ent permet également d’utiliser du SQL brut lorsqu’il est nécessaire via le package
entsql
ou en obtenant le pilote sous-jacent. La capacité à générer du code supplémentaire signifie que les équipes peuvent utiliser Ent comme base et ajouter leurs propres modèles si nécessaire. - Intégration GraphQL/REST : Un grand point de vente de l’approche basée sur le graphe d’Ent est qu’elle s’adapte bien à GraphQL — votre schéma Ent peut être presque directement cartographié à un schéma GraphQL. Des outils existent pour automatiser beaucoup de cela. Cela peut être un gain de productivité si vous construisez un serveur API.
- Ajustements de performance : Par exemple, Ent peut charger en bloc les arêtes liées pour éviter les requêtes N+1 (il a une API de chargement en avance
.With<EdgeName>()
). Il utilisera des JOINs ou des requêtes supplémentaires sous le capot de manière optimisée. De plus, le cache des résultats de requête (en mémoire) peut être activé pour éviter d’interroger la base de données pour des requêtes identiques dans un court laps de temps.
En résumé, l’ensemble des fonctionnalités d’Ent est axé sur les projets à grande échelle et maintenables. Il peut manquer certains des bonbons prêts à l’emploi de GORM (par exemple, les migrations de schéma automatiques de GORM vs les scripts de migration générés par Ent — Ent choisit de séparer cette préoccupation), mais il le compense avec des outils de développement puissants et la sécurité de type. L’extensibilité d’Ent est sur la génération de ce dont vous avez besoin — elle est très flexible si vous êtes prêt à plonger dans la manière dont entc (le générateur de code) fonctionne.
- Fonctionnalités de Bun : Bun se concentre sur être un outil de puissance pour ceux qui connaissent SQL. Certaines fonctionnalités et points d’extensibilité :
- Constructeur de requête : Au cœur de Bun se trouve le constructeur de requête fluide qui prend en charge la plupart des constructions SQL (SELECT avec des joints, CTEs, sous-requêtes, ainsi que INSERT, UPDATE avec un support en masse). Vous pouvez commencer une requête et la personnaliser profondément, même en injectant du SQL brut (
.Where("some_condition (?)", value)
). - Fonctionnalités spécifiques à PostgreSQL : Bun a un support intégré pour les types d’array Postgres, JSON/JSONB (mappés à
[]<type>
oumap[string]interface{}
ou des types personnalisés), et prend en charge le balayage vers des types composites. Par exemple, si vous avez une colonne JSONB, vous pouvez la mapper à une structure Go avec des balisesjson:
et Bun gérera le marshaling pour vous. C’est un point fort par rapport à certains ORMs qui traitent JSONB comme opaque. - Relations/chargement en avance : Comme montré précédemment, Bun vous permet de définir des balises struct pour les relations et puis d’utiliser
.Relation("FieldName")
dans les requêtes pour joindre et charger les entités liées. Par défaut,.Relation
utilise LEFT JOIN (vous pouvez simuler des joints internes ou d’autres types en ajoutant des conditions). Cela vous donne un contrôle fin sur la manière dont les données liées sont récupérées (dans une seule requête vs plusieurs). Si vous préférez des requêtes manuelles, vous pouvez toujours écrire un joint directement dans le constructeur SQL de Bun. Contrairement à GORM, Bun ne chargera jamais automatiquement les relations sans que vous ne le demandiez, évitant ainsi les problèmes N+1 involontaires. - Migrations : Bun fournit un outil de migration séparé (dans
github.com/uptrace/bun/migrate
). Les migrations sont définies en Go (ou en chaînes SQL) et versionnées. Ce n’est pas aussi automagique que l’auto-migration de GORM, mais c’est solide et s’intègre bien à la bibliothè队. C’est excellent pour garder les changements de schéma explicites. - Extensibilité : Bun est construit sur des interfaces similaires à celles de
database/sql
— il enveloppe même un*sql.DB
. Vous pouvez donc utiliser tout outil de niveau inférieur avec lui (par exemple, exécuter une requête*pgx.Conn
si nécessaire et toujours balayer vers les modèles Bun). Bun permet des scanneurs de valeur personnalisés, donc si vous avez un type complexe que vous souhaitez stocker, vous implémentezsql.Scanner
/driver.Valuer
et Bun l’utilisera. Pour étendre les fonctionnalités de Bun, puisque c’est relativement simple, beaucoup de personnes écrivent simplement des fonctions d’aide supplémentaires sur l’API de Bun dans leurs projets plutôt que des plugins distincts. La bibliothèque elle-même évolue, donc de nouvelles fonctionnalités (comme des helpers supplémentaires de requête ou des intégrations) sont ajoutées par les mainteneurs. - Autres fonctionnalités : Bun prend en charge l’annulation du contexte (toutes les requêtes acceptent
ctx
), il a un pool de connexions viadatabase/sql
(configurable là-bas), et prend en charge le cache des requêtes préparées (via le pilote si vous utilisezpgx
). Il y a également une fonctionnalité sympa où Bun peut balayer vers des pointeurs de structure ou des tranches primitives facilement (par exemple, sélectionner une colonne dans une[]string
directement). Ce sont ces petites commodités qui rendent Bun agréable pour ceux qui détestent le code de balayage répétitif.
En résumé, l’ensemble des fonctionnalités de Bun couvre 90 % des besoins ORM mais avec un accent sur la transparence et les performances. Il n’a peut-être pas toutes les fonctionnalités (par exemple, il ne fait pas de mises à jour de schéma automagiques ou n’a pas de modèle d’objet actif), mais il fournit les blocs de construction pour implémenter ce dont vous avez besoin. Comme il est jeune, prévoyez que son ensemble de fonctionnalités continuera à s’élargir, guidé par des cas d’utilisation réels (les mainteneurs ajoutent souvent des fonctionnalités selon les demandes des utilisateurs).
- Fonctionnalités de sqlc : Les « fonctionnalités » de sqlc sont très différentes car c’est un générateur :
- Support complet SQL : La plus grande fonctionnalité est simplement que vous utilisez directement les fonctionnalités de PostgreSQL. Si Postgres le prend en charge, vous pouvez l’utiliser dans sqlc. Cela inclut les CTEs, les fonctions fenêtres, les opérateurs JSON, les requêtes spatiales (PostGIS), etc. Il n’est pas nécessaire que la bibliothèque elle-même implémente quelque chose de spécial pour utiliser ces fonctionnalités.
- Cartographie des types : sqlc est intelligent dans la cartographie des types SQL vers les types Go. Il gère les types standard et vous permet de configurer ou d’étendre les cartographies pour les types personnalisés ou les énumérations. Par exemple, un
UUID
Postgres peut mapper à un typegithub.com/google/uuid
si vous le souhaitez, ou un type de domaine peut mapper au type Go sous-jacent. - Gestion des valeurs nulles : Il peut générer
sql.NullString
ou des pointeurs pour les colonnes nulles, selon vos préférences, donc vous n’avez pas à lutter avec le balayage des valeurs nulles. - Opérations en lots : Bien que sqlc lui-même ne fournisse pas d’API de haut niveau pour les opérations en lots, vous pouvez certainement écrire une insertion en masse SQL et générer du code pour cela. Ou appeler une procédure stockée — c’est une autre fonctionnalité : puisque c’est SQL, vous pouvez exploiter les procédures stockées ou les fonctions de base de données, et avoir sqlc générer un wrapper Go.
- Requêtes multi-énoncés : Vous pouvez mettre plusieurs énoncés SQL dans une seule requête nommée et sqlc les exécutera dans une transaction (si le pilote le prend en charge), renvoyant les résultats de la dernière requête ou ce que vous spécifiez. C’est une façon de faire quelque chose comme un « créer puis sélectionner » en une seule appel.
- Extensibilité : Étant un compilateur, l’extensibilité vient sous la forme de plugins pour de nouveaux langages ou de contributions communautaires pour supporter de nouvelles constructions SQL. Par exemple, si un nouveau type de données PostgreSQL sort, sqlc peut être mis à jour pour supporter la cartographie. Dans votre application, vous pouvez étendre sqlc en écrivant des fonctions d’assistance. Puisque le code généré est sous votre contrôle, vous pourriez le modifier — bien que normalement vous ne le fassiez pas, vous ajusteriez le SQL et régénéreriez. Si nécessaire, vous pouvez toujours mélanger des appels
database/sql
bruts avec sqlc dans votre codebase (il n’y a pas de conflit, car sqlc ne produit que du code Go). - Dépendances de runtime minimales : Le code généré par sqlc dépend généralement uniquement de la bibliothèque standard
database/sql
(et d’un pilote spécifique comme pgx). Il n’y a pas de bibliothèque de runtime lourde ; il s’agit simplement de quelques types d’aide. Cela signifie zéro surcoût en production du côté de la bibliothèque — tout le travail est à la phase de compilation. Cela signifie également que vous ne bénéficierez pas de fonctionnalités comme un cache d’objets ou une carte d’identité (comme certaines ORMs) — si vous avez besoin de cache, vous l’implémenteriez dans votre couche de service ou utiliseriez une bibliothèque séparée.
En résumé, la « fonctionnalité » de sqlc est qu’il réduit l’écart entre votre base de données SQL et votre code Go sans ajouter de choses supplémentaires entre les deux. C’est un outil spécialisé — il ne fait pas de migrations, il ne suit pas l’état des objets, etc., par conception. Ces préoccupations sont laissées à d’autres outils ou au développeur. Cela est attrayant si vous souhaitez une couche de données légère, mais si vous cherchez une solution tout-en-un qui gère tout, de la schéma aux requêtes aux relations en code, sqlc seul n’est pas — vous le pairez avec d’autres modèles ou outils.
Pour résumer cette section, GORM est le choix de puissance en termes de fonctionnalités et une option claire si vous avez besoin d’un ORM mûr, extensible qui peut être adapté via des plugins. Ent propose une approche moderne, sécurisée par le type des fonctionnalités, priorisant la précision et la maintenabilité (avec des choses comme la génération de code, les hooks et les intégrations dans les couches API). Bun fournit les essentiels et mise sur la maîtrise de SQL, ce qui le rend facile à étendre en écrivant plus de SQL ou des wrappers légers. sqlc réduit les fonctionnalités au métal nu — il est essentiellement aussi riche en fonctionnalités que SQL lui-même, mais tout au-delà (le cache, etc.) est à vous de le mettre en place.
Exemple de code : opérations CRUD dans chaque ORM
Rien ne montre mieux les différences que de voir comment chaque outil gère les opérations CRUD de base pour un modèle simple. Supposons que nous ayons un modèle User
avec les champs ID
, Name
et Email
. Ci-dessous, des extraits de code côte à côte pour créer, lire, mettre à jour et supprimer un utilisateur dans chaque bibliothèque, en utilisant PostgreSQL comme base de données.
Note : Dans tous les cas, supposons qu’une connexion à la base de données (par exemple, db
ou client
) soit déjà établie, et que la gestion des erreurs soit omise pour la brièveté. L’objectif est de comparer l’API et la verbosité de chaque approche.
GORM (style Active Record)
// Définition du modèle
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string
}
// Créer un nouvel utilisateur
user := User{Name: "Alice", Email: "alice@example.com"}
db.Create(&user) // INSERT INTO users (name,email) VALUES ('Alice','alice@example.com')
// Lire (recherche par clé primaire)
var u User
db.First(&u, user.ID) // SELECT * FROM users WHERE id = X LIMIT 1
// Mettre à jour (un seul champ)
db.Model(&u).Update("Email", "alice_new@example.com")
// (génère : UPDATE users SET email='alice_new@example.com' WHERE id = X)
// Supprimer
db.Delete(&User{}, u.ID) // DELETE FROM users WHERE id = X
Ent (Codegen, API fluide)
// (Le schéma Ent est défini ailleurs et le code est généré. Supposons que le client soit ent.Client)
// Créer un nouvel utilisateur
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
// Lire (par ID)
u2, err := client.User.Get(ctx, u.ID)
// SQL : SELECT * FROM users WHERE id = X
// Mettre à jour (un seul champ)
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
// Supprimer
err = client.User.DeleteOneID(u.ID).Exec(ctx)
// SQL : DELETE FROM users WHERE id = X
Bun
// Définition du modèle
type User struct {
bun.BaseModel `bun:"table:users"`
ID int64 `bun:",pk,autoincrement"`
Name string `bun:",notnull"`
Email string `bun:",unique,notnull"`
}
// Créer un nouvel utilisateur
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')
// Lire (recherche par ID)
var u User
err = db.NewSelect().Model(&u).
Where("id = ?", user.ID).
Scan(ctx)
// SELECT * FROM users WHERE id = X
// Mettre à jour (un seul champ)
_, 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
// Supprimer
_, err = db.NewDelete().Model((*User)(nil)).
Where("id = ?", u.ID).
Exec(ctx)
// DELETE FROM users WHERE id = X
sqlc
// Supposons que nous ayons écrit du SQL dans des fichiers et que sqlc ait généré une structure Queries avec des méthodes.
queries := New(db) // New prend un *sql.DB (ou pgx.Conn) et retourne les Queries générées
// Créer un nouvel utilisateur (la méthode générée exécute l'INSERT et scanne le résultat)
newUser, err := queries.CreateUser(ctx, "Alice", "alice@example.com")
// SQL dans queries.sql -> INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email
// Lire (par ID)
user, err := queries.GetUser(ctx, newUser.ID)
// SQL -> SELECT id, name, email FROM users WHERE id = $1
// Mettre à jour (email par 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
// Supprimer
err = queries.DeleteUser(ctx, newUser.ID)
// SQL -> DELETE FROM users WHERE id = $1
Comme le montre le code ci-dessus, chaque approche a une sensation différente :
-
GORM utilise des méthodes de struct et une chaîne fluide sur
db.Model(&obj)
ou directement sur l’objetdb
. Il remplit le struct avec les valeurs retournées (par exemple, aprèsCreate
,user.ID
est défini). Il cache également les détails SQL par conception — vous ne voyez généralement pas la requête sauf en mode débogage. -
Ent utilise une API fluide générée. Notez comment les méthodes comme
Create().SetX().Save(ctx)
ouUpdateOneID(id).SetX().Save(ctx)
séparent clairement les phases de construction et d’exécution. Ent retourne des objets ent.Type (qui correspondent aux lignes) et des erreurs, de manière similaire à comment une requête SQL retournerait des résultats ou une erreur. -
Bun exige de spécifier davantage explicitement (par exemple, en utilisant
Set("email = ?", ...)
pour les mises à jour), ce qui est très proche d’écrire du SQL mais avec la syntaxe Go. Après une insertion, le structuser
n’est pas automatiquement rempli avec l’ID nouvellement créé sauf si vous ajoutez une clauseRETURNING
(Bun prend en charge.Returning()
si nécessaire). L’exemple ci-dessus garde les choses simples. -
sqlc ressemble à appeler toute fonction Go. Nous appelons
queries.CreateUser
, etc., et sous le capot, ce sont des instructions préparées qui s’exécutent. Le SQL est écrit dans des fichiers externes, donc bien que vous ne le voyez pas dans le code Go, vous avez un contrôle total dessus. Les objets retournés (par exemple,newUser
) sont des structs Go purs générés par sqlc pour modéliser les données.
On peut observer des différences de verbosité (GORM est très concis ; Bun et sqlc nécessitent un peu plus de frappe de code ou de SQL) et des différences de style (Ent et GORM offrent des abstractions de haut niveau alors que Bun et sqlc sont plus proches des requêtes brutes). Selon vos préférences, vous pourriez privilégier l’explicititude par rapport à la brièveté ou inversement.
TL;DR
Le choix de l’ORM ou de la bibliothèque de base de données “correcte” en Go dépend des besoins de votre application et des préférences de votre équipe :
-
GORM est un excellent choix si vous souhaitez un ORM éprouvé qui fait beaucoup pour vous. Il brille dans le développement rapide d’applications CRUD où la commodité et un ensemble riche de fonctionnalités sont plus importants que d’extraire chaque goutte de performance. Le support communautaire et la documentation sont excellents, ce qui peut lisser les angles durs de sa courbe d’apprentissage. Soyez attentif à utiliser ses fonctionnalités de manière appropriée (par exemple, utilisez
Preload
ouJoins
pour éviter les pièges de chargement paresseux) et attendez un peu de surcoût. En échange, vous obtenez de la productivité et une solution unique pour la plupart des problèmes. Si vous avez besoin de choses comme le support de plusieurs bases de données ou un écosystème de plugins étendu, GORM vous couvre. -
Ent s’adresse à ceux qui privilégient la sécurité de type, la clarté et la maintenabilité. Il est bien adapté aux grands projets de code où les changements de schéma sont fréquents et où vous souhaitez que le compilateur soit de votre côté pour attraper les erreurs. Ent peut impliquer plus de conception initiale (définir des schémas, exécuter la génération), mais cela paie quand le projet grandit — votre code reste solide et facile à refactoriser. En termes de performance, il peut gérer des charges lourdes et des requêtes complexes efficacement, souvent mieux que les ORMs en mode ActiveRecord, grâce à sa génération optimisée de SQL et à son cache. Ent est légèrement moins “plug-and-play” pour les scripts rapides, mais pour les services longue durée, il fournit une base solide et scalable. Son approche moderne (et son développement actif) en font un choix orienté vers l’avenir.
-
Bun est idéal pour les développeurs qui disent “je connais le SQL et je veux juste un assistant léger autour”. Il renonce à certains “magiques” pour vous donner le contrôle et la vitesse. Si vous construisez un service sensible à la performance et que vous n’avez pas peur d’être explicite dans votre code d’accès aux données, Bun est une option convaincante. C’est également un bon compromis si vous migrez depuis
database/sql
+sqlx
et souhaitez ajouter de la structure sans sacrifier beaucoup d’efficacité. Le compromis est une communauté plus petite et moins d’abstractions de haut niveau — vous écrirez un peu plus de code à la main. Mais comme le disent les docs de Bun, il ne se met pas en travers de votre chemin. Utilisez Bun quand vous souhaitez un ORM qui ressemble à l’utilisation directe du SQL, particulièrement pour les projets centrés sur PostgreSQL où vous pouvez exploiter les fonctionnalités spécifiques à la base de données. -
sqlc est dans une catégorie à part. Il est parfait pour l’équipe Go qui dit “nous n’avons pas besoin d’un ORM, nous voulons des assurances de type pour notre SQL”. Si vous avez des compétences en SQL solides et préférez gérer les requêtes et le schéma en SQL (peut-être que vous avez des DBAs ou que vous appréciez simplement la création d’efficaces requêtes SQL), sqlc augmentera probablement votre productivité et votre confiance. La performance est essentiellement optimale, car rien ne se situe entre votre requête et la base de données. La seule raison de ne pas utiliser sqlc serait si vous détestez vraiment écrire du SQL ou si vos requêtes sont si dynamiques que d’écrire de nombreuses variantes devient lourd. Même alors, vous pourriez utiliser sqlc pour la majorité des requêtes statiques et gérer les quelques cas dynamiques avec une autre approche. Sqlc s’intègre également bien avec d’autres outils — il n’exclut pas l’utilisation d’un ORM dans certaines parties de votre projet (certains projets utilisent GORM pour les choses simples et sqlc pour les chemins critiques, par exemple). En bref, choisissez sqlc si vous valorisez l’explicititude, l’absence de surcoût et le SQL sécurisé par type — c’est un outil puissant à avoir dans votre boîte à outils.
Enfin, il convient de noter que ces outils ne sont pas mutuellement exclusifs dans l’écosystème. Chacun a ses propres avantages et inconvénients, et dans l’esprit pragmatique de Go, de nombreuses équipes évaluent les compromis cas par cas. Il n’est pas rare de commencer avec un ORM comme GORM ou Ent pour la vitesse de développement, puis d’utiliser sqlc ou Bun pour des chemins spécifiques qui nécessitent une performance maximale. Les quatre solutions sont activement maintenues et largement utilisées, donc il n’y a pas de “mauvais” choix global — il s’agit du bon choix pour votre contexte. J’espère que cette comparaison vous a donné une image plus claire de la manière dont GORM, Ent, Bun et sqlc se comparent, et vous aide à prendre une décision éclairée pour votre prochain projet en Go.
Liens utiles
- https://gorm.io
- https://entgo.io/
- https://bun.uptrace.dev
- https://github.com/sqlc-dev/sqlc
- https://blog.jetbrains.com/go/2023/04/27/comparing-db-packages/
- Feuille de calcul Go
- Résoudre l’erreur GORM AutoMigrate PostgreSQL
- Réordonner des documents textuels avec Ollama et modèle d’embedding Qwen3 - en Go