GraphQL vs REST in Go: Duell der Titanen, oder wie man die richtige Wahl trifft
\n\nWenn Sie seit mindestens einem halben Jahr in Go programmieren, sind Sie sicherlich auf die ewige Debatte gestoßen: REST oder GraphQL? Der eine ist ein alter, guter „Arbeitsspecht", der andere ein modischer „Sportwagen" mit vielen Extras. Aber in der Welt von Go, wo Leistung und Einfachheit eine Religion sind, ist die Wahl nicht immer offensichtlich.
\n\nIn diesem Artikel werden wir nicht nur die Vor- und Nachteile auflisten. Wir werden echten Go-Code schreiben, die Ausführungszeit messen und analysieren, wann GraphQL ein Schuss ins Knie ist und wann es eine Abkürzung in die Welt effizienter APIs ist. Los geht's!
\n\nREST in Go: Stabilität und Vorhersagbarkeit
\n\nREST ist wie ein alter, bewährter Hammer. Er glänzt nicht, aber er schlägt Nägel perfekt ein. In Go werden REST-APIs mit net/http oder leichten Frameworks wie gin und chi geschrieben. Schauen wir uns ein einfaches Beispiel an:
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: "Alexej"}\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\nSchön? Ja. Einfach? Sehr. Aber was, wenn das Frontend nur den Benutzernamen benötigt, wir aber das gesamte Objekt übertragen? In REST nennt man das Over-Fetching – Datenüberfluss. Und in Go, wo wir es gewohnt sind, jedes Byte zu kontrollieren, kann das nerven.
\n\nDas N+1-Problem in REST
\n\nAngenommen, wir haben eine Liste von Bestellungen und benötigen für jede den Benutzernamen. In REST machen wir entweder N Anfragen (langsam) oder erstellen einen separaten Endpunkt /api/orders-with-users (viele Endpunkte). Go bewältigt dies durch Join-Abfragen in der Datenbank, aber auf API-Ebene wirkt es dennoch klobig.
GraphQL in Go: Leistung und Flexibilität
\n\nGraphQL ist wie ein Schweizer Taschenmesser: ein Endpunkt, und der Client entscheidet selbst, welche Daten er benötigt. In Go gibt es dafür das Framework gqlgen (von 99designs) – es generiert Code aus dem Schema, was ganz im Sinne von Go ist (weniger Boilerplate – mehr Action).
// 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\nNun die Implementierung in 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 // In der Realität – Datenbankabfrage\n return &User{\n ID: args.ID,\n Name: "Maria",\n Orders: []*Order{\n {ID: 1, Total: 250.50},\n },\n }, nil\n}\n\nDer Client fragt nur name an – erhält nur name. Kein Over-Fetching. Aber es gibt einen Haken: Wenn der Client orders für 10 Benutzer anfordert und jeder 50 Bestellungen hat – kann die Datenbank in die Knie gehen. Dies nennt man das N+1-Problem in GraphQL, und in Go wird es durch DataLoader gelöst.
DataLoader – Retter vor 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 // Batch-Anfragen an die DB\n userIDs := make([]int, len(keys))\n for i, key := range keys {\n userIDs[i] = key.Int()\n }\n users := batchFetchUsers(userIDs) // Ein SQL-Query mit WHERE IN\n results := make([]*dataloader.Result, len(ke