GraphQL vs REST em Go: duelo de titãs, ou como não errar na escolha
\n\nSe você programa em Go há pelo menos seis meses, certamente já se deparou com o debate eterno: REST ou GraphQL? Um é o bom e velho "cavalo de batalha", o outro é um "carro esportivo" moderno com várias opções. Mas no mundo Go, onde desempenho e simplicidade são religião, a escolha nem sempre é óbvia.
\n\nNeste artigo, não vamos apenas listar prós e contras. Vamos escrever código real em Go, medir o tempo de execução e analisar quando GraphQL é um tiro no pé e quando é um passe VIP para o mundo das APIs eficientes. Vamos nessa!
\n\nREST em Go: estabilidade e previsibilidade
\n\nREST é como um martelo velho e testado. Não brilha, mas martela pregos perfeitamente. Em Go, APIs REST são escritas com net/http ou frameworks leves como gin e chi. Vamos ver um exemplo simples:
package main\n\nimport (\n "encoding/json"\n "net/http"\n)\n\ntype User struct {\n ID int `json:"id"`\n Name string `json:"name"`\n}\n\nfunc getUserHandler(w http.ResponseWriter, r *http.Request) {\n user := User{ID: 1, Name: "Alexei"}\n w.Header().Set("Content-Type", "application/json")\n json.NewEncoder(w).Encode(user)\n}\n\nfunc main() {\n http.HandleFunc("/api/user", getUserHandler)\n http.ListenAndServe(":8080", nil)\n}\n\nBonito? Sim. Simples? Muito. Mas e se o frontend precisar apenas do nome do usuário e estivermos enviando o objeto inteiro? Em REST, isso é chamado de over-fetching — excesso de dados. E em Go, onde estamos acostumados a controlar cada byte, isso pode irritar.
\n\nO problema N+1 no REST
\n\nSuponha que temos uma lista de pedidos e para cada um precisamos do nome do usuário. No REST, fazemos N requisições (lento) ou criamos um endpoint separado /api/orders-with-users (vários endpoints). Go lida com isso através de consultas JOIN no banco de dados, mas no nível da API ainda parece rústico.
GraphQL em Go: potência e flexibilidade
\n\nGraphQL é como um canivete suíço: um único endpoint, e o cliente decide quais dados precisa. Em Go, existe o framework gqlgen (da 99designs) — ele gera código a partir do schema, o que é bem no estilo Go (menos boilerplate, mais ação).
// schema.graphql\ntype Query {\n user(id: Int!): User\n}\n\ntype User {\n id: Int!\n name: String!\n orders: [Order!]!\n}\n\ntype Order {\n id: Int!\n total: Float!\n}\n\nAgora a implementação em Go:
\n\npackage graph\n\nimport (\n "context"\n "fmt"\n)\n\ntype Resolver struct{}\n\nfunc (r *Resolver) User(ctx context.Context, args struct{ ID int }) (*User, error) {\n // Na prática — consulta ao BD\n return &User{\n ID: args.ID,\n Name: "Maria",\n Orders: []*Order{\n {ID: 1, Total: 250.50},\n },\n }, nil\n}\n\nO cliente solicita apenas name — recebe apenas name. Sem over-fetching. Mas há um detalhe: se o cliente solicitar orders para 10 usuários, e cada um tiver 50 pedidos — o banco de dados pode cair de joelhos. Isso é chamado de problema N+1 no GraphQL, e em Go é resolvido com DataLoaders.
DataLoader — salvador do N+1
\n\nimport "github.com/graph-gophers/dataloader"\n\ntype UserLoader struct {\n *dataloader.Loader\n}\n\nfunc NewUserLoader() *UserLoader {\n return &UserLoader{\n Loader: dataloader.NewBatchedLoader(func(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {\n // Agrupa consultas ao BD\n userIDs := make([]int, len(keys))\n for i, key := range keys {\n userIDs[i] = key.Int()\n }\n users := batchFetchUsers(userIDs) // Uma única consulta SQL com WHERE IN\n results := make([]*dataloader.Result, len(ke