ORM a usar em GO: GORM, sqlc, Ent ou Bun?
GORM vs sqlc vs Ent vs Bun
O ecossistema do Go oferece uma variedade de ferramentas ORM (Object-Relational Mapping) e bibliotecas de banco de dados, cada uma com sua própria filosofia. Aqui está uma comparação abrangente de quatro soluções principais para usar PostgreSQL no Go: GORM, sqlc, Ent e Bun.
Vamos avaliá-las em termos de desempenho, experiência do desenvolvedor, popularidade e
funcionalidades/extensibilidade, com exemplos de código demonstrando operações básicas de CRUD
em um modelo User
. Desenvolvedores intermediários e avançados do Go
obterão insights sobre os trade-offs de cada ferramenta e quais podem se adequar
melhor às suas necessidades.
Visão geral dos ORMs
-
GORM – Um ORM rico em recursos, no estilo Active Record. O GORM permite que você defina structs do Go como modelos e fornece uma API extensa para consultar e manipular dados usando encadeamento de métodos. Ele existe há anos e é um dos ORMs do Go mais amplamente utilizados (com quase 39k estrelas no GitHub). O apelo do GORM é seu conjunto robusto de recursos (migrações automáticas, manipulação de relacionamentos, carregamento antecipado, transações, ganchos, etc.) que permite um código base do Go limpo com mínimo de SQL bruto. No entanto, ele introduz seus próprios padrões e tem sobrecarga de tempo de execução devido à reflexão e uso de interfaces, o que pode impactar o desempenho em operações grandes.
-
sqlc – Não é um ORM tradicional, mas um gerador de código SQL. Com sqlc, você escreve consultas SQL puras (em arquivos
.sql
), e a ferramenta gera código Go tipo seguro (funções DAO) para executar essas consultas. Isso significa que você interage com o banco de dados chamando funções Go geradas (por exemplo,CreateUser
,GetUser
) e obtém resultados fortemente tipados sem escanear manualmente as linhas. O sqlc permite usar SQL bruto com segurança em tempo de compilação — não há camada de construção de consultas ou reflexão em tempo de execução. Ele se tornou rapidamente popular (~16k estrelas) por sua simplicidade e desempenho: ele evita totalmente a sobrecarga em tempo de execução aproveitando consultas preparadas, tornando-o tão rápido quanto usardatabase/sql
diretamente. O trade-off é que você deve escrever (e manter) instruções SQL para suas consultas, e lógica de consulta dinâmica pode ser menos direta comparada a ORMs que constroem SQL para você. -
Ent (Entgo) – Um ORM code-first que usa geração de código para criar uma API tipo segura para seu modelo de dados. Você define seu esquema em Go (usando a DSL do Ent para campos, arestas/relações, restrições, etc.), então o Ent gera pacotes Go para os modelos e métodos de consulta. O código gerado usa uma API fluente para construir consultas, com verificação de tipo em tempo de compilação completa. O Ent é relativamente novo (apoiado pela Fundação Linux e desenvolvido pela Ariga). Ele se concentra em experiência do desenvolvedor e correção — consultas são construídas via métodos encadeáveis em vez de strings brutas, o que as torna mais fáceis de compor e menos propensas a erros. A abordagem do Ent produz consultas SQL altamente otimizadas e até mesmo cache resultados de consultas para reduzir viagens repetidas ao banco de dados. Foi projetado com escalabilidade em mente, então ele lida com esquemas complexos e relações de forma eficiente. No entanto, usar o Ent adiciona um passo de construção (executar codegen) e o vincula ao seu ecossistema. Atualmente, ele suporta PostgreSQL, MySQL, SQLite (e suporte experimental para outros bancos de dados), enquanto o GORM suporta uma gama mais ampla de bancos de dados (incluindo SQL Server e ClickHouse).
-
Bun – Um ORM mais novo com uma abordagem “SQL-first”. O Bun foi inspirado no
database/sql
do Go e na biblioteca go-pg mais antiga, visando ser leve e rápido com foco em recursos do PostgreSQL. Em vez do padrão Active Record, o Bun fornece um construtor de consultas fluente: você começa uma consulta comdb.NewSelect()
/NewInsert()
/ etc., e a constrói com métodos que se assemelham muito ao SQL (você pode até inserir snippets de SQL bruto). Ele mapeia resultados de consultas para structs do Go usando tags de struct semelhantes às do GORM. O Bun favorece a explicitidade e permite recorrer facilmente ao SQL bruto quando necessário. Ele não executa migrações automaticamente para você (você escreve migrações ou usa o pacote migrate do Bun manualmente), o que alguns consideram um ponto positivo para o controle. O Bun é elogiado por lidar com consultas complexas de forma elegante e suportar tipos avançados de Postgres (arrays, JSON, etc.) fora da caixa. Na prática, o Bun impõe muito menos sobrecarga de tempo de execução do que o GORM (não há reflexão pesada em cada consulta), traduzindo-se em melhor throughput em cenários de alta carga. Sua comunidade é menor (~4k estrelas) e seu conjunto de recursos, embora sólido, não é tão expansivo quanto o do GORM. O Bun pode exigir um pouco mais de conhecimento em SQL para ser usado efetivamente, mas ele recompensa isso com desempenho e flexibilidade.
Benchmarks de Desempenho
Quando se trata de desempenho (latência de consulta e throughput), há diferenças significativas no comportamento dessas ferramentas, especialmente à medida que a carga aumenta. Benchmarks recentes e experiências dos usuários destacam alguns pontos-chave:
-
GORM: A conveniência do GORM vem ao custo de algum desempenho. Para operações simples ou conjuntos de resultados pequenos, o GORM pode ser bastante rápido — de fato, um benchmark mostrou que o GORM teve o tempo de execução mais rápido para consultas muito pequenas (por exemplo, buscar 1 ou 10 registros). No entanto, à medida que o número de registros cresce, a sobrecarga do GORM faz com que ele fique significativamente para trás. Em um teste que buscava 15.000 linhas, o GORM foi aproximadamente 2× mais lento que o sqlc ou até mesmo o
database/sql
direto (59,3 ms para o GORM vs ~31,7 ms para o sqlc e 32,0 ms para o database/sql nesse cenário). O design pesado com reflexão e abstrações de composição de consultas em tempo de execução adicionam latência, e o GORM pode emitir várias consultas para cargas complexas (por exemplo, uma consulta por entidade relacionada quando se usaPreload
por padrão), o que pode amplificar o atraso. Em resumo: o GORM geralmente está bem para cargas de trabalho moderadas, mas em cenários de alta throughput ou operações em massa grandes, sua sobrecarga pode se tornar um gargalo. -
sqlc: Como as chamadas do sqlc são essencialmente SQL escrito à mão sob o capô, seu desempenho está à altura de usar a biblioteca padrão diretamente. Benchmarks consistentemente colocam sqlc entre as opções mais rápidas, frequentemente apenas marginalmente mais lento ou até mesmo ligeiramente mais rápido que o
database/sql
bruto para operações comparáveis. Por exemplo, em 10k+ registros, o sqlc foi observado para superar levemente odatabase/sql
bruto em throughput (provavelmente devido à evitação de alguns da sobrecarga de escaneamento e uso de código gerado eficiente). Com o sqlc, não há construtor de consultas em tempo de execução — sua consulta é executada como uma instrução preparada com mínima sobrecarga. O ponto principal é que você já fez o trabalho de escrever uma consulta SQL otimizada; o sqlc simplesmente poupou você do esforço de escanear linhas em tipos Go. Na prática, isso significa excelente escalabilidade — o sqlc lidará com grandes volumes de dados tão bem quanto o SQL bruto, tornando-o uma escolha de topo quando a velocidade bruta é crítica. -
Ent: O desempenho do Ent está entre abordagens de SQL bruto e ORMs tradicionais. Como o Ent gera código tipo seguro, ele evita reflexão e custos de composição de consultas em tempo de execução. O SQL que ele produz é bastante otimizado (você tem controle para escrever consultas eficientes via a API do Ent), e o Ent inclui uma camada interna de cache para reutilizar planos de execução de consultas/resultados em alguns casos. Isso pode reduzir os acessos repetidos ao banco de dados em fluxos de trabalho complexos. Embora benchmarks específicos de Ent contra outros variem, muitos desenvolvedores relatam que o Ent supera o GORM para operações equivalentes, especialmente à medida que a complexidade cresce. Uma razão é que o GORM pode gerar consultas subótimas (por exemplo, N+1 selects se não for usado cuidadosamente junções/preloads), enquanto o Ent incentiva o carregamento antecipado explícito de relações e fará junções de dados em menos consultas. O Ent também tende a alocar menos memória por operação do que o GORM, o que pode melhorar o throughput reduzindo a pressão sobre o coletor de lixo do Go. No geral, o Ent foi construído para alto desempenho e esquemas grandes — sua sobrecarga é baixa, e ele pode lidar com consultas complexas de forma eficiente — mas pode não corresponder à throughput bruta de SQL escrito à mão (sqlc) em todos os cenários. É uma forte candidata se você quiser tanto velocidade quanto a segurança de uma camada ORM.
-
Bun: O Bun foi criado com desempenho em mente e é frequentemente citado como uma alternativa mais rápida ao GORM. Ele usa uma API fluente para construir consultas SQL, mas esses construtores são leves. O Bun não esconde o SQL de você — é mais de uma camada fina sobre
database/sql
, o que significa que você incurre em muito pouco overhead além do que a biblioteca padrão do Go faz. Os usuários notaram melhorias significativas ao migrar do GORM para o Bun em grandes projetos: por exemplo, um relato mencionou que o GORM era “muito lento” para a escala deles, e substituí-lo pelo Bun (e alguns SQL bruto) resolveu seus problemas de desempenho. Em benchmarks como go-orm-benchmarks, o Bun tende a estar perto do topo em velocidade para a maioria das operações (muitas vezes dentro de 1,5× do sqlx/sql bruto) e bem à frente do GORM em throughput. Ele também suporta inserções em lote, controle manual de junções vs consultas separadas e outras otimizações que os desenvolvedores podem aproveitar. Conclusão: o Bun entrega desempenho próximo ao de hardware bruto, e é uma ótima escolha quando você quer a conveniência de um ORM, mas não pode sacrificar velocidade. Sua natureza SQL-first garante que você sempre possa otimizar consultas se as geradas não forem ideais.
Resumo de Desempenho: Se o objetivo for o máximo de desempenho (por exemplo, aplicações intensivas de dados, microserviços sob alta carga), sqlc ou até mesmo chamadas database/sql
escritas à mão são os
vencedores.
Eles têm praticamente nenhum custo de abstração e escalam linearmente. Ent e Bun também performam muito bem e podem lidar com consultas complexas de forma eficiente — eles encontram um equilíbrio entre velocidade e abstração.
GORM oferece mais recursos, mas ao custo de sobrecarga; é perfeitamente aceitável para muitas aplicações, mas você deve estar ciente de seu impacto se esperar lidar com grandes volumes de dados ou precisar de latência ultra-baixa. (Nesses casos, você pode mitigar o custo do GORM usando cuidadosamente Joins
em vez de Preload
para reduzir a contagem de consultas, ou misturando SQL bruto em caminhos críticos, mas isso adiciona complexidade.)
Experiência do Desenvolvedor e Facilidade de Uso
A experiência do desenvolvedor pode ser subjetiva, mas abrange a curva de aprendizado, a clareza da API e quão produtivo você pode ser no dia a dia de programação. Aqui está como nossos quatro concorrentes se comparam em termos de facilidade de uso e fluxo de desenvolvimento:
-
GORM – Recursos ricos, mas uma curva de aprendizado: O GORM é frequentemente o primeiro ORM que novatos no Go tentam porque promete uma experiência totalmente automatizada (você define structs e pode imediatamente Criar/Encontrar sem escrever SQL). A documentação é muito abrangente com muitos guias, o que ajuda. No entanto, o GORM tem sua própria forma idiomática de fazer coisas (“abordagem baseada em código” para interações com o banco de dados) e pode parecer incômodo inicialmente se você estiver acostumado com SQL bruto. Muitas operações comuns são diretas (por exemplo,
db.Find(&objs)
), mas quando você se aventura em associações, relações polimórficas ou consultas avançadas, você precisa aprender as convenções do GORM (tags de struct,Preload
vsJoins
, etc.). Por isso, há frequentemente menção de uma curva de aprendizado inicial acentuada. Uma vez que você subir essa curva, o GORM pode ser muito produtivo — você passa menos tempo escrevendo SQL repetitivo e mais tempo em lógica Go. Na verdade, depois de dominá-lo, muitos encontram que podem desenvolver recursos mais rapidamente com o GORM do que com bibliotecas de nível mais baixo. Em resumo, a experiência do desenvolvedor com o GORM: alta complexidade inicial, mas se suaviza com experiência. A grande comunidade significa muitos exemplos e respostas no StackOverflow disponíveis, e um ecossistema de plugins rico pode simplificar ainda mais tarefas (por exemplo, plugins para exclusões suaves, auditoria, etc.). O principal caveat é que você deve permanecer ciente do que o GORM está fazendo sob o capô para evitar armadilhas (como consultas N+1 acidentais). Mas para um desenvolvedor que investe tempo no GORM, ele realmente “sente” como uma solução poderosa e integrada que lida com muitas coisas para você. -
Ent – Schema-first e type-safe: O Ent foi explicitamente projetado para melhorar a experiência do desenvolvedor dos ORMs. Você descreve seu esquema em um único lugar (código Go) e obtém uma API fortemente tipada para trabalhar — isso significa não há consultas tipadas com strings, e muitos erros são capturados em tempo de compilação. Por exemplo, se você tentar se referir a um campo ou aresta que não existe, seu código simplesmente não compilará. Esse “rede de segurança” é um grande ganho de experiência do desenvolvedor para projetos grandes. A API do Ent para construir consultas é fluente e intuitiva: você encadeia métodos como
.Where(user.EmailEQ("alice@example.com"))
e parece quase como inglês. Desenvolvedores vindo de ORMs em outras linguagens frequentemente encontram a abordagem do Ent natural (é semelhante a Entity Framework ou Prisma, mas em Go). Aprendizado com o Ent requer entender seu fluxo de geração de código. Você precisará executarentc generate
(ou usargo generate
) sempre que modificar seu esquema. Isso é um passo extra, mas geralmente parte do processo de construção. A curva de aprendizado com o Ent é moderada — se você souber Go e alguns fundamentos de SQL, os conceitos do Ent (Campos, Arestas, etc.) são diretos. Na verdade, muitos encontram o Ent mais fácil de racionalizar do que o GORM, porque você não precisa se perguntar qual SQL está sendo produzido; muitas vezes você pode prever isso a partir dos nomes dos métodos, e você pode sair a consulta para depuração se necessário. A documentação e exemplos do Ent são bastante bons, e sendo apoiado por uma empresa garante que ele está sendo mantido ativamente. Experiência do desenvolvedore com o Ent: muito amigável para aqueles que desejam clareza e segurança. O código que você escreve é mais verboso comparado a SQL bruto, mas é auto-documentado. Além disso, refatorar é mais seguro (renomear um campo no esquema e regenerar atualizará todas as usagens nas consultas). O lado negativo pode ser que você está um pouco limitado pelo que o Ent codegen oferece — se você precisar de uma consulta muito personalizada, você pode escrevê-la com a API do Ent (que pode lidar com junções complexas, mas ocasionalmente você pode encontrar um caso que é mais fácil de escrever em SQL bruto). O Ent permite snippets de SQL bruto quando necessário, mas se você se encontrar fazendo isso com frequência, você pode questionar se um ORM é a escolha certa. Em resumo, o Ent fornece uma experiência do desenvolvedor suave para a maioria da lógica CRUD e consulta com mínimos surpresas, desde que você esteja okay executando um passo de geração de código e seguindo os padrões que o Ent impõe. -
Bun – Mais próximo do SQL, menos magia: Usar o Bun se sente diferente de usar o GORM ou o Ent. A filosofia do Bun é não esconder SQL — se você souber SQL, já sabe como usar o Bun em grande parte. Por exemplo, para consultar usuários você pode escrever:
db.NewSelect().Model(&users).Where("name = ?", name).Scan(ctx)
. Isso é uma API fluente, mas muito próxima da estrutura de uma instrução SELECT real. A curva de aprendizado para o Bun é geralmente baixa se você estiver confortável com SQL em si. Há menos “magia ORM” para aprender; você aprende principalmente os nomes dos métodos para construir consultas e as convenções de tags de struct. Isso torna o Bun bastante acessível para desenvolvedores experientes que usaramdatabase/sql
ou outros construtores de consultas comosqlx
. Em contraste, um iniciante que não estiver confiante em escrever SQL pode encontrar o Bun um pouco menos útil do que o GORM — o Bun não gerará automaticamente consultas complexas para você via relações a menos que você as especifique. No entanto, o Bun does suporta relações e carregamento antecipado (método Relation()
para juntar tabelas) — ele apenas o faz de forma explícita, o que alguns desenvolvedores preferem para clareza. A experiência do desenvolvedor com o Bun pode ser descrita como leve e previsível. Você escreve um pouco mais de código do que com o GORM para algumas operações (já que o Bun frequentemente pede que você especifique explicitamente colunas ou junções), mas em troca você tem mais controle e visibilidade. Há mínima magia interna, então o depuramento é mais fácil (você normalmente pode registrar a string da consulta que o Bun construiu). A documentação do Bun (o guia do uptrace.dev) é abrangente e inclui padrões para migrações, transações, etc., embora a comunidade sendo menor significar menos tutoriais de terceiros. Outro aspecto da experiência do desenvolvedor é a ferramenta disponível: o Bun sendo apenas uma extensão dodatabase/sql
significa que qualquer ferramenta que funcione comsql.DB
(como proxies de depuração, registradores de consultas) funciona facilmente com o Bun. Em resumo, o Bun oferece uma experiência sem rodeios: parece escrever SQL estruturado em Go. Isso é ótimo para desenvolvedores que valorizam controle e desempenho, mas pode não ajudar tanto desenvolvedores menos experientes quanto algo como o GORM. O consenso é que o Bun faz coisas simples fáceis e coisas complexas possíveis (muito como SQL bruto), sem impor muito framework sobre você. -
sqlc – Escreva SQL, obtenha código Go: A abordagem do sqlc inverte o narrativo usual do ORM. Em vez de escrever código Go para produzir SQL, você escreve SQL e obtém código Go. Para desenvolvedores que amam SQL, isso é fantástico — você pode usar toda a força do SQL (junções complexas, CTEs, funções de janela, você nomeia) com zero limitações do ORM. A curva de aprendizado para o sqlc em si é muito pequena. Se você souber como escrever um SELECT/INSERT/UPDATE em SQL, já fez a parte difícil. Você precisa aprender como anotar consultas com comentários
-- name: Name :one/many/exec
e configurar o arquivo de configuração, mas isso é trivial. O código gerado será definições de funções diretas, que você chama como qualquer função Go. Vantagens da experiência do desenvolvedor: não há API ORM para aprender, nenhuma surpresa — as consultas executam exatamente como escritas. Você evita uma classe inteira de problemas de ORMs (como descobrir por que um ORM gerou uma certa junção ou como ajustar uma consulta). Além disso, suas revisões de código podem incluir revisar o SQL em si, o que frequentemente é mais claro para lógica complexa. Outra grande vantagem de experiência do desenvolvedor: segurança de tipo e suporte ao IDE — os métodos e structs gerados podem ser acessados no seu editor, ferramentas de refatoração funcionam com eles, etc., ao contrário de consultas de string brutas que são opacas para IDEs. Do lado negativo, o sqlc exige que você gerencie scripts SQL. Isso significa que, se seu esquema ou requisitos mudarem, você terá que atualizar ou adicionar manualmente o SQL relevante e reexecutar o codegen. Não é difícil, mas é mais trabalho manual do que apenas chamar um método ORM. Além disso, consultas dinâmicas (onde partes do SQL são condicionais) podem ser trabalhosas — você escreve várias variantes de SQL ou usa truques de sintaxe SQL. Alguns desenvolvedores mencionam isso como uma limitação da abordagem do sqlc. Na prática, você pode frequentemente estruturar seu acesso aos dados de forma que não precise de SQL dinâmico excessivamente, ou pode chamardatabase/sql
bruto para esses casos de borda. Mas é uma consideração: o sqlc é excelente para consultas bem definidas, menos eficiente para construção de consultas ad-hoc. Em resumo, para um desenvolvedor proficiente com SQL, usar o sqlc se sente natural e altamente eficiente. Há muito pouco novo para aprender, e ele remove a repetição de código Go. Para um desenvolvedor menos familiarizado com SQL, o sqlc pode ser inicialmente mais lento para trabalhar com (em comparação com um ORM que, por exemplo, gera automaticamente consultas para CRUD básico). No entanto, muitos desenvolvedores Go consideram o sqlc um must-have porque ele atinge um ponto de equilíbrio: controle manual com alta segurança e sem custo em tempo de execução.
Popularidade e Suporte ao Ecossistema
A adoção e o suporte da comunidade podem influenciar sua escolha — uma biblioteca popular significa mais contribuições da comunidade, manutenção melhor e mais recursos para aprender.
-
GORM: Como o mais antigo e maduro dos quatro, o GORM tem, de longe, a maior base de usuários e ecossistema. Atualmente, é a ORM do Go com mais estrelas no GitHub (mais de 38k estrelas) e é usado em inúmeros projetos de produção. Os mantenedores estão ativos e o projeto atualiza-se regularmente (a versão v2 do GORM foi uma grande reformulação que melhorou o desempenho e a arquitetura). Uma grande vantagem da popularidade do GORM é a riqueza de extensões e integrações. Existem plugins oficiais e de terceiros para coisas como drivers de banco de dados (por exemplo, para PostgreSQL, MySQL, SQLite, SQL Server, ClickHouse), prontos para uso. O GORM também suporta um amplo conjunto de casos de uso: migrações, geração automática de esquema, exclusões suaves, campos JSON (com
gorm.io/datatypes
), busca de texto completo, etc., muitas vezes por meio de funcionalidades embutidas ou complementos. A comunidade produziu várias ferramentas, comogormt
(para gerar definições de struct a partir de um banco de dados existente), e muitos tutoriais e exemplos. Se você encontrar um problema, uma rápida pesquisa provavelmente encontrará uma questão ou pergunta no Stack Overflow feita por alguém. Resumo do ecossistema: O GORM é extremamente bem suportado. É a escolha padrão de ORM para muitos, o que significa que você encontrará ele em frameworks e modelos de projeto. O lado negativo é que seu tamanho pode fazê-lo parecer pesado, mas para muitos, esse é um trade-off aceitável pelo profundidade da comunidade. -
Ent: Apesar de ser mais novo (open-sourced em torno de 2019), o Ent cresceu uma comunidade forte. Com ~16k estrelas e apoio da Linux Foundation, não é um projeto de nicho. Grandes empresas começaram a usar o Ent por causa de suas vantagens centradas em esquema, e os mantenedores (na Ariga) oferecem suporte empresarial, que é um sinal de confiança para uso crítico de negócios. O ecossistema em torno do Ent está crescendo: existem extensões do Ent (ent/go) para coisas como integração OpenAPI/GraphQL, integração gRPC e até uma ferramenta de migração SQL que funciona com esquemas do Ent. Como o Ent gera código, alguns padrões do ecossistema são diferentes — por exemplo, se você quiser integrar com GraphQL, pode usar a geração de código do Ent para produzir resolvers de GraphQL. Os recursos de aprendizado para o Ent são bons (documentação oficial e um projeto de exemplo que cobre uma aplicação simples). Os fóruns da comunidade e discussões no GitHub estão ativos com perguntas sobre design de esquema e dicas. Em termos de suporte da comunidade, o Ent certamente passou pela fase de “adotante inicial” e é considerado pronto para produção e confiável. Talvez ainda não tenha tantas respostas no Stack Overflow quanto o GORM, mas está se aproximando rapidamente. Uma coisa a notar: como o Ent é um pouco mais opiniado (por exemplo, ele quer gerenciar o esquema), você provavelmente o usará sozinho, em vez de junto com outro ORM. Ele não tem “plugins” no mesmo sentido que o GORM, mas você pode escrever templates personalizados para estender a geração de código ou conectar-se a eventos de ciclo de vida (o Ent tem suporte a hooks/middleware para clientes gerados). O apoio da fundação indica suporte de longo prazo, então escolher o Ent é uma aposta segura se seu modelo se encaixar nas suas necessidades.
-
Bun: Bun (parte da suite open-source Uptrace) está ganhando popularidade, especialmente entre aqueles que eram fãs da biblioteca
go-pg
agora desmantelada. Com ~4.3k estrelas, é a comunidade mais pequena nesta comparação, mas é um projeto muito ativo. O mantenedor é responsivo e tem adicionado rapidamente recursos. A comunidade do Bun está entusiasmada com seu desempenho. Você encontrará discussões nos fóruns Go e no Reddit de desenvolvedores que migraram para o Bun por causa da velocidade. No entanto, devido à base de usuários menor, você pode não encontrar respostas para perguntas específicas com facilidade — às vezes você precisará ler a documentação/fonte ou perguntar no GitHub ou Discord do Bun (Uptrace fornece um chat de comunidade). O ecossistema de extensões é mais limitado: o Bun vem com sua própria biblioteca de migração, um carregador de fixtures e integra-se com as ferramentas de observabilidade da Uptrace, mas você não encontrará a variedade de plugins que o GORM tem. Dito isso, o Bun é compatível com o uso desql.DB
, então você pode misturar e combinar — por exemplo, usargithub.com/jackc/pgx
como o driver subjacente ou integrar-se com outros pacotes que esperam um*sql.DB
. O Bun não o trava. Em termos de suporte, sendo mais novo, a documentação está atualizada e os exemplos são modernos (muitas vezes mostrando o uso com context e outros). Os documentos oficiais comparam diretamente o Bun com o GORM e o Ent, o que é útil. Se o tamanho da comunidade for uma preocupação, uma estratégia poderia ser adotar o Bun por seus benefícios, mas usar seu uso relativamente superficial (por exemplo, você poderia trocá-lo por outra solução se necessário, já que ele não impõe uma abstração pesada). De qualquer forma, a trajetória do Bun é ascendente, e ele preenche uma necessidade específica (ORM orientado a desempenho), o que lhe dá força de permanência. -
sqlc: O sqlc é bastante popular na comunidade Go, comprovado por ~15.9k estrelas e muitos defensores, especialmente em círculos que se preocupam com o desempenho. É frequentemente recomendado em discussões sobre “evitar ORMs” porque atinge um equilíbrio agradável. A ferramenta é mantida por contribuidores (incluindo o autor original, que está ativo em melhorá-la). Sendo mais de um compilador do que uma biblioteca de tempo de execução, seu ecossistema gira em torno de integrações: por exemplo, editores/IDEs podem ter destaque de sintaxe para arquivos
.sql
e você executasqlc generate
como parte de seu build ou pipeline de CI. A comunidade criou templates e exemplos de repositórios base sobre como organizar o código com sqlc (muitas vezes combinando com uma ferramenta de migração como Flyway ou Golang-Migrate para versionamento de esquema, já que sqlc em si não gerencia o esquema). Há um Slack/Discord oficial para sqlc onde você pode fazer perguntas, e problemas no GitHub tendem a receber atenção. Muitos dos padrões comuns (como lidar com valores nulos ou campos JSON) são documentados nos docs do sqlc ou têm posts de blog da comunidade. Uma coisa a destacar: sqlc não é específico para Go — ele pode gerar código em outras linguagens (como TypeScript, Python). Isso amplia sua comunidade além do Go, mas especificamente no Go, é amplamente respeitado. Se você escolher sqlc, você está em boa companhia: é usado em produção por muitas startups e grandes empresas (conforme mostrado na comunidade e nos patrocinadores listados no repositório). A consideração-chave do ecossistema é que, como sqlc não fornece recursos de tempo de execução como um ORM, você pode precisar trazer outras bibliotecas para coisas como transações (embora você possa usar facilmentesql.Tx
com sqlc) ou talvez um wrapper leve de DAL. Na prática, a maioria usa sqlc junto com a biblioteca padrão (para transações, cancelamento de contexto, etc., você usa diretamente os idiomas dedatabase/sql
no seu código). Isso significa menos dependência de fornecedores — se você se afastar do sqlc, apenas significa escrever sua própria camada de dados, o que é tão difícil quanto escrever o SQL (que você já fez). No geral, comunidade e suporte para sqlc são robustos, com muitos recomendando-o como um “must-use” para projetos Go que interagem com bancos de dados SQL devido à sua simplicidade e confiabilidade.
Conjunto de Recursos e Extensibilidade
Cada uma dessas ferramentas oferece um conjunto diferente de recursos. Aqui comparamos suas capacidades e como extensíveis são para casos de uso avançados:
- Recursos do GORM: O GORM tem como objetivo ser um ORM completo. Sua lista de recursos é extensa:
- Vários Bancos de Dados: Suporte de primeira classe para PostgreSQL, MySQL, SQLite, SQL Server e mais. Migrar entre bancos geralmente é tão fácil quanto mudar o driver de conexão.
- Migrações: Você pode migrar automaticamente seu esquema a partir de seus modelos (
db.AutoMigrate(&User{})
criará ou alterará a tabelausers
). Embora conveniente, tenha cuidado ao usar migração automática em produção — muitos a usam para desenvolvimento e têm migrações mais controladas para produção. - Relacionamentos: As tags de struct do GORM (
gorm:"foreignKey:...,references:..."
) permitem que você defina um para muitos, muitos para muitos, etc. Ele pode lidar com tabelas de ligação para muitos para muitos e temPreload
para carregar relações com antecedência. O GORM padrão usa carregamento lento (ou seja, consultas separadas) ao acessar campos relacionados, mas você pode usarPreload
ouJoins
para personalizar isso. Versões mais recentes também têm uma API baseada em generics para consultas de associação mais fáceis. - Transações: O GORM tem um wrapper fácil de usar para transações (
db.Transaction(func(tx *gorm.DB) error { ... })
) e até suporte para transações aninhadas (pontos de salvamento). - Hooks e Callbacks: Você pode definir métodos como
BeforeCreate
,AfterUpdate
em seus structs de modelo, ou registrar callbacks globais que o GORM chamará em certos eventos de ciclo de vida. Isso é ótimo para coisas como definir automaticamente timestamps ou comportamento de exclusão suave. - Extensibilidade: O sistema de plugins do GORM (
gorm.Plugin
interface) permite estender sua funcionalidade. Exemplos: o pacotegorm-gen
gera métodos de consulta tipo seguro (se você preferir verificações de consulta em tempo de compilação), ou plugins da comunidade para auditoria, multi-tenancy, etc. Você também pode recorrer a SQL bruto a qualquer momento viadb.Raw("SELECT ...", params).Scan(&result)
. - Outras conveniências: O GORM suporta chaves primárias compostas, embutimento de modelos, associações polimórficas e até um resolutor de esquema para usar múltiplos bancos de dados (para réplicas de leitura, shardamento, etc.).
No geral, o GORM é muito extensível e rico em recursos. Virtualmente qualquer recurso relacionado a ORM que você possa querer tem um mecanismo embutido ou um padrão documentado no GORM. O custo dessa amplitude é complexidade e alguma rigidez (você frequentemente precisa se conformar com a maneira do GORM de fazer as coisas). Mas se você precisar de uma solução completa, o GORM entrega.
- Recursos do Ent: A filosofia do Ent está centrada em esquema como código. Recursos-chave incluem:
- Definição de Esquema: Você pode definir campos com restrições (únicos, valores padrão, enums, etc.), e arestas (relações) com cardinalidade (um para muitos, etc.). O Ent usa isso para gerar código e também pode gerar SQL de migração para você (existe um componente
ent/migrate
que pode produzir SQL de diferença entre seu esquema atual e o desejado). - Segurança de Tipo e Validação: Como os campos são fortemente tipados, você não pode, por exemplo, acidentalmente definir um campo inteiro como uma string. O Ent também permite tipos de campo personalizados (por exemplo, você pode integrar com
sql.Scanner
/driver.Valuer
para campos JSONB ou outros tipos complexos). - Construtor de Consultas: A API de consulta gerada pelo Ent cobre a maioria das construções SQL: você pode fazer selects, filtros, ordenação, limites, agregações, junções em arestas e até subconsultas. É expressiva — por exemplo, você pode escrever
client.User.Query().WithOrders(func(q *ent.OrderQuery) { q.Limit(5) }).Where(user.StatusEQ(user.StatusActive)).All(ctx)
para obter usuários ativos com suas primeiras 5 ordens cada, em uma única operação. - Transações: O cliente do Ent suporta transações ao expor uma variante transacional do cliente (via
tx, err := client.Tx(ctx)
que produz ument.Tx
que você pode usar para fazer várias operações e depois confirmar ou reverter). - Hooks e Middleware: O Ent permite registrar hooks em operações de criação/atualização/exclusão — são como interceptadores onde você pode, por exemplo, preencher automaticamente um campo ou impor regras personalizadas. Também há middleware para o cliente para envolver operações (útil para logs, instrumentação).
- Extensibilidade: Embora o Ent não tenha “plugins” propriamente ditos, sua geração de código é baseada em templates e você pode escrever templates personalizados se precisar estender o código gerado. Muitas funcionalidades avançadas foram implementadas dessa forma: por exemplo, integração com OpenTelemetry para rastreamento de chamadas de banco de dados, ou geração de resolvers de GraphQL a partir do esquema do Ent, etc. O Ent também permite misturar SQL bruto quando necessário através do pacote
entsql
ou obtendo o driver subjacente. A capacidade de gerar código adicional significa que equipes podem usar o Ent como base e adicionar seus próprios padrões por cima, se necessário. - Integração com GraphQL/REST: Um grande ponto de venda da abordagem baseada em gráfico do Ent é que se encaixa bem com GraphQL — seu esquema do Ent pode ser mapeado quase diretamente a um esquema GraphQL. Existem ferramentas para automatizar muito disso. Isso pode ser um ganho de produtividade se você estiver construindo um servidor de API.
- Ajustes de desempenho: Por exemplo, o Ent pode carregar arestas relacionadas em lote para evitar consultas N+1 (tem uma API de carregamento antecipado
.With<EdgeName>()
). Ele usará JOINs ou consultas adicionais de forma otimizada. Além disso, o cache de resultados de consulta (em memória) pode ser ativado para evitar acionar o banco de dados para consultas idênticas em um período curto.
Resumindo, o conjunto de recursos do Ent está orientado para projetos de grande escala e mantiveis. Pode faltar algumas das vantagens prontas do GORM (por exemplo, a migração automática de esquema do GORM vs scripts de migração gerados pelo Ent — o Ent escolhe separar essa preocupação), mas compensa com ferramentas poderosas de desenvolvimento e segurança de tipo. A extensibilidade no Ent é sobre gerar o que você precisa — é muito flexível se você estiver disposto a mergulhar em como o entc (a geração de código) funciona.
- Recursos do Bun: O Bun se concentra em ser uma ferramenta poderosa para aqueles que conhecem SQL. Alguns recursos e pontos de extensibilidade:
- Construtor de Consultas: No núcleo do Bun está o construtor de consultas fluente que suporta a maioria das construções SQL (SELECT com junções, CTEs, subconsultas, além de INSERT, UPDATE com suporte a bulk). Você pode iniciar uma consulta e personalizá-la profundamente, até injetar SQL bruto
(
.Where("some_condition (?)", value)
). - Recursos específicos do PostgreSQL: O Bun tem suporte embutido para tipos de array do Postgres, JSON/JSONB (mapeando para
[]<type>
oumap[string]interface{}
ou tipos personalizados), e suporta o escaneamento em tipos compostos. Por exemplo, se você tiver uma coluna JSONB, pode mapeá-la para uma estrutura Go com tagsjson:
e o Bun lidará com o marshaling para você. Isso é uma vantagem sobre alguns ORMs que tratam JSONB como opaco. - Relacionamentos/Carregamento Antecipado: Como mostrado anteriormente, o Bun permite que você defina tags de struct para relações e, em seguida, use
.Relation("FieldName")
em consultas para juntar e carregar entidades relacionadas. Por padrão,.Relation
usa LEFT JOIN (você pode simular junções internas ou outros tipos adicionando condições). Isso lhe dá controle fino sobre como os dados relacionados são buscados (em uma consulta vs múltiplas). Se você preferir consultas manuais, sempre pode escrever uma junção diretamente no construtor de SQL do Bun. Diferente do GORM, o Bun nunca carregará relações automaticamente sem você pedir, o que evita problemas N+1 acidentais. - Migrações: O Bun fornece uma ferramenta de migração separada (em
github.com/uptrace/bun/migrate
). As migrações são definidas em Go (ou strings SQL) e versionadas. Não é tão automágico quanto a migração automática do GORM, mas é robusta e integra-se bem com a biblioteca. Isso é ótimo para manter mudanças no esquema explícitas. - Extensibilidade: O Bun é construído com interfaces semelhantes às de
database/sql
— até mesmo envolve um*sql.DB
. Portanto, você pode usar qualquer ferramenta de nível inferior com ele (por exemplo, você poderia executar uma consulta*pgx.Conn
se necessário e ainda assim escanear em modelos do Bun). O Bun permite scanners de valor personalizados, então, se você tiver um tipo complexo que quiser armazenar, implementesql.Scanner
/driver.Valuer
e o Bun usará. Para estender a funcionalidade do Bun, já que é relativamente direta, muitas pessoas simplesmente escrevem funções de ajuda adicionais sobre a API do Bun em seus projetos, em vez de plugins distintos. A biblioteca em si está evoluindo, então novos recursos (como ajudas adicionais de consulta ou integrações) estão sendo adicionados pelos mantenedores. - Outros recursos: O Bun suporta cancelamento de contexto (todas as consultas aceitam
ctx
), tem um pool de conexões viadatabase/sql
(configurável lá), e suporta cache de instruções preparadas (por meio do driver se estiver usandopgx
). Há também uma funcionalidade agradável onde o Bun pode escanear para ponteiros de struct ou fatias primitivas facilmente (por exemplo, selecionar uma coluna em uma[]string
diretamente). São pequenas conveniências como esta que tornam o Bun agradável para aqueles que detestam código de escaneamento repetitivo.
Resumindo, o conjunto de recursos do Bun cobre 90% das necessidades de ORM, mas com ênfase em transparência e desempenho. Talvez não tenha todos os recursos (por exemplo, não faz atualizações automáticas de esquema ou tem um padrão de ativo), mas fornece os blocos de construção para implementar o que você precisar por cima. Como é jovem, espere que seu conjunto de recursos continue se expandindo, guiado por casos de uso reais (os mantenedores frequentemente adicionam recursos conforme os usuários os solicitam).
- Recursos do sqlc: Os “recursos” do sqlc são bastante diferentes, já que é um gerador:
- Suporte completo ao SQL: O maior recurso é simplesmente que você está usando diretamente os recursos do próprio PostgreSQL. Se o Postgres suporta, você pode usá-lo no sqlc. Isso inclui CTEs, funções de janela, operadores JSON, consultas espaciais (PostGIS), etc. Não há necessidade para a biblioteca em si implementar algo especial para usar esses recursos.
- Mapeamento de tipos: O sqlc é inteligente ao mapear tipos SQL para tipos Go. Ele lida com tipos padrão e permite configurar ou estender mapeamentos para tipos personalizados ou enums. Por exemplo, um
UUID
do Postgres pode mapear para um tipogithub.com/google/uuid
se quiser, ou um tipo de domínio pode mapear para o tipo Go subjacente. - Tratamento de nulos: Ele pode gerar
sql.NullString
ou ponteiros para colunas nulas, dependendo da sua preferência, então você não precisa lutar com o escaneamento de nulos. - Operações em lote: Embora o sqlc em si não forneça uma API de nível superior para operações em lote, você certamente pode escrever uma instrução SQL de inserção em lote e gerar código para ela. Ou chamar um procedimento armazenado — isso é outro recurso: como é SQL, você pode aproveitar procedimentos armazenados ou funções do banco de dados, e ter o sqlc gerar um wrapper Go.
- Consultas com múltiplas instruções: Você pode colocar múltiplas instruções SQL em uma única consulta nomeada e o sqlc executará elas em uma transação (se o driver suportar), retornando os resultados da última consulta ou o que você especificar. Isso é uma maneira de, por exemplo, fazer algo como “criar e depois selecionar” em uma única chamada.
- Extensibilidade: Como um compilador, a extensibilidade vem na forma de plugins para novas linguagens ou contribuições da comunidade para suportar novas construções SQL. Por exemplo, se um novo tipo de dados do PostgreSQL surgir, o sqlc pode ser atualizado para suportar o mapeamento dele. Em seu aplicativo, você pode estender em torno do sqlc escrevendo funções de wrapper. Como o código gerado está sob seu controle, você poderia modificá-lo — embora normalmente você não o faça, você ajustaria o SQL e re-geraria. Se necessário, você sempre pode misturar chamadas brutas de
database/sql
com o sqlc em seu código (não há conflito, já que o sqlc apenas gera algum código Go). - Dependências de tempo de execução mínimas: O código gerado pelo sqlc normalmente depende apenas do
database/sql
padrão (e um driver específico como pgx). Não há uma biblioteca de tempo de execução pesada; é apenas alguns tipos de ajuda. Isso significa zero sobrecarga em produção do lado da biblioteca — todo o trabalho é no tempo de compilação. Isso também significa que você não obterá recursos como um cache de objetos ou mapa de identidade (como alguns ORMs têm) — se você precisar de cache, implementará isso em sua camada de serviço ou usará uma biblioteca separada.
Na prática, o “recurso” do sqlc é que ele reduz a lacuna entre seu banco de dados SQL e seu código Go sem adicionar coisas extras entre eles. É uma ferramenta especializada — não faz migrações, não rastreia o estado do objeto, etc., por design. Essas preocupações são deixadas para outras ferramentas ou para o desenvolvedor. Isso é atraente se você quiser uma camada de dados leve, mas se você estiver procurando por uma solução completa que lide com tudo desde o esquema até consultas e relações no código, o sqlc sozinho não é — você o combinaria com outros padrões ou ferramentas.
Para recapitular esta seção, GORM é a ferramenta de recursos e uma escolha clara se você precisar de uma ORM madura, extensível que possa ser adaptada por meio de plugins. Ent oferece uma abordagem moderna e segura de tipo aos recursos, priorizando corretude e manutenibilidade (com coisas como geração de código, hooks e integrações em camadas de API). Bun fornece os essenciais e aposta na proficiência em SQL, tornando fácil estender escrevendo mais SQL ou wrappers leves. sqlc reduz os recursos ao metal — é essencialmente tão rico em recursos quanto o próprio SQL, mas qualquer coisa além disso (cache, etc.) é up a você para camadas.
Exemplo de Código: Operações CRUD em Cada ORM
Nada ilustra as diferenças melhor do que ver como cada ferramenta
lida com operações básicas de CRUD para um modelo simples. Vamos supor que temos um modelo User
com campos ID
, Name
e Email
. Abaixo estão trechos de código lado a lado para criar, ler, atualizar e excluir um usuário em cada
biblioteca, usando PostgreSQL como banco de dados.
Nota: Em todos os casos, assuma que temos uma conexão com o banco de dados
estabelecida (por exemplo, db
ou client
) já configurada, e o tratamento de erros
foi omitido por brevidade. O objetivo é comparar a API e a verbosidade
de cada abordagem.
GORM (Estilo Active Record)
// Definição do modelo
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string
}
// Criar um novo usuário
user := User{Name: "Alice", Email: "alice@example.com"}
db.Create(&user) // INSERT INTO users (name,email) VALUES ('Alice','alice@example.com')
// Ler (buscar por chave primária)
var u User
db.First(&u, user.ID) // SELECT * FROM users WHERE id = X LIMIT 1
// Atualizar (único campo)
db.Model(&u).Update("Email", "alice_new@example.com")
// (gera: UPDATE users SET email='alice_new@example.com' WHERE id = X)
// Excluir
db.Delete(&User{}, u.ID) // DELETE FROM users WHERE id = X
Ent (Codegen, API fluente)
// (O esquema Ent é definido em outro lugar e o código é gerado. Suponha que o cliente é ent.Client)
// Criar um novo usuário
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
// Ler (por ID)
u2, err := client.User.Get(ctx, u.ID)
// SQL: SELECT * FROM users WHERE id = X
// Atualizar (único 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
// Excluir
err = client.User.DeleteOneID(u.ID).Exec(ctx)
// SQL: DELETE FROM users WHERE id = X
Bun
// Definição do modelo
type User struct {
bun.BaseModel `bun:"table:users"`
ID int64 `bun:",pk,autoincrement"`
Name string `bun:",notnull"`
Email string `bun:",unique,notnull"`
}
// Criar um novo usuário
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')
// Ler (buscar por ID)
var u User
err = db.NewSelect().Model(&u).
Where("id = ?", user.ID).
Scan(ctx)
// SELECT * FROM users WHERE id = X
// Atualizar (único 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
// Excluir
_, err = db.NewDelete().Model((*User)(nil)).
Where("id = ?", u.ID).
Exec(ctx)
// DELETE FROM users WHERE id = X
sqlc
// Suponha que escrevemos SQL em arquivos e sqlc gerou uma estrutura Queries com métodos.
queries := New(db) // New aceita um *sql.DB (ou pgx.Conn) e retorna Queries geradas
// Criar um novo usuário (método gerado executa o INSERT e escaneia o resultado)
newUser, err := queries.CreateUser(ctx, "Alice", "alice@example.com")
// SQL em queries.sql -> INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email
// Ler (por ID)
user, err := queries.GetUser(ctx, newUser.ID)
// SQL -> SELECT id, name, email FROM users WHERE id = $1
// Atualizar (email por 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
// Excluir
err = queries.DeleteUser(ctx, newUser.ID)
// SQL -> DELETE FROM users WHERE id = $1
Como o código acima mostra, cada abordagem tem uma sensação diferente:
-
GORM usa métodos de struct e encadeamento fluente em
db.Model(&obj)
ou diretamente no objetodb
. Ele preenche a struct com os valores retornados (por exemplo, apósCreate
,user.ID
é definido). Ele também esconde detalhes SQL por design — geralmente você não vê a consulta a menos que esteja depurando. -
Ent usa uma API fluente gerada. Observe como métodos como
Create().SetX().Save(ctx)
ouUpdateOneID(id).SetX().Save(ctx)
separam claramente as fases de construção versus execução. O Ent retorna objetos ent.Type (que correspondem a linhas) e erros, semelhante a como uma consulta SQL retornaria resultados ou um erro. -
Bun exige especificar mais explicitamente (por exemplo, usando
Set("email = ?", ...)
para atualizações), o que é muito parecido com escrever SQL, mas com sintaxe Go. Após uma inserção, a structuser
não é automaticamente preenchida com o novo ID a menos que você adicione uma cláusulaRETURNING
(Bun suporta.Returning()
se necessário). O exemplo acima mantém as coisas simples. -
sqlc parece chamar qualquer função Go. Chamamos
queries.CreateUser
, etc., e por baixo dos panos, essas são executando instruções preparadas. O SQL é escrito em arquivos externos, então embora você não o veja no código Go, você tem pleno controle sobre ele. Os objetos retornados (por exemplo,newUser
) são structs Go comuns gerados por sqlc para modelar os dados.
Pode-se observar diferenças de verbosidade (GORM é bastante conciso; Bun e sqlc exigem um pouco mais de digitação no código ou em SQL) e diferenças de estilo (Ent e GORM oferecem abstrações de nível mais alto, enquanto Bun e sqlc estão mais próximos de consultas brutas). Dependendo de suas preferências, você pode preferir a explicitidade sobre a brevidade ou vice-versa.
TL;DR
Escolher a “certa” ORM ou biblioteca de banco de dados em Go depende das necessidades do seu aplicativo e das preferências da sua equipe:
-
GORM é uma excelente escolha se você quiser uma ORM testada e comprovada que lida com muitas coisas por você. Ele brilha no desenvolvimento rápido de aplicações CRUD onde a conveniência e um conjunto rico de recursos são mais importantes do que extrair cada gota de desempenho. O suporte da comunidade e a documentação são excelentes, o que pode suavizar as bordas ásperas da curva de aprendizado. Esteja atento ao usar seus recursos de forma apropriada (por exemplo, use
Preload
ouJoins
para evitar armadilhas de carregamento lento) e espere algum overhead. Em troca, você obtém produtividade e uma solução abrangente para a maioria dos problemas. Se você precisar de coisas como suporte a múltiplos bancos de dados ou um ecossistema de plugins extenso, o GORM tem você coberto. -
Ent atrai aqueles que priorizam segurança de tipo, clareza e manutenibilidade. Ele é adequado para grandes bases de código onde alterações no esquema são frequentes e você quer que o compilador esteja do seu lado para capturar erros. O Ent pode envolver mais design inicial (definir esquemas, executar geração), mas compensa conforme o projeto cresce — seu código permanece robusto e amigável para refatoração. Em termos de desempenho, ele pode lidar com cargas pesadas e consultas complexas de forma eficiente, muitas vezes melhor do que ORMs de estilo active-record, graças à geração otimizada de SQL e cache. O Ent é ligeiramente menos “plug-and-play” para scripts rápidos, mas para serviços de longa duração, ele fornece uma base sólida e escalável. Sua abordagem moderna (e desenvolvimento ativo) tornam-no uma escolha à frente do tempo.
-
Bun é ideal para desenvolvedores que dizem “eu sei SQL e só quero um ajudante leve ao redor dele.” Ele abdica de alguns encantos para lhe dar controle e velocidade. Se você está construindo um serviço sensível ao desempenho e não tem medo de ser explícito no seu código de acesso aos dados, o Bun é uma opção convincente. Ele também é um bom meio-termo se você estiver migrando de
database/sql
+sqlx
e quiser adicionar estrutura sem sacrificar muito eficiência. O trade-off é uma comunidade menor e menos abstrações de alto nível — você escreverá um pouco mais de código à mão. Mas, como dizem os documentos do Bun, ele não se intromete. Use o Bun quando quiser uma ORM que se sinta como usar SQL diretamente, especialmente para projetos centrais em PostgreSQL onde você pode aproveitar recursos específicos do banco de dados. -
sqlc está em uma categoria própria. É perfeito para a equipe Go que diz “não queremos uma ORM, queremos garantias de tempo de compilação para nosso SQL.” Se você tem habilidades fortes em SQL e prefere gerenciar consultas e esquema em SQL (talvez você tenha DBAs ou simplesmente goste de criar SQL eficiente), o sqlc provavelmente aumentará sua produtividade e confiança. O desempenho é essencialmente ótimo, pois nada está entre sua consulta e o banco de dados. As únicas razões para não usar o sqlc seriam se você realmente odiar escrever SQL ou se suas consultas forem tão dinâmicas que escrever muitas variantes se tornar uma tarefa pesada. Mesmo assim, você poderia usar o sqlc para a maioria das consultas estáticas e lidar com os poucos casos dinâmicos com outra abordagem. O sqlc também se dá bem com outros — não exclui o uso de uma ORM em partes do seu projeto (alguns projetos usam GORM para coisas simples e sqlc para os caminhos críticos, por exemplo). Em resumo, escolha o sqlc se valorizar explicitidade, zero overhead e SQL seguro por tipo — é uma ferramenta poderosa para ter em sua caixa de ferramentas.
Finalmente, vale ressaltar que essas ferramentas não são mutuamente exclusivas no ecossistema. Cada uma tem seus prós e contras, e no espírito pragmático do Go, muitas equipes avaliam os trade-offs caso a caso. Não é incomum começar com uma ORM como GORM ou Ent para a velocidade de desenvolvimento, e depois usar sqlc ou Bun para caminhos específicos que precisam de máximo desempenho. Todas as quatro soluções são mantidas ativamente e amplamente usadas, então não há uma “escolha errada” no geral — é sobre a escolha certa para o seu contexto. Espero que esta comparação tenha dado a você uma imagem mais clara de como o GORM, Ent, Bun e sqlc se comparam, e ajude você a tomar uma decisão informada para seu próximo projeto em Go.
Links úteis
- 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/
- Folha de Dicas de Golang
- Corrigindo o erro de GORM AutoMigrate no PostgreSQL
- Reclassificação de documentos de texto com Ollama e modelo de embedding Qwen3 - em Go