ORM a usar em Go: GORM, sqlc, Ent ou Bun?

GORM vs sqlc vs Ent vs Bun

Conteúdo da página

O ecossistema Go oferece uma variedade de ferramentas ORM (Mapeamento Objeto-Relacional) 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 em Go: GORM, sqlc, Ent e Bun.

Vamos avaliá-los em desempenho, experiência do desenvolvedor, popularidade e recursos/extensibilidade, com exemplos de código demonstrando operações CRUD básicas em um modelo User. Desenvolvedores Go intermediários e avançados ganharão insights sobre as compensações de cada ferramenta e qual pode atender melhor às suas necessidades.

tela de laptop com algum código

Visão Geral dos ORMs

  • GORM – Um ORM no estilo Active Record rico em recursos. O GORM permite que você defina structs 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 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 e muito mais), que permite uma base de código Go limpa com SQL bruto mínimo. No entanto, ele introduz seus próprios padrões e tem sobrecarga de tempo de execução devido ao uso de reflexão e 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 o sqlc, você escreve consultas SQL puras (em arquivos .sql) e a ferramenta gera código Go com segurança de tipos (funções DAO) para executar essas consultas. Isso significa que você interage com o banco de dados chamando funções Go geradas (ex. CreateUser, GetUser) e obtém resultados fortemente tipados sem escanear linhas manualmente. O sqlc essencialmente permite que você use 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 rapidamente se tornou popular (~16k estrelas) por sua simplicidade e desempenho: ele evita sobrecarga de tempo de execução inteiramente aproveitando SQL preparado, tornando-o tão rápido quanto usar database/sql diretamente. A compensação é que você deve escrever (e manter) instruções SQL para suas consultas, e a lógica de consulta dinâmica pode ser menos direta em comparação com ORMs que constroem SQL para você.

  • Ent (Entgo) – Um ORM baseado em código que usa geração de código para criar uma API com segurança de tipos para seu modelo de dados. Você define seu esquema em Go (usando a DSL do Ent para campos, arestas/relacionamentos, restrições, etc.), e o Ent gera pacotes Go para os modelos e métodos de consulta. O código gerado usa uma API fluida para construir consultas, com verificação de tipos completa em tempo de compilação. O Ent é relativamente mais novo (apoiado pela Linux Foundation e desenvolvido pela Ariga). Ele foca na experiência do desenvolvedor e correção – as 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 resulta em consultas SQL altamente otimizadas e até mesmo cacheia resultados de consulta para reduzir viagens duplicadas ao banco de dados. Foi projetado com escalabilidade em mente, então lida com esquemas complexos e relacionamentos de forma eficiente. No entanto, usar o Ent adiciona uma etapa de build (executando codegen) e o vincula ao seu ecossistema. Atualmente 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 mais antiga go-pg, visando ser leve e rápido com foco nos recursos do PostgreSQL. Em vez de um padrão Active Record, o Bun fornece um construtor de consultas fluido: você inicia uma consulta com db.NewSelect() / NewInsert() / etc., e a constrói com métodos que se assemelham muito ao SQL (você pode até incorporar fragmentos SQL brutos). Ele mapeia resultados de consulta para structs Go usando tags de struct semelhantes ao do GORM. O Bun favorece a explícito e permite recorrer ao SQL bruto facilmente quando necessário. Ele não executa automaticamente migrações para você (você escreve migrações ou usa o pacote migrate do Bun manualmente), o que alguns consideram uma vantagem para controle. O Bun é elogiado por lidar com consultas complexas elegantemente e suportar tipos avançados do Postgres (arrays, JSON, etc.) nativamente. Na prática, o Bun impõe muito menos sobrecarga de tempo de execução do que o GORM (sem 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 de SQL para usar efetivamente, mas recompensa isso com desempenho e flexibilidade.

Benchmarks de Desempenho

Quando se trata de desempenho (latência de consulta e throughput), existem diferenças significativas em como essas ferramentas se comportam, especialmente conforme a carga aumenta. Benchmarks recentes e experiências de usuários destacam alguns pontos-chave:

  • GORM: A conveniência do GORM vem com um custo de 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 (ex. buscar 1 ou 10 registros). No entanto, conforme o número de registros cresce, a sobrecarga do GORM faz com que ele fique significativamente atrás. Em um teste buscando 15.000 linhas, o GORM foi aproximadamente 2× mais lento que o sqlc ou até mesmo o database/sql bruto (59,3 ms para o GORM vs ~31,7 ms para o sqlc e 32,0 ms para database/sql nesse cenário). O design pesado em reflexão e abstrações de construção de consulta adicionam latência, e o GORM pode emitir múltiplas consultas para cargas complexas (ex. uma consulta por entidade relacionada ao usar Preload por padrão), o que pode amplificar o atraso. Em resumo: o GORM geralmente é bom para cargas moderadas, mas em cenários de alto throughput ou operações em lote grandes, sua sobrecarga pode se tornar um gargalo.

  • sqlc: Porque as chamadas do sqlc são essencialmente SQL escrito à mão por baixo, seu desempenho está no mesmo nível de usar a biblioteca padrão diretamente. Benchmarks consistentemente colocam o sqlc entre as opções mais rápidas, frequentemente apenas marginalmente mais lento ou até ligeiramente mais rápido que o database/sql bruto para operações comparáveis. Por exemplo, em 10k+ registros, o sqlc foi observado a superar ligeiramente o database/sql puro em throughput (provavelmente devido à evitação de algumas boilerplate de escaneamento e uso de codegen eficiente). Com o sqlc, não há construtor de consulta em tempo de execução – sua consulta é executada como uma instrução preparada com sobrecarga mínima. O segredo é que você já fez o trabalho de escrever uma consulta SQL ótima; o sqlc simplesmente poupa o esforço de escanear linhas para tipos Go. Na prática, isso significa escalabilidade excelente – o sqlc lidará com cargas de dados grandes tão bem quanto o SQL bruto faria, tornando-o uma escolha de topo quando a velocidade bruta é crítica.

  • Ent: O desempenho do Ent fica em algum lugar entre abordagens de SQL bruto e ORMs tradicionais. Porque o Ent gera código com segurança de tipos, ele evita reflexão e a maioria dos custos de composição de consulta 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 de cache interna para reutilizar planos de execução/resultados de consulta em alguns casos. Isso pode reduzir hits repetitivos no banco de dados em fluxos de trabalho complexos. Embora benchmarks específicos do Ent vs outros variem, muitos desenvolvedores relatam que o Ent supera o GORM para operações equivalentes, especialmente conforme a complexidade cresce. Uma razão é que o GORM pode gerar consultas subótimas (ex., seleções N+1 se não usar joins/preloads cuidadosamente), enquanto o Ent incentiva o carregamento antecipado explícito de relacionamentos e juntará dados em menos consultas. O Ent também tende a alocar menos memória por operação que o GORM, o que pode melhorar o throughput exercendo menos pressão no coletor de lixo do Go. No geral, o Ent é 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 igualar o throughput bruto de SQL escrito à mão (sqlc) em todos os cenários. É um concorrente forte se você quer velocidade e 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 fluida para construir consultas SQL, mas esses construtores são leves. O Bun não esconde o SQL de você – é mais uma camada fina sobre o database/sql, o que significa que você incorre em muito pouca sobrecarga além do que a biblioteca padrão do Go faz. Usuários notaram melhorias significativas ao trocar do GORM para o Bun em grandes projetos: por exemplo, um relatório mencionou que o GORM era “super lento” para sua escala, e substituí-lo pelo Bun (e algum 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 (frequentemente dentro de 1,5× do sqlx/sql bruto) e bem à frente do GORM em throughput. Ele também suporta inserts em lote, controle manual de joins vs consultas separadas e outras otimizações que os desenvolvedores podem aproveitar. Conclusão: o Bun entrega desempenho próximo ao hardware nu, e é uma ótima escolha quando você quer conveniência de ORM mas não pode sacrificar velocidade. Sua natureza SQL-first garante que você sempre possa otimizar consultas se as geradas não forem ótimas.

Resumo de Desempenho: Se o desempenho máximo é o objetivo (ex. aplicações intensivas em dados, microsserviços sob alta carga), sqlc ou até chamadas database/sql escritas à mão são os vencedores. Eles têm virtualmente 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 equilibram velocidade e abstração. GORM oferece os mais recursos, mas com o custo de sobrecarga; é perfeitamente aceitável para muitas aplicações, mas você deve estar atento ao seu impacto se espera lidar com volumes de dados enormes 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 para 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 o quão produtivo você pode ser no dia a dia de codificação. Veja como nossos quatro concorrentes se comparam em termos de facilidade de uso e fluxo de desenvolvimento:

  • GORM – Rico em recursos, mas com Curva de Aprendizado: O GORM é frequentemente o primeiro ORM que novos desenvolvedores Go tentam porque promete uma experiência totalmente automatizada (você define structs e pode criar/encontrar imediatamente sem escrever SQL). A documentação é muito abrangente com muitos guias, o que ajuda. No entanto, o GORM tem sua própria maneira idiomática de fazer as coisas (“abordagem baseada em código” para interações de DB) e pode parecer desajeitado inicialmente se você estiver acostumado ao SQL bruto. Muitas operações comuns são diretas (ex. db.Find(&objs)), mas quando você se aventura em associações, relacionamentos polimórficos ou consultas avançadas, você precisa aprender as convenções do GORM (tags de struct, Preload vs Joins, etc.). É por isso que frequentemente se menciona uma curva de aprendizado inicial íngreme. Uma vez que você supera essa curva, o GORM pode ser muito produtivo – você gasta menos tempo escrevendo SQL repetitivo e mais tempo em lógica Go. De fato, após dominá-lo, muitos descobrem que podem desenvolver recursos mais rápido com o GORM do que com bibliotecas de nível inferior. Em suma, a DX do GORM: alta complexidade inicial, mas suaviza com experiência. A grande comunidade significa que há muitos exemplos e respostas no StackOverflow disponíveis, e um rico ecossistema de plugins pode simplificar ainda mais tarefas (por exemplo, plugins para soft deletes, auditoria, etc.). A principal ressalva é que você deve permanecer consciente do que o GORM está fazendo por baixo dos panos para evitar armadilhas (como consultas N+1 acidentais). Mas para um desenvolvedor que investe tempo no GORM, ele realmente “parece” uma solução poderosa e integrada que cuida de muito por você.

  • Ent – Schema-First e com Segurança de Tipos: O Ent foi explicitamente projetado para melhorar a experiência do desenvolvedor de ORMs. Você descreve seu esquema em um lugar (código Go) e obtém uma API fortemente tipada para trabalhar com – isso significa sem consultas com tipagem de string, 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á. Essa rede de segurança é uma grande vitória de DX para grandes projetos. A API do Ent para construir consultas é fluida e intuitiva: você encadeia métodos como .Where(user.EmailEQ("alice@example.com")) e lê quase como inglês. Desenvolvedores vindos de ORMs em outras línguas frequentemente acham a abordagem do Ent natural (é algo como Entity Framework ou Prisma, mas em Go). Aprender o Ent exige entender seu fluxo de geração de código. Você precisará executar entc generate (ou usar go generate) sempre que modificar seu esquema. Isso é uma etapa extra, mas geralmente faz parte do processo de build. A curva de aprendizado do Ent é moderada – se você conhece Go e alguns fundamentos de SQL, os conceitos do Ent (Campos, Arestas, etc.) são diretos. De fato, muitos acham o Ent mais fácil de raciocinar que o GORM, porque você não precisa se perguntar que SQL está sendo produzido; você pode frequentemente prevê-lo pelos nomes dos métodos, e pode outputar a consulta para depuração se necessário. A documentação e exemplos do Ent são bastante bons, e ser apoiado por uma empresa garante que esteja ativamente mantido. DX geral com Ent: muito amigável para quem quer clareza e segurança. O código que você escreve é verboso em comparação com SQL bruto, mas é auto-documentado. Além disso, refatoração é mais segura (renomear um campo no esquema e regenerar atualizará todos os usos nas consultas). A desvantagem pode ser que você esteja um pouco limitado pelo que o codegen do Ent fornece – se você precisar de uma consulta muito customizada, pode escrevê-la com a API do Ent (que pode lidar com joins complexos, mas ocasionalmente você pode encontrar um caso que seja mais fácil de escrever em SQL bruto). O Ent permite fragmentos SQL brutos quando necessário, mas se você se pegar fazendo isso frequentemente, pode questionar se um ORM é o ajuste certo. Em resumo, o Ent oferece uma experiência de desenvolvedor suave para a maioria da lógica CRUD e de consulta com mínimas surpresas, desde que você esteja bem em executar uma etapa de codegen e seguir os padrões que o Ent impõe.

  • Bun – Mais próximo do SQL, menos Magia: Usar o Bun sente diferente de usar o GORM ou Ent. A filosofia do Bun é não esconder o SQL — se você conhece SQL, você 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 fluida 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 o SQL em si. Há menos “magia de ORM” para aprender; você basicamente aprende 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 usaram database/sql ou outros construtores de consulta como sqlx. Em contraste, um iniciante que não está confiante em escrever SQL pode achar o Bun um pouco menos útil que o GORM – o Bun não gerará automaticamente consultas complexas para você via relacionamentos a menos que você os especifique. No entanto, o Bun suporta relacionamentos e carregamento antecipado (método Relation() para juntar tabelas) – ele apenas faz isso explicitamente, 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 que com o GORM para algumas operações (porque o Bun frequentemente pede para você especificar explicitamente colunas ou joins), mas em troca você tem mais controle e visibilidade. Há magia interna mínima, então depuração é mais fácil (você geralmente pode logar a string de consulta que o Bun construiu). A documentação do Bun (o guia uptrace.dev) é completa e inclui padrões para migrações, transações, etc., embora a comunidade sendo menor signifique menos tutoriais de terceiros. Outro aspecto de DX é a ferramenta disponível: o Bun sendo apenas uma extensão de database/sql significa que qualquer ferramenta que funcione com sql.DB (como proxies de depuração, loggers de consulta) funciona com o Bun facilmente. 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 desenvolvedores menos experientes tanto quanto algo como GORM. O consenso é que o Bun torna coisas simples fáceis e coisas complexas possíveis (muito como SQL bruto), sem impor muita estrutura de framework em você.

  • sqlc – Escreva SQL, Obtenha Código Go: A abordagem do sqlc inverte a narrativa usual de 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ê obtém usar todo o poder do SQL (joins complexos, CTEs, funções de janela, o que você quiser) com zero limitações de ORM. A curva de aprendizado do próprio sqlc é muito pequena. Se você sabe como escrever um SELECT/INSERT/UPDATE em SQL, você já fez a parte difícil. Você precisa aprender como anotar consultas com comentários -- name: Nome :one/many/exec e configurar o arquivo de configuração, mas isso é trivial. O código gerado serão definições de função diretas, que você chama como qualquer função Go. Prós de experiência do desenvolvedor: não há API de ORM para aprender, nenhuma surpresa – as consultas rodem exatamente como escritas. Você evita toda uma classe de problemas de ORMs (como descobrir por que um ORM gerou um JOIN específico ou como ajustar uma consulta). Além disso, suas revisões de código podem incluir revisar o próprio SQL, o que frequentemente é mais claro para lógica complexa. Outra grande vantagem de DX: segurança de tipos e suporte de IDE – os métodos e structs gerados podem ser saltados em seu editor, ferramentas de refatoração funcionam neles, etc., ao contrário de consultas de string bruta que são opacas para IDEs. No lado con, o sqlc exige que você gerencie scripts SQL. Isso significa que se seu esquema ou requisitos mudarem, você deve atualizar manualmente ou adicionar o SQL relevante e re-executar o codegen. Não é difícil, mas é mais trabalho manual que apenas chamar um método de ORM. Além disso, consultas dinâmicas (onde partes do SQL são condicionais) podem ser trabalhosas – você ou escreve múltiplas variantes SQL ou usa truques de sintaxe SQL. Alguns desenvolvedores mencionam isso como uma limitação da abordagem do sqlc. Na prática, você frequentemente pode estruturar seu acesso a dados tal que não precisa de SQL excessivamente dinâmico, ou pode chamar database/sql bruto para esses casos de borda. Mas é uma consideração: o sqlc é excelente para consultas bem definidas, menos para construção de consultas ad-hoc. Em resumo, para um desenvolvedor proficiente em SQL, usar sqlc sente natural e altamente eficiente. Há muito pouco para aprender, e remove boilerplate Go repetitivo. Para um desenvolvedor não tão confortável com SQL, o sqlc pode ser inicialmente mais lento para trabalhar (em comparação com um ORM que, por exemplo, gera automaticamente consultas para CRUD básico). Ainda assim, muitos desenvolvedores Go consideram o sqlc obrigatório porque acerta o ponto ideal: controle manual com alta segurança e sem custo de tempo de execução.

Popularidade e Suporte de Ecossistema

Adoção e suporte da comunidade podem influenciar sua escolha – uma biblioteca popular significa mais contribuições da comunidade, melhor manutenção e mais recursos para aprender.

  • GORM: Como o mais velho e maduro dos quatro, o GORM tem de longe a maior base de usuários e ecossistema. É atualmente o ORM Go mais estrelado no GitHub (mais de 38k estrelas) e é usado em inúmeros projetos em produção. Os mantenedores são ativos, e o projeto atualiza regularmente (GORM v2 foi uma grande reforma melhorando desempenho e arquitetura). Uma grande vantagem da popularidade do GORM é a riqueza de extensões e integrações. Há plugins oficiais e de terceiros para coisas como drivers de banco de dados (ex. para PostgreSQL, MySQL, SQLite, SQL Server, ClickHouse), nativos. O GORM também suporta um amplo conjunto de casos de uso: migrações, geração automática de esquema, soft deletes, campos JSON (com gorm.io/datatypes), pesquisa de texto completo, etc., frequentemente via funcionalidade nativa ou ad-ons. A comunidade produziu várias ferramentas como gormt (para gerar definições de struct de um banco de dados existente), e muitos tutoriais e exemplos. Se você tem um problema, uma busca rápida provavelmente encontrará uma questão ou pergunta no Stack Overflow feita por alguém. Resumo de ecossistema: O GORM é extremamente bem suportado. É a escolha de ORM “padrão” para muitos, significando que você o encontrará em frameworks e boilerplates. O lado negativo é que seu tamanho muito grande pode fazê-lo parecer pesado, mas para muitos isso é uma compensação valiosa pela profundidade da comunidade.

  • Ent: Apesar de ser mais novo (open-sourced por volta de 2019), o Ent cresceu uma comunidade forte. Com ~16k estrelas e apoio da Linux Foundation, não é um projeto marginal. Grandes empresas começaram a usar o Ent por suas vantagens centradas em esquema, e os mantenedores (na Ariga) fornecem suporte empresarial, que é um sinal de confiança para uso crítico para negócios. O ecossistema ao redor do Ent está crescendo: há extensões 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 Ent. Porque o Ent gera código, alguns padrões de ecossistema diferem – por exemplo, se você quer integrar com GraphQL, pode usar a geração de código do Ent para produzir resolvers GraphQL. Os recursos de aprendizado para o Ent são bons (documentação oficial e um projeto de exemplo cobrindo um app simples). Os fóruns da comunidade e discussões no GitHub são ativos com perguntas de design de esquema e dicas. Em termos de suporte da comunidade, o Ent certamente passou da fase de “adoção precoce” e é considerado pronto para produção e confiável. Pode não ter tantas respostas no Stack Overflow quanto o GORM ainda, mas está pegando rápido. Uma coisa a notar: como o Ent é um pouco mais opinativo (ex., ele quer gerenciar o esquema), você provavelmente usará sozinho em vez de ao lado de outro ORM. Ele não tem “plugins” da mesma forma que o GORM, mas você pode escrever templates customizados para estender a geração de código ou ganchos em eventos de ciclo de vida (o Ent tem suporte a hooks/middleware para clientes gerados). O apoio por uma fundação indica suporte de longo prazo, então escolher o Ent é uma aposta segura se seu modelo se encaixar em suas necessidades.

  • Bun: O Bun (parte da suíte open-source Uptrace) está ganhando tração, especialmente entre aqueles que eram fãs da biblioteca agora descontinuada go-pg. Com ~4,3k estrelas, é a menor comunidade nesta comparação, mas é um projeto muito ativo. O mantenedor é responsivo e tem adicionado recursos rapidamente. A comunidade do Bun é entusiasta sobre seu desempenho. Você encontrará discussões em fóros Go e Reddit de desenvolvedores que trocaram para o Bun por velocidade. No entanto, porque a base de usuários é menor, você pode não sempre encontrar respostas prontas para perguntas de nicho – à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 se conecta às ferramentas de observabilidade Uptrace, mas você não encontrará a plethora de plugins que o GORM tem. Diz-se que o Bun é compatível com o uso de sql.DB, então você pode misturar e combinar – por exemplo, usando github.com/jackc/pgx como driver por baixo ou integrando com outros pacotes que esperam um *sql.DB. O Bun não te trava. Em termos de suporte, ser mais novo significa que a documentação é atualizada e exemplos são modernos (frequentemente mostrando uso com context, etc.). A documentação oficial compara o Bun com GORM e Ent diretamente, o que é útil. Se o tamanho da comunidade é uma preocupação, uma estratégia poderia ser adotar o Bun por suas vantagens mas manter seu uso relativamente superficial (ex., você poderia trocá-lo por outra solução se necessário, pois não impõe uma abstração pesada). De qualquer forma, a trajetória do Bun é ascendente, e ele preenche um nicho específico (ORM orientado a desempenho) que lhe dá poder de permanência.

  • sqlc: O sqlc é bastante popular na comunidade Go, evidenciado por ~15,9k estrelas e muitos defensores especialmente em círculos conscientes de desempenho. É frequentemente recomendado em discussões sobre “evitar ORMs” porque acerta um equilíbrio bom. A ferramenta é mantida por contribuintes (incluindo o autor original, que está ativo em melhorá-la). Sendo mais um compilador que uma biblioteca de tempo de execução, seu ecossistema gira em torno de integrações: por exemplo, editores/IDEs podem ter realce de sintaxe para arquivos .sql e você executa sqlc generate como parte de seu build ou pipeline de CI. A comunidade criou templates e exemplos de repositórios base sobre como organizar código com sqlc (frequentemente emparelhando com uma ferramenta de migração como Flyway ou Golang-Migrate para versionamento de esquema, pois o sqlc em si não gerencia esquema). Há um Slack/Discord oficial para sqlc onde você pode fazer perguntas, e issues no GitHub tendem a receber atenção. Muitos dos padrões comuns (como lidar com valores nulos ou campos JSON) estão documentados na documentação do sqlc ou em posts de blog da comunidade. Uma coisa a destacar: o sqlc não é específico do Go – pode gerar código em outras línguas (como TypeScript, Python). Isso amplia sua comunidade além do Go, mas especificamente no Go, é amplamente respeitado. Se você escolher sqlc, estará em boa companhia: é usado em produção por muitas startups e grandes firmas (de acordo com showcases da comunidade e patrocinadores listados no repo). A consideração de ecossistema chave é que como o 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 possa usar sql.Tx facilmente com sqlc) ou talvez um wrapper DAL leve. Na prática, a maioria usa sqlc junto com a biblioteca padrão (para transações, cancelamento de contexto, etc., você usa idioms database/sql diretamente em seu código). Isso significa menos lock-in de fornecedor – sair do sqlc significaria apenas 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 como “obrigatório” para projetos Go interagindo 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 quão extensíveis são para casos de uso avançados:

  • Recursos do GORM: O GORM visa ser um ORM completo. Sua lista de recursos é extensa:
  • Múltiplos Bancos de Dados: Suporte de primeira classe para PostgreSQL, MySQL, SQLite, SQL Server e mais. Trocar DBs é geralmente tão fácil quanto mudar o driver de conexão.
  • Migrações: Você pode auto-migrar seu esquema de seus modelos (db.AutoMigrate(&User{}) criará ou alterará a tabela users). Embora conveniente, tenha cautela usando auto-migração em produção – muitos usam para dev e têm migrações mais controladas para prod.
  • 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 tem Preload para carregamento antecipado de relacionamentos. O GORM padrão para carregamento preguiçoso (ou seja, consultas separadas) quando você acessa campos relacionados, mas pode usar Preload ou Joins para customizar isso. Versões mais novas também têm uma API baseada em genéricos para consultas de associação mais fáceis.
  • Transações: O GORM tem um wrapper de transação fácil de usar (db.Transaction(func(tx *gorm.DB) error { ... })) e até suporte para transações aninhadas (savepoints).
  • Ganchos 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 soft-delete.
  • Extensibilidade: O sistema de plugins do GORM (interface gorm.Plugin) permite estender sua funcionalidade. Exemplos: o pacote gorm-gen gera métodos de consulta com segurança de tipos (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 ao SQL bruto a qualquer momento via db.Raw("SELECT ...", params).Scan(&result).
  • Outras gentilezas: O GORM suporta chaves primárias compostas, embedding de modelos, associações polimórficas e até um resolvedor de esquema para usar múltiplos bancos de dados (para réplicas de leitura, sharding, etc.).

No geral, o GORM é altamente extensível e rico em recursos. Virtualmente qualquer recurso relacionado a ORM que você possa querer tem ou um mecanismo nativo ou um padrão documentado no GORM. O custo dessa amplitude é complexidade e alguma rigidez (você frequentemente precisa se conformar à maneira do GORM de fazer as coisas). Mas se você precisa de uma solução one-stop, o GORM entrega.

  • Recursos do Ent: A filosofia do Ent é centrada em schema como código. Recursos chave incluem:
  • Definição de Esquema: Você pode definir campos com restrições (único, valores padrão, enums, etc.), e arestas (relacionamentos) 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ê (há um componente ent/migrate que pode produzir SQL de diferença entre seu esquema atual e o desejado).
  • Segurança de Tipos e Validação: Porque campos são fortemente tipados, você não pode, por exemplo, acidentalmente definir um campo inteiro como string. O Ent também permite tipos de campo customizados (ex., você pode integrar com sql.Scanner/driver.Valuer para campos JSONB ou outros tipos complexos).
  • Construtor de Consulta: A API de consulta gerada do Ent cobre a maioria dos construtos SQL: você pode fazer selects, filtros, ordenação, limites, agregados, joins através de arestas e até sub-consultas. É expressivo – ex., 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, tudo de uma vez.
  • Transações: O cliente do Ent suporta transações expondo uma variante transacional do cliente (via tx, err := client.Tx(ctx) que produz um ent.Tx que você pode usar para fazer múltiplas operações e depois commitar ou rollback).
  • Ganchos e Middleware: O Ent permite registrar ganchos em operações de criar/atualizar/excluir – estes são como interceptadores onde você pode, por exemplo, preencher automaticamente um campo ou impor regras customizadas. Há também middleware para o cliente para envolver operações (útil para logging, 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 customizados se precisar estender o código gerado. Muitos recursos avançados foram implementados assim: por exemplo, integração com OpenTelemetry para rastrear chamadas de DB, ou gerar resolvers GraphQL do esquema 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 camadas seus próprios padrões em cima, se necessário.
  • Integração GraphQL/REST: Um grande ponto de venda da abordagem baseada em grafo do Ent é que se encaixa bem com GraphQL – seu esquema ent pode ser quase diretamente mapeado para um esquema GraphQL. Ferramentas existem para automatizar muito disso. Isso pode ser uma vitória de produtividade se você estiver construindo um servidor de API.
  • Ajustes de desempenho: O Ent, por exemplo, pode carregar arestas relacionadas em lote para evitar consultas N+1 (ele tem uma API de carregamento antecipado .With<NomeAresta>()). Ele usará JOINs ou consultas adicionais por baixo de uma maneira otimizada. Além disso, o cache de resultados de consulta (em memória) pode ser habilitado para evitar hits no DB para consultas idênticas em um curto espaço.

Em resumo, o conjunto de recursos do Ent é voltado para projetos de grande escala e mantíveis. Pode faltar alguns dos bons do GORM fora da caixa (por exemplo, migração automática de esquema do GORM vs scripts de migração gerados do Ent – o Ent escolhe separar essa preocupação), mas compensa com ferramentas de desenvolvedor poderosas e segurança de tipos. Extensibilidade no Ent é sobre gerar o que você precisa – é muito flexível se você estiver disposto a mergulhar em como o entc (o codegen) funciona.

  • Recursos do Bun: O Bun foca em ser uma ferramenta poderosa para aqueles que conhecem SQL. Alguns recursos e pontos de extensibilidade:
  • Construtor de Consulta: No núcleo do Bun está o construtor de consulta fluido que suporta a maioria dos construtos SQL (SELECT com joins, CTEs, sub-consultas, além de INSERT, UPDATE com suporte em lote). Você pode iniciar uma consulta e customizá-la profundamente, até injetando SQL bruto (.Where("alguma_condição (?)", valor)).
  • Recursos específicos do PostgreSQL: O Bun tem suporte nativo para tipos de array do Postgres, JSON/JSONB (mapeando para []<tipo> ou map[string]interface{} ou tipos customizados), e suporta escaneamento para tipos compostos. Por exemplo, se você tem uma coluna JSONB, pode mapeá-la para um struct Go com tags json:, e o Bun cuidará do marshalling para você. Isso é uma força sobre alguns ORMs que tratam JSONB como opaco.
  • Relacionamentos/Carregamento Antecipado: Como mostrado anteriormente, o Bun permite que você defina tags de struct para relacionamentos e então pode usar .Relation("NomeCampo") em consultas para juntar e carregar entidades relacionadas. Por padrão, .Relation usa LEFT JOIN (você pode simular joins internos ou outros tipos adicionando condições). Isso lhe dá controle fino sobre como dados relacionados são buscados (em uma consulta vs múltiplas). Se você preferir consultas manuais, pode sempre escrever um join diretamente no construtor SQL do Bun. Diferente do GORM, o Bun nunca carregará relacionamentos automaticamente sem você pedir, o que evita problemas inadvertidos de N+1.
  • Migrações: O Bun fornece uma ferramenta de migração separada (em github.com/uptrace/bun/migrate). Migrações são definidas em Go (ou strings SQL) e versionadas. Não é tão automágico quanto o auto-migrate do GORM, mas é robusto e integra-se bem com a biblioteca. Isso é ótimo para manter mudanças de esquema explícitas.
  • Extensibilidade: O Bun é construído em interfaces semelhantes ao database/sql – ele até envolve um *sql.DB. Você pode portanto usar qualquer ferramenta de nível inferior com ele (ex., você poderia executar uma consulta *pgx.Conn se necessário e ainda escanear para modelos Bun). O Bun permite scanners de valor customizados, então se você tem um tipo complexo que quer armazenar, implementa sql.Scanner/driver.Valuer e o Bun usará. Para estender a funcionalidade do Bun, como é relativamente simples, muitas pessoas apenas escrevem funções auxiliares adicionais em cima da API do Bun em seus projetos em vez de plugins distintos. A biblioteca em si está evoluindo, então novos recursos (como auxiliares de consulta adicionais 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 via database/sql por baixo (configurável lá), e suporta cache de instruções preparadas (através do driver se usando pgx). Há também um recurso legal onde o Bun pode escanear para ponteiros de struct ou slices primitivos facilmente (por exemplo, selecionando uma coluna para um []string diretamente). São pequenas conveniências como essas que tornam o Bun agradável para aqueles que odeiam código de escaneamento repetitivo.

Em resumo, o conjunto de recursos do Bun cobre 90% das necessidades de ORM mas com ênfase em transparência e desempenho. Pode não ter todos os sinos e assobios (por exemplo, não faz atualizações de esquema automágicas ou tem um padrão active record), mas fornece os blocos de construção para implementar o que você precisar em cima. Porque é jovem, espere que seu conjunto de recursos continue a expandir, guiado por casos de uso do mundo real (os mantenedores frequentemente adicionam recursos conforme usuários pedem).

  • Recursos do sqlc: Os “recursos” do sqlc são bastante diferentes porque é um gerador:
  • Suporte SQL completo: O maior recurso é simplesmente que você está usando os próprios recursos do PostgreSQL diretamente. Se o Postgres suportar, você pode usar no sqlc. Isso inclui CTEs, funções de janela, operadores JSON, consultas espaciais (PostGIS), etc. Não há necessidade da própria biblioteca implementar nada especial para usar esses.
  • Mapeamentos de Tipo: O sqlc é inteligente sobre mapear tipos SQL para tipos Go. Ele lida com tipos padrão e permite configurar ou estender mapeamentos para tipos customizados ou enums. Por exemplo, um UUID do Postgres pode mapear para um tipo github.com/google/uuid se quiser, ou um tipo de domínio pode mapear para o tipo Go subjacente.
  • Tratamento de Nulos: Pode gerar sql.NullString ou ponteiros para colunas nulas, dependendo de sua preferência, então você não precisa lutar com escaneamento de nulos.
  • Operações em Lote: Embora o sqlc em si não forneça uma API de alto nível para loteamento, você certamente pode escrever um SQL de insert em lote e gerar código para ele. Ou chamar um procedimento armazenado – isso é outro recurso: como é SQL, você pode aproveitar procedimentos armazenados ou funções de DB, e ter o sqlc gerar um wrapper Go.
  • Consultas Multi-instrução: Você pode colocar múltiplas instruções SQL em uma consulta nomeada e o sqlc as executará em uma transação (se o driver suportar), retornando resultados da última consulta ou o que você especificar. Isso é uma maneira de, digamos, fazer algo como “criar e depois selecionar” em uma chamada.
  • Extensibilidade: Sendo um compilador, extensibilidade vem na forma de plugins para novas línguas ou contribuições da comunidade para suportar novos construtos SQL. Por exemplo, se um novo tipo de dado PostgreSQL sair, o sqlc pode ser atualizado para suportar mapeamento. Em sua aplicação, você pode estender ao redor do sqlc escrevendo funções wrapper. Como o código gerado está sob seu controle, você poderia modificá-lo – embora normalmente não faria, você ajustaria o SQL e re-generaria. Se necessário, você sempre pode misturar chamadas database/sql brutas com sqlc em sua base de código (não há conflito, pois o sqlc apenas outputa algum código Go).
  • Dependências de tempo de execução mínimas: O código que o sqlc gera tipicamente depende apenas do database/sql padrão (e um driver específico como pgx). Não há biblioteca de tempo de execução pesada; são apenas alguns tipos auxiliares. Isso significa sobrecarga zero em produção do lado da biblioteca – todo o trabalho é em tempo de compilação. Também significa que você não obterá recursos como cache de objetos ou mapa de identidade (como alguns ORMs têm) – se precisar de cache, implementaria isso em sua camada de serviço ou usaria uma biblioteca separada.

Em efeito, o “recurso” do sqlc é que ele reduz a lacuna entre seu banco de dados SQL e seu código Go sem adicionar coisas extras no meio. É uma ferramenta especializada – não faz migrações, não rastreia estado de objeto, etc., por design. Essas preocupações são deixadas para outras ferramentas ou para o desenvolvedor. Isso é atraente se você quer uma camada de dados lean, mas se você está procurando uma solução one-and-done que lida com tudo de esquema a consultas a relacionamentos em código, o sqlc sozinho não é – você o emparelharia com outros padrões ou ferramentas.

Para recapitular esta seção, GORM é o centro de recursos e uma escolha clara se você precisa de um ORM maduro e extensível que pode ser adaptado via plugins. Ent oferece uma abordagem moderna e com segurança de tipos aos recursos, priorizando correção e mantibilidade (com coisas como codegen, ganchos e integrações em camadas de API). Bun fornece o essencial e aposta na proficiência em SQL, tornando fácil estender escrevendo mais SQL ou wrappers ligeiros. sqlc reduz os recursos ao hardware nu – é essencialmente tão rico em recursos quanto o SQL em si, mas tudo além disso (cache, etc.) depende de você camadas.

Exemplo de Código: Operações CRUD em Cada ORM

Nada ilustra melhor as diferenças do que ver como cada ferramenta lida com CRUD básico para um modelo simples. Vamos assumir que temos um modelo User com campos ID, Name e Email. Abaixo estão snippets 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, assumimos que temos uma conexão de banco de dados estabelecida (ex., db ou client) já configurada, e o tratamento de erro é omitido por brevidade. O objetivo é comparar a API e 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 (encontrar por chave primária)
    var u User
    db.First(&u, user.ID)                // SELECT * FROM users WHERE id = X LIMIT 1

    // Atualizar (única coluna)
    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 fluida)

    // (Esquema Ent é definido em outro lugar e código gerado. Assuma client é 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 (encontrar por ID)
    var u User
    err = db.NewSelect().Model(&u).
          Where("id = ?", user.ID).
          Scan(ctx)
    // SELECT * FROM users WHERE id = X

    // Atualizar (única coluna)
    _, 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


    // Assuma que escrevemos SQL em arquivos e sqlc gerou um struct Queries com métodos.
    queries := New(db)  // New toma um *sql.DB (ou pgx.Conn) e retorna Queries gerados

    // 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 fluido em db.Model(&obj) ou diretamente no objeto db. Ele preenche o struct com valores retornados (ex., após Create, user.ID é definido). Ele também esconde detalhes SQL por design – você geralmente não vê a consulta a menos que esteja depurando.

  • Ent usa uma API fluida gerada. Note como métodos como Create().SetX().Save(ctx) ou UpdateOneID(id).SetX().Save(ctx) separam claramente as fases de build vs execução. O Ent retorna objetos ent.Type (que correspondem a linhas) e erros, similar a como uma consulta SQL retornaria resultados ou um erro.

  • Bun exige especificar mais explicitamente (ex., usando Set("email = ?", ...) para atualizações), o que é muito parecido com escrever SQL mas com sintaxe Go. Após um insert, o struct user não é automaticamente preenchido com o novo ID a menos que você adicione uma cláusula RETURNING (o 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 eles estã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 controle total sobre ele. Os objetos retornados (ex., newUser) são structs Go puros gerados pelo sqlc para modelar os dados.

Pode-se observar diferenças de verbosidade (GORM é bastante conciso; Bun e sqlc requerem um pouco mais de digitação em código ou 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 favorecer explícito sobre brevidade ou vice-versa.


TL;DR

Escolher o “certo” ORM ou biblioteca de banco de dados em Go depende das necessidades da sua aplicação e preferências da sua equipe:

  • GORM é uma ótima escolha se você quer um ORM testado e comprovado que cuida de muito por você. Brilha no desenvolvimento rápido de aplicações CRUD onde conveniência e um rico conjunto de recursos são mais importantes que extrair cada gota de desempenho. O suporte da comunidade e documentação são excelentes, o que pode suavizar as arestas ásperas de sua curva de aprendizado. Esteja atento para usar seus recursos apropriadamente (ex., use Preload ou Joins para evitar armadilhas de carregamento preguiçoso) e espere alguma sobrecarga. Em troca, você obtém produtividade e uma solução one-stop para a maioria dos problemas. Se você precisa de coisas como suporte a múltiplos bancos de dados ou um ecossistema extenso de plugins, o GORM cobre você.

  • Ent atrai aqueles que priorizam segurança de tipos, clareza e mantibilidade. É bem adequado para grandes bases de código onde mudanças de esquema são frequentes e você quer o compilador ao seu lado para capturar erros. O Ent pode envolver mais design inicial (definindo esquemas, executando geração), mas paga off conforme o projeto cresce – seu código permanece robusto e amigável à refatoração. Em termos de desempenho, pode lidar com cargas pesadas e consultas complexas de forma eficiente, frequentemente melhor que ORMs active-record, graças à sua geração de SQL otimizada e cache. O Ent é um pouco menos “plug-and-play” para scripts rápidos, mas para serviços de longa vida fornece uma base sólida e escalável. Sua abordagem moderna (e desenvolvimento ativo) o torna uma escolha futurista.

  • Bun é ideal para desenvolvedores que dizem “conheço SQL e só quero um auxiliar leve ao redor.” Ele abre mão de alguma magia 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 em seu código de acesso a dados, o Bun é uma opção atraente. Também é um bom meio termo se estiver migrando de database/sql+sqlx bruto e quiser adicionar estrutura sem sacrificar muita eficiência. A compensação é uma comunidade menor e menos abstrações de alto nível – você escreverá um pouco mais de código à mão. Mas como a documentação do Bun coloca, não fica no seu caminho. Use o Bun quando quiser um ORM que pareça usar SQL diretamente, especialmente para projetos centrados 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 um ORM, queremos garantias em tempo de compilação para nosso SQL.” Se você tem fortes habilidades em SQL e prefere gerenciar consultas e esquema em SQL (talvez você tenha DBAs ou apenas aprecie criar SQL eficiente), o sqlc provavelmente impulsionará sua produtividade e confiança. O desempenho é essencialmente ótimo, pois nada fica entre sua consulta e o banco de dados. As únicas razões para não usar sqlc seriam se você realmente não gosta de escrever SQL ou se suas consultas são tão dinâmicas que escrever muitas variantes se torna um fardo. Mesmo assim, você pode usar 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 impede o uso de um ORM em partes do seu projeto (alguns projetos usam GORM para coisas simples e sqlc para caminhos críticos, por exemplo). Em suma, escolha sqlc se você valoriza explicitação, sobrecarga zero e SQL com segurança de tipos – é uma ferramenta poderosa para ter em sua caixa de ferramentas.

Finalmente, vale notar 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 compensações caso a caso. Não é incomum começar com um ORM como GORM ou Ent para velocidade de desenvolvimento, e então usar sqlc ou Bun para caminhos específicos quentes que precisam de desempenho máximo. Todas as quatro soluções são ativamente mantidas e amplamente usadas, então não há escolha “errada” no geral – é sobre a escolha certa para seu contexto. Espero que esta comparação tenha lhe dado uma imagem mais clara de como GORM, Ent, Bun e sqlc se comparam, e ajude você a tomar uma decisão informada para seu próximo projeto Go.