Schema, Query, Mutation, Resolver wirklich verstehen
Schema-Definition, Query-Sprache, Mutations und Resolver sind die vier tragenden Säulen jedes GraphQL-Systems. Wer versteht, wie sie zusammenwirken und wo die typischen Fehler entstehen, baut APIs, die skalieren – statt APIs, die nach dem ersten Produktivbetrieb umgebaut werden müssen.
Inhaltsverzeichnis
- 1. Was GraphQL wirklich anders macht als REST
- 2. Das Schema: der Vertrag zwischen Client und Server
- 3. Queries: Daten präzise abfragen
- 4. Mutations: Daten verändern mit klarer Struktur
- 5. Resolver: wo die Ausführungslogik lebt
- 6. Der Request-Lifecycle von Parse bis Response
- 7. Falsch gegen richtig: typische Schema-Muster
- 8. GraphQL-Grundlagen im Magento-Kontext
- 9. Tooling für den Einstieg: GraphiQL und Inspector
- 10. Zusammenfassung
- 11. FAQ
1. Was GraphQL wirklich anders macht als REST
REST exponiert Ressourcen als URLs. Ein Endpunkt gibt eine feste Datenmenge zurück – der Client nimmt, was kommt, oder macht mehrere Requests. GraphQL dreht dieses Verhältnis um: Es gibt einen einzigen Endpunkt, der Client beschreibt präzise, welche Felder er braucht, und der Server liefert genau das. Das klingt nach einer kleinen Verschiebung, hat aber weitreichende Konsequenzen für Schema-Design, Caching, Testing und die Zusammenarbeit zwischen Frontend und Backend-Teams.
Der zweite strukturelle Unterschied ist die starke Typisierung. Jedes Feld in einem GraphQL-Schema hat einen expliziten Typ. Das Schema ist gleichzeitig Dokumentation und Vertrag. Frontend-Entwickler können Queries gegen das Schema validieren, bevor der Server überhaupt gefragt wird. Code-Generatoren produzieren aus dem Schema typsichere Clients für TypeScript, Swift oder Kotlin. Diese Eigenschaften machen GraphQL besonders wertvoll in Teams, die Frontend und Backend parallel entwickeln – solange das Schema-Design sauber bleibt.
2. Das Schema: der Vertrag zwischen Client und Server
Das GraphQL-Schema wird in der Schema Definition Language (SDL) geschrieben. Es definiert alle Typen, Felder, Argumente und Beziehungen der API. Das Schema hat drei besondere Einstiegspunkte: Query für Lesezugriffe, Mutation für schreibende Operationen und optional Subscription für Echtzeit-Updates. Alles andere sind benannte Typen – type für Objekte, input für Eingabedaten, enum für Aufzählungen, interface und union für Polymorphismus.
Ein häufiger Anfängerfehler ist, das Schema als technischen Implementierungsdetail zu betrachten. In der Praxis ist das Schema das wichtigste Kommunikationsmittel zwischen Frontend-Entwicklern, Backend-Entwicklern und Stakeholdern. Jede Schema-Änderung, die existierende Felder entfernt oder umbenennt, ist ein Breaking Change. Deshalb beginnt gutes GraphQL-Design beim Schema – nicht beim Resolver. Das Schema zuerst zu definieren, bevor eine Zeile Implementierungscode geschrieben wird, ist eine der wichtigsten Praktiken für wartbare APIs.
# Complete schema example: product catalog with typed relationships
type Query {
product(sku: String!): Product
products(filter: ProductFilterInput, pageSize: Int = 20, currentPage: Int = 1): ProductList!
}
type Mutation {
addProductReview(input: AddReviewInput!): AddReviewResult!
}
type Product {
sku: String!
name: String!
price: Money!
description: String
categories: [Category!]!
reviews(pageSize: Int = 5): ReviewList!
}
type Money {
value: Float!
currency: CurrencyEnum!
}
enum CurrencyEnum {
EUR
USD
GBP
}
type Category {
id: ID!
name: String!
urlKey: String!
}
input ProductFilterInput {
sku: FilterEqualTypeInput
name: FilterMatchTypeInput
price: FilterRangeTypeInput
categoryId: FilterEqualTypeInput
}
input FilterEqualTypeInput {
eq: String
in: [String]
}
3. Queries: Daten präzise abfragen
Eine GraphQL-Query beschreibt, welche Felder der Client benötigt. Der Server gibt genau diese Felder zurück – keine mehr, keine weniger. Das eliminiert Overfetching (REST gibt alles zurück) und Underfetching (REST gibt zu wenig zurück, was mehrere Requests erfordert). Queries können Argumente enthalten, um Daten zu filtern oder zu paginieren. Fragments erlauben die Wiederverwendung von Feldauswahlen. Benannte Queries mit Variablen sind die empfohlene Form für jede Query, die in Produktionscode landet: Sie sind besser cachebar, leichter zu loggen und ermöglichen Persisted Queries.
Das N+1-Problem ist der häufigste Performance-Fehler in GraphQL-Abfragen. Wenn eine Query eine Liste von Produkten lädt und jedes Produkt seinen Lieferanten über einen separaten Resolver-Aufruf lädt, entstehen N+1 Datenbankabfragen. Die Lösung ist ein DataLoader-Pattern: Resolver sammeln alle angeforderten IDs in einem Tick und laden sie in einem einzigen Batch-Request. Wer das nicht von Anfang an einplant, baut sich ein Performance-Problem, das sich erst unter Last zeigt.
4. Mutations: Daten verändern mit klarer Struktur
Mutations sind in GraphQL syntaktisch ähnlich wie Queries, haben aber eine andere Semantik: Sie führen Seiteneffekte aus. Gut designte Mutations akzeptieren alle Eingabedaten als ein einzelnes input-Objekt statt als separate Argumente. Das macht das Schema stabiler, weil neue Felder zur input-Definition hinzugefügt werden können, ohne die Mutation-Signatur zu ändern. Der Rückgabetyp einer Mutation sollte immer ein dediziertes Result-Objekt sein, das sowohl Erfolgsdaten als auch Fehlerinformationen enthält – niemals einfach Boolean.
Eine häufige Fehlannahme: Mutations sind automatisch atomisch. Das ist falsch. GraphQL führt mehrere Mutations in einem Request sequenziell aus, aber jede einzelne Mutation ist nur dann transaktional, wenn die Resolver-Implementierung das explizit sicherstellt. Wer eine Mutation baut, die mehrere Tabellen ändert, muss Transaktionen in der Datenzugriffsschicht implementieren – nicht im Resolver. Der Resolver delegiert, die Datenzugriffsschicht entscheidet über Atomizität.
# Well-designed mutation: input type + rich result type
mutation AddToCart($input: AddToCartInput!) {
addProductsToCart(input: $input) {
cart {
id
totalQuantity
itemsV2 {
items {
product { sku name }
quantity
prices {
rowTotal { value currency }
}
}
}
}
userErrors {
code
message
field
}
}
}
# Variables — always use named queries with variables in production code
# {
# "input": {
# "cartId": "abc123",
# "cartItems": [{ "sku": "DEMO-001", "quantity": 2 }]
# }
# }
5. Resolver: wo die Ausführungslogik lebt
Jedes Feld in einem GraphQL-Schema hat einen Resolver – eine Funktion, die den Wert dieses Feldes zurückgibt. Der Root-Resolver für eine Query erhält die Argumente aus dem Client-Request. Alle nachgelagerten Resolver erhalten den Wert des übergeordneten Objekts als ersten Parameter. Diese Kette von Resolvern ist der GraphQL Execution-Lifecycle. Das ermöglicht eine saubere Trennung: Root-Resolver für Einstiegspunkte, Feld-Resolver für berechnete oder relational geladene Daten.
Das wichtigste Design-Prinzip für Resolver: Resolver delegieren, sie implementieren nicht. Ein guter Resolver hat drei bis zehn Zeilen Code: Eingabe extrahieren, an einen Service delegieren, Ergebnis zurückgeben. Geschäftslogik, Datenbankzugriffe, Validierungen und Transformationen gehören in separate Service-Klassen. Resolver, die mehrere hundert Zeilen lang sind und direkte SQL-Abfragen enthalten, sind ein Anti-Pattern, das Wartbarkeit und Testbarkeit massiv einschränkt.
6. Der Request-Lifecycle von Parse bis Response
Ein GraphQL-Request durchläuft nach dem Empfang durch den Server mehrere Phasen. Die erste Phase ist das Parsing: Der Query-String wird in einen Abstract Syntax Tree (AST) transformiert. Syntaxfehler werden hier erkannt. Die zweite Phase ist die Validation: Der AST wird gegen das Schema geprüft. Werden Felder angefragt, die nicht im Schema existieren? Haben Argumente den richtigen Typ? Diese Phase schlägt fehl, bevor ein einziger Resolver ausgeführt wird. Die dritte Phase ist die Execution: Die Resolver werden aufgerufen, Seiteneffekte werden ausgeführt, das Ergebnis wird zusammengesetzt.
Middlewares und Extensions greifen an verschiedenen Punkten in diesen Lifecycle ein. Query-Complexity-Checks laufen typischerweise nach der Validation-Phase. Authentication-Middleware setzt den Request-Context vor der Execution. Tracing-Extensions messen die Ausführungszeit einzelner Resolver. Das Verständnis dieses Lifecycles ist entscheidend für Debugging: Ein 400-Fehler kommt meist aus der Validation-Phase (Schema-Mismatch), ein 403 aus der Auth-Middleware, ein langsamer Response aus der Execution-Phase.
7. Falsch gegen richtig: typische Schema-Muster
Die Qualität eines GraphQL-Schemas zeigt sich oft erst, wenn das System gewachsen ist und das erste Mal umgebaut werden muss. Typische Schlechte Muster entstehen durch voreilige Vereinfachung: Generic-Typen statt domänenspezifischer Typen, String-Argumente statt Input-Typen, fehlende Non-Null-Markierungen. Diese Entscheidungen sind zu Beginn schnell, machen aber spätere Erweiterungen schwierig ohne Breaking Changes.
| Muster | Problematisch | Empfohlen | Begründung |
|---|---|---|---|
| Mutation-Argumente | createUser(name: String!, email: String!) |
createUser(input: CreateUserInput!) |
Erweiterbar ohne Breaking Change |
| Rückgabetyp | mutation: Boolean! |
mutation: CreateUserResult! |
Fehler und Daten klar trennbar |
| ID-Typen | id: String! |
id: ID! |
Semantisch korrekt, Tools-freundlich |
| Nullable-Felder | Alles nullable (Standard) | Non-Null ! explizit setzen |
Client kann Null-Checks sparen |
| Fehler | Nur Top-Level errors |
UserErrors im Result-Typ | Fachfehler vom Transport trennen |
Ein weiterer häufiger Fehler: Resolver, die direkt Datenbankentitäten als GraphQL-Typen exponieren. Das koppelt das Schema eng an das Datenmodell und macht Refactoring der Datenbankstruktur zu Breaking Changes im Schema. Besser ist eine explizite Mapping-Schicht, die interne Datenstrukturen in Schema-Typen übersetzt – ähnlich wie ein ViewModel in einer MVC-Applikation.
8. GraphQL-Grundlagen im Magento-Kontext
Magento 2.3+ implementiert GraphQL als vollständig typisierte API neben der bestehenden REST-API. Das Schema wächst mit jedem Release und umfasst mittlerweile hunderte von Typen, Queries und Mutations für Produktkatalog, Checkout, Kundenverwaltung und mehr. Die Architektur folgt dem GraphQL-Standard: SDL-Dateien definieren das Schema modulweise, Resolver-Klassen implementieren die Ausführungslogik, und der Dependency-Injection-Container verbindet beides.
Die wichtigste Besonderheit in Magento: Resolver erhalten den Context als Objekt, das unter anderem den aktuellen Store, die Kundengruppe und den Authorization-Token enthält. Wer eigene Resolver schreibt, muss diesen Context korrekt auswerten, um Store-spezifische Daten zurückzugeben und Zugriffsrechte zu prüfen. Ein häufiger Fehler ist, Store-Konfigurationen direkt im Resolver zu laden statt über den Context – das führt zu Caching-Problemen und inkonsistenten Responses in Multi-Store-Setups.
# Magento GraphQL: product query with all key fields
query GetProductDetails($sku: String!) {
products(filter: { sku: { eq: $sku } }) {
items {
__typename
sku
name
url_key
price_range {
minimum_price {
final_price { value currency }
regular_price { value currency }
discount { percent_off amount_off { value } }
}
}
media_gallery {
url
label
position
disabled
}
categories {
id
name
url_path
breadcrumbs {
category_name
category_url_path
}
}
... on ConfigurableProduct {
configurable_options {
attribute_code
label
values { uid label swatch_data { value } }
}
variants {
attributes { code uid label }
product { sku price_range { minimum_price { final_price { value } } } }
}
}
}
}
}
9. Tooling für den Einstieg: GraphiQL und Inspector
GraphiQL ist das interaktive Schema-Explorer-Tool, das die meisten GraphQL-Server im Development-Modus eingebettet bereitstellen. Es bietet Autocomplete basierend auf dem Schema, Echtzeit-Fehlerprüfung, Query-Variablen-Editor und History. Für den Einstieg ist GraphiQL unverzichtbar – sowohl um das Schema zu erkunden als auch um Queries schrittweise zu entwickeln. Altair GraphQL Client ist eine Alternative mit zusätzlichen Features wie Collections, Environments und Pre-Request-Scripts.
GraphQL Inspector ist ein CLI-Tool für Teams: Es vergleicht zwei Versionen eines Schemas und identifiziert Breaking Changes, Deprecations und neue Felder. In einer CI-Pipeline eingebettet verhindert es, dass Schema-Änderungen unbemerkt Clients brechen. Schema-Registry-Dienste wie Apollo Studio oder Hive bauen darauf auf und bieten eine vollständige Versionshistorie des Schemas mit Client-Impact-Analysen. Wer im Team arbeitet und ein öffentliches oder intern geteiltes Schema pflegt, braucht eines dieser Tools.
10. Zusammenfassung
GraphQL-Grundlagen sind mehr als Syntax. Das Schema ist ein Vertrag, der sorgfältig designt werden muss, bevor Resolver implementiert werden. Queries erlauben präzise Datenabfragen und vermeiden Over- und Underfetching. Mutations verändern Daten und sollten immer Input-Typen mit reichen Result-Typen kombinieren. Resolver delegieren an Services und halten Geschäftslogik aus dem GraphQL-Layer heraus. Der Request-Lifecycle von Parse über Validation bis Execution bietet mehrere Ebenen, um Sicherheit, Performance und Observability einzubauen.
Im Magento-Kontext sind diese Grundlagen die Voraussetzung für eigene Resolver, Schema-Erweiterungen und die Integration mit Headless-Frontends. Wer die Grundlagen versteht, erkennt sofort, warum ein langsamer Resolver an einem N+1-Problem liegt, warum eine Mutation keinen Breaking Change einführen sollte und warum Schema-Design immer vor Resolver-Implementierung kommt.
GraphQL Grundlagen — Das Wichtigste auf einen Blick
Schema zuerst
Das Schema ist Vertrag und Dokumentation. Es wird vor dem Resolver entworfen und so modelliert, dass es ohne Breaking Changes wachsen kann.
Resolver delegieren
Resolver haben 3–10 Zeilen: Eingabe extrahieren, Service aufrufen, Ergebnis zurückgeben. Geschäftslogik gehört in den Service-Layer.
Mutations mit Input-Typen
Immer Input-Typen statt separater Argumente. Result-Typen mit UserErrors statt Boolean-Rückgaben.
N+1 von Anfang an
DataLoader oder Batching-Strategien von Beginn einplanen. N+1-Probleme zeigen sich erst unter Last, wenn Refactoring teuer ist.