in GraphQL APIs modellieren
Wie man Filter- und Sortierargumente als saubere Input-Typen modelliert, Operator-Muster für verschiedene Datentypen implementiert, Magento's Filter-Konventionen nutzt und Pagination-Strategien für den jeweiligen Use Case wählt.
Inhaltsverzeichnis
- 1. Warum Filter-Design unterschätzt wird
- 2. Input Types als Basis für Filterlogik
- 3. Operator-Muster: eq, in, range, like
- 4. Magento FilterEqualTypeInput: Konventionen nutzen
- 5. Sortierung modellieren: Enum-Felder und Richtung
- 6. Pagination: Offset vs. Cursor-basiert
- 7. Filter an den Resolver und das Repository anbinden
- 8. Sicherheit: was nicht gefiltert werden darf
- 9. Zusammenfassung
- 10. Vergleich: Filter-Modellierungsansätze
- 11. FAQ
1. Warum Filter-Design unterschätzt wird
Filter- und Sortierlogik in GraphQL-APIs wirkt auf den ersten Blick trivial: Ein paar Argumente, ein bisschen SQL im Resolver und fertig. In der Praxis ist schlechtes Filter-Design aber einer der häufigsten Gründe für APIs, die schwer erweiterbar sind, Performance-Probleme erzeugen und Frontend-Teams frustrieren. Eine Query, die Filterbedingungen als flache Strings übergibt, ist schwer zu validieren, schwer zu dokumentieren und unmöglich maschinell zu verarbeiten.
Das Gegenstück dazu ist typisiertes Filter-Design mit strukturierten Input-Typen und Operator-Mustern. Dieser Ansatz ist explizit, maschinenlesbar, validierbar und erweiterbar. Magento macht es vor: FilterEqualTypeInput, FilterRangeTypeInput und FilterMatchTypeInput sind typisierte Filter-Input-Typen, die konsistent über alle Produktlisten-Queries verwendet werden. Wer eigene APIs baut, profitiert davon, dieses Muster zu übernehmen – angepasst an den eigenen Datenkontext.
2. Input Types als Basis für Filterlogik
Der erste Schritt zu sauberem Filter-Design ist die Wahl von Input Types statt Scalar-Argumenten. Statt query products(priceMin: Float, priceMax: Float) modelliert man query products(filter: ProductFilterInput), wobei ProductFilterInput ein Input-Typ mit Feldern für verschiedene Filterkriterien ist. Dieser Ansatz ist flexibler – neue Filterfelder können hinzugefügt werden, ohne die Query-Signatur zu ändern – und klarer in der Dokumentation, weil jedes Filterfeld einen expliziten Namen und Typ hat.
Input Types in GraphQL folgen denselben Typ-Regeln wie Output Types, aber sie sind nur als Eingabe erlaubt. Sie können andere Input Types als Felder enthalten, aber keine Output Types. Das erlaubt die Komposition von Filtern: Ein ProductFilterInput kann ein Feld price: PriceRangeFilterInput enthalten, das wiederum from: Float und to: Float definiert. Diese Verschachtelung ist klar, typsicher und semantisch eindeutig.
# Typed filter input types — composable and self-documenting
input PriceRangeFilterInput {
from: Float
to: Float
}
input StringFilterInput {
eq: String # exact match
in: [String!] # match any in list
like: String # pattern match (% wildcard)
neq: String # not equal
}
input IntFilterInput {
eq: Int
gt: Int # greater than
lt: Int # less than
gte: Int # greater than or equal
lte: Int # less than or equal
in: [Int!]
}
input ProductFilterInput {
sku: StringFilterInput
name: StringFilterInput
price: PriceRangeFilterInput
category_id: IntFilterInput
status: IntFilterInput
}
extend type Query {
filteredProducts(
filter: ProductFilterInput
sort: ProductSortInput
pageSize: Int = 20
currentPage: Int = 1
): ProductSearchResult
@resolver(class: "Vendor\\Catalog\\Model\\Resolver\\FilteredProducts")
}
3. Operator-Muster: eq, in, range, like
Operator-Muster strukturieren die Filterbedingungen nach ihrer Semantik. Der einfachste Operator ist eq (exact match): Ein Feld muss exakt einem Wert entsprechen. Für Listenfilterung ist in der passende Operator: Das Feld muss einem der Werte in der Liste entsprechen. Für numerische Bereiche wie Preise oder Datumsangaben eignen sich from/to oder gte/lte-Paare. Für Textsuche ist like mit Wildcard-Unterstützung der Standard.
Ein wichtiger Aspekt von Operator-Mustern ist die Behandlung von null-Werten. In Magento werden nicht angegebene Filter-Felder einfach ignoriert – kein Filter bedeutet keine Einschränkung. Das macht Sinn für optionale Filterfelder, ist aber bei Pflichtfiltern (etwa einem Mandanten-Filter für Multi-Store-Setups) eine Sicherheitslücke. Pflichtfilter sollten als Non-nullable-Argumente deklariert werden, nicht als optionale Felder in einem Input Type – oder der Resolver muss explizit prüfen, ob der Pflichtfilter gesetzt ist.