Pavel Simonov

Город IT · 2023 · Pavel Simonov

Records API: GraphQL für eine Low-Code-Plattform neu gedacht

Redigierte Transkription auf Grundlage der automatischen russischen Untertitel der Aufnahme und der Folien; keine anhand des Audios geprüfte wortgetreue Transkription. Wiederholungen und Füllwörter wurden entfernt; Fachbegriffe wurden mit den Folien abgeglichen. Unklare Stellen sind gekennzeichnet. Der Inhalt bleibt im Kontext des Vortrags von 2023.

Die Originalfolien bleiben auf Russisch. Der Vortragstext ist in der gewählten Sprache verfügbar; Code und API-Bezeichner werden nicht übersetzt.

Inhalt

1 / 36Records API

Originalfolie 1: Records API
Originalfolie · RU Diese Stelle im Video

Vortragstext

Hallo! Ich bin Pavel Simonov, Systemarchitekt bei Citeck. In diesem Vortrag geht es um Records API, unsere eigene Entwicklung und eine Neuinterpretation von GraphQL für eine Low-Code-Plattform.

2 / 36Wie alles begann

Originalfolie 2: Wie alles begann
Originalfolie · RU Diese Stelle im Video

Vortragstext

Zunächst etwas zur Vorgeschichte. Wie viele andere entwickelten wir unser System lange mit einer REST API. Alles lief gut, bis wir bemerkten, dass eine relativ einfache Tabelle mit wenigen Feldern und hundert Datensätzen zehn Sekunden oder länger zum Laden vom Backend brauchte.

Bei der Untersuchung stellte sich heraus, dass das Backend pro Zeile so viele Daten zurückgab, dass uns das Ausmaß des Problems erschreckte.

3 / 36Overfetching

Originalfolie 3: Overfetching
Originalfolie · RU Diese Stelle im Video

Vortragstext

Wir waren auf Overfetching gestoßen: Der Client benötigt bestimmte Daten, der Server liefert jedoch erheblich mehr. Die Folie zeigt das mit einer Frage: Wo ist in all diesen Daten die E-Mail-Adresse des Benutzers?

Eine übliche schnelle Lösung ist, eine große Anfrage in viele kleine, auf einzelne Aufgaben zugeschnittene Anfragen aufzuteilen, die nur bestimmte Daten zurückgeben. Das war aber nicht unser Ansatz: Wir entwickelten eine Plattform.

4 / 36Eine Low-Code-Plattform

Originalfolie 4: Eine Low-Code-Plattform
Originalfolie · RU Diese Stelle im Video

Vortragstext

Wir finden, dass Roboter die mühsame Arbeit erledigen sollten, nicht Menschen. Was sich automatisieren lässt, sollte automatisiert werden.

Bei klassischer Entwicklung bedeutet ein neuer Typ mit seinen Entitäten: Entität, Repository, Service, Controller und gegebenenfalls weitere Komponenten implementieren. Das ist kein Low-Code. Wir brauchten einen anderen Ansatz.

5 / 36Neuer Typ: Kennung und Name

Originalfolie 5: Neuer Typ: Kennung und Name
Originalfolie · RU Diese Stelle im Video

Vortragstext

In unserer Plattform öffnen wir den Bereich für Typen und legen einen neuen Typ mit Kennung und Namen an. Die Folie zeigt den Vollmachtstyp «Доверенность» mit der Kennung attorney.

6 / 36Neuer Typ: Attribute

Originalfolie 6: Neuer Typ: Attribute
Originalfolie · RU Diese Stelle im Video

Vortragstext

Anschließend konfigurieren wir die Attribute. In diesem Beispiel sind es zwei: Name und Nummer. Nach der Konfiguration erstellen wir den Typ. Im Screenshot hat name den Typ Text und number den Typ Number.

7 / 36Eine Tabelle zum Menü hinzufügen

Originalfolie 7: Eine Tabelle zum Menü hinzufügen
Originalfolie · RU Diese Stelle im Video

Vortragstext

Nach dem Anlegen des Typs wechseln wir in den Menüeditor und fügen eine Tabelle zur Anzeige seiner Entitäten hinzu. Im Screenshot heißt dieser Eintrag «Журнал».

8 / 36Den neuen Typ auswählen

Originalfolie 8: Den neuen Typ auswählen
Originalfolie · RU Diese Stelle im Video

Vortragstext

Wir suchen den gewünschten Typ, wählen ihn aus und speichern die Menükonfiguration.

9 / 36Die erzeugte Tabelle

Originalfolie 9: Die erzeugte Tabelle
Originalfolie · RU Diese Stelle im Video

Vortragstext

Beim Öffnen des Menüs sehen wir eine fertige Tabelle mit den gerade konfigurierten Attributen. Über die Plus-Schaltfläche legen wir eine neue Entität an.

10 / 36Automatisch erzeugtes Formular

Originalfolie 10: Automatisch erzeugtes Formular
Originalfolie · RU Diese Stelle im Video

Vortragstext

Ein automatisch erzeugtes Formular erscheint. Wir tragen Daten ein und speichern sie. Die Vollmacht hat in diesem Beispiel einen Namen und eine Nummer.

11 / 36Die Detailansicht des Dokuments

Originalfolie 11: Die Detailansicht des Dokuments
Originalfolie · RU Diese Stelle im Video

Vortragstext

Nach dem Speichern erscheint die Detailansicht des Dokuments. Für den neuen Typ mussten wir weder Formular noch Detailansicht gesondert programmieren.

12 / 36Der Datensatz in der Tabelle

Originalfolie 12: Der Datensatz in der Tabelle
Originalfolie · RU Diese Stelle im Video

Vortragstext

Wir kehren zur Tabelle zurück, aktualisieren sie und sehen unseren Datensatz. Der Ablauf ist einfach und schnell.

13 / 36Was im Backend entsteht

Originalfolie 13: Was im Backend entsteht
Originalfolie · RU Diese Stelle im Video

Vortragstext

Im Backend entsteht eine relativ einfache Tabelle. Sie enthält Metadaten: wer den Datensatz wann angelegt und wer ihn wann geändert hat. Dazu kommen unsere Felder Name und Nummer.

Eine solche Low-Code-Plattform ermöglicht eine flexible Umsetzung dieser Aufgaben. Kehren wir nun zum Overfetching zurück.

14 / 36Warum wir GraphQL erwogen

Originalfolie 14: Warum wir GraphQL erwogen
Originalfolie · RU Diese Stelle im Video

Vortragstext

Wir suchten eine Lösung, und GraphQL schien die naheliegendste Wahl. Auf den ersten Blick passt es: Der Client nennt die benötigten Daten, und das Backend gibt nur diese zurück. Das Problem scheint gelöst.

15 / 36GraphQL passte nicht

Originalfolie 15: GraphQL passte nicht
Originalfolie · RU Diese Stelle im Video

Vortragstext

Als wir mit der Integration begannen, fühlte sich die Anpassung von GraphQL an unsere Low-Code-Plattform jedoch an, als wollten wir etwas Unpassendes mit Gewalt passend machen. Es passte nicht. Sehen wir uns die konkreten Probleme an.

16 / 36GraphQL-Nachteile für unsere Plattform

Originalfolie 16: GraphQL-Nachteile für unsere Plattform
Originalfolie · RU Diese Stelle im Video

Vortragstext

Erstens das vorgeschriebene Schema. Normalerweise ist das ein großer Vorteil: ein Vertrag zwischen Client und Server, sodass beide wissen, was zurückgegeben wird. Bei uns entstehen Entitäten jedoch dynamisch. Ein Analyst kann Typen mit wenigen Klicks hinzufügen. Das Schema automatisch zu erzeugen oder universell zu gestalten, war sehr aufwendig.

Zweitens sind universelle Clients schwierig. Unser Frontend darf nicht von konkreten Geschäftsobjekten wie einer Vertragsklasse abhängen. Wir arbeiten mit Daten, Maps und Arrays, möglichst allgemein und flexibel.

Drittens kann auch das Backend ein Client sein. Microservices sollen dieselbe API verwenden: Warum sie zweimal erfinden? Auch Kunden fragen nach Integrationsmöglichkeiten. Wir möchten ihnen dieselbe API anbieten, die der Browser nutzt.

Viertens ist das GraphQL-Format nicht so verbreitet, wie wir es gern hätten. Es wird viel in seine Verbreitung investiert, und es gibt Online- und Offline-Editoren. Vertraute Formate werden jedoch von fast jedem Werkzeug unterstützt.

17 / 36Was uns an GraphQL gefiel

Originalfolie 17: Was uns an GraphQL gefiel
Originalfolie · RU Diese Stelle im Video

Vortragstext

Einige Vorteile gefielen uns sehr. Der Client kennt den Typ des Ergebnisses genau; bei GraphQL gewährleistet das das Schema.

Es gibt weder Underfetching noch Overfetching und nur einen gemeinsamen Endpoint. Neue Endpoints für alltägliche Aufgaben sind in unserem System unnötig. Diese Eigenschaften wollten wir beibehalten.

18 / 36Grundbegriffe von Records API

Originalfolie 18: Grundbegriffe von Records API
Originalfolie · RU Diese Stelle im Video

Vortragstext

Wir entwickelten eine eigene API und nannten sie Records API. Zum Verständnis definieren wir drei Grundbegriffe und die zugehörigen Regeln.

Ein Microservice ist hier eine Anwendung mit einem innerhalb der Plattform eindeutigen Namen. Ein Record ist jede beliebige Ressource des Systems. Eine Datenquelle ist ein DAO für Records, das alle oder einige CRUD-Operationen implementiert: Anlegen, Lesen, Ändern und Löschen. Seine Kennung ist innerhalb des Microservices eindeutig.

Jeder Record besitzt eine globale Kennung. emodel/person@admin besteht aus drei Teilen: dem Microservice emodel, der Datenquelle person nach dem Schrägstrich und der lokalen Kennung admin nach @.

Beispiel aus der Folie

emodel/person@admin

19 / 36Ein universelles Suchformat

Originalfolie 19: Ein universelles Suchformat
Originalfolie · RU Diese Stelle im Video

Vortragstext

Uns fehlte ein universelles Format für Suchanfragen. Zuvor mussten wir Objekte mit den benötigten Feldern über Parameter übergeben, was umständlich war. Wir entwickelten ein Format, das mehrere Fragen beantwortet.

Wo suchen wir? sourceId enthält den Anwendungs- oder Microservice-Namen, einen Schrägstrich und die Kennung der Datenquelle. Wie suchen wir? language bezeichnet die Abfragesprache. Für die API ist das eine beliebige Zeichenfolge; die Datenquelle muss diese Sprache aber verstehen.

Was suchen wir? query ist die eigentliche Anfrage. Sie hängt von der Sprache ab, und ihre Interpretation liegt vollständig bei der Datenquelle. Meist ist es eine Zeichenfolge oder ein Objekt.

Die übrigen Felder sind üblich: groupBy gibt Gruppierungsattribute an; sortBy das Attribut und die Sortierrichtung; page die Anzahl der zu überspringenden und abzurufenden Records.

Beispiel aus der Folie

sourceId: String
language: String
query: Any
groupBy: String[]
sortBy: SortBy[] { attribute: String, ascending: Bool }
page: Page { skipCount: Int, maxItems: Int }

20 / 36Attribute und Beziehungen

Originalfolie 20: Attribute und Beziehungen
Originalfolie · RU Diese Stelle im Video

Vortragstext

Attribute sind ein wichtiger Teil der API. Damit kann der Client die benötigten Daten flexibel und bequem anfordern. Mit Kennungen zu arbeiten ist das eine; über eine Vertragsreferenz den vollständigen Namen der zugehörigen Gegenpartei zu ermitteln, ist etwas anderes.

Betrachten wir einen einfachen Datengraphen: Ein Vertrag hat einen Textnamen und eine Assoziation zu einer Gegenpartei. Diese besitzt den vollständigen Organisationsnamen und eine Adresse, ebenfalls Textfelder.

21 / 36Verschachtelte Attribute mit Punkten

Originalfolie 21: Verschachtelte Attribute mit Punkten
Originalfolie · RU Diese Stelle im Video

Vortragstext

Nehmen wir an, die Entitäten enthalten die Daten aus der Folie. Jeder Record hat eine lokale Kennung. counterparty speichert eine Referenz im zuvor beschriebenen globalen Format.

Die erste Regel für Records-API-Attribute lautet: Verschachtelte Werte werden mit Punkten angesprochen. Vom Vertrag aus fragen wir den vollständigen Namen mit counterparty.fullOrgName ab. Ein längerer Pfad wäre association0.association1.someField.

Beispiel aus der Folie

association0.association1.someField
counterparty.fullOrgName

22 / 36Skalare: der Ergebnistyp

Originalfolie 22: Skalare: der Ergebnistyp
Originalfolie · RU Diese Stelle im Video

Vortragstext

Wir wollten den GraphQL-Vorteil eines bekannten Ergebnistyps erhalten. Da wir kein Schema haben, brauchten wir eine andere Lösung. So entstanden Skalare.

Ein Wert im Graphen kann unterschiedlich dargestellt werden. Eine Zahl kann als Zahl oder Zeichenfolge erscheinen; auch ein boolescher Wert hat verschiedene Darstellungen. Bei einer Entitätsreferenz kann der Client die Referenz selbst oder den Anzeigenamen der Entität benötigen. Der Ausgangswert bleibt derselbe.

Der Skalar folgt auf das Attribut und ein Fragezeichen: attribute?scalar. Der Client legt die gewünschte Darstellung fest, und die Engine konvertiert das Ergebnis. [Ein Teil der folgenden Erklärung ist in den automatischen Untertiteln unklar.]

Beispiel aus der Folie

association0.association1.someField?scalar

23 / 36Den Vertragsnamen laden

Originalfolie 23: Den Vertragsnamen laden
Originalfolie · RU Diese Stelle im Video

Vortragstext

Für das einfache Lesen eines Namens schreiben wir name. Ohne angegebenen Skalar wird disp angenommen, also display name.

Bei einer Zeichenfolge ist das ihr Inhalt. Bei Referenzen unterscheidet sich der Anzeigename von der Referenz selbst. Daher sind name und name?disp in diesem Beispiel gleichwertig.

Beispiel aus der Folie

name
name?disp

24 / 36Vollständiger Name der Gegenpartei

Originalfolie 24: Vollständiger Name der Gegenpartei
Originalfolie · RU Diese Stelle im Video

Vortragstext

Um über eine Vertragsreferenz den vollständigen Namen der Gegenpartei zu erhalten, folgen wir counterparty und dann fullOrgName. Für eine Zeichenfolge ergänzen wir str: counterparty.fullOrgName?str.

Beispiel aus der Folie

counterparty.fullOrgName?str

25 / 36Attribut-Postprozessoren

Originalfolie 25: Attribut-Postprozessoren
Originalfolie · RU Diese Stelle im Video

Vortragstext

Neben der Darstellung mit Skalaren müssen wir oft ein bereits berechnetes Ergebnis nachbearbeiten. Dafür gibt es Postprozessoren.

Der häufigste ist or. Bei null oder einer leeren Zeichenfolge liefert er den angegebenen Ersatzwert. Dafür gibt es eine Kurzschreibweise: Ausrufezeichen und Standardwert. counterparty.fullOrgName?str!"unknown" entspricht counterparty.fullOrgName?str|or("unknown").

Allgemein folgen auf das Attribut ein senkrechter Strich und ein Prozessor mit Argumenten; weitere Prozessoren lassen sich aneinanderreihen. Häufig sind Zahlenformatierungen wie führende Nullen. [Der Name eines weiteren Postprozessors ist in den automatischen Untertiteln unklar.] Daneben gibt es speziellere Verarbeitungsschritte.

Beispiel aus der Folie

counterparty.fullOrgName?str!"unknown"
==
counterparty.fullOrgName?str|or("unknown")

attribute|processor0(arg0,arg1,argN)|processor1(arg0,arg1,argN)

26 / 36HTTP API: Records suchen

Originalfolie 26: HTTP API: Records suchen
Originalfolie · RU Diese Stelle im Video

Vortragstext

Wechseln wir von der Theorie zur Praxis. HTTP ist eine Möglichkeit, Records API zu verwenden. Wir nutzen immer POST-Anfragen. Wir wollten unabhängig vom Transport bleiben und verteilen die Operationen daher nicht auf verschiedene HTTP-Methoden.

Eine Suche enthält query im beschriebenen Format und die gewünschten attributes. Links in attributes steht der Ergebnisalias, rechts der Attributausdruck. Hier suchen wir einen Benutzer anhand seiner E-Mail-Adresse, nehmen einen Record und fordern die lokale Kennung als userId an.

Beispiel aus der Folie

POST /gateway/records/query

{
  "query": {
    "sourceId": "emodel/person",
    "language": "predicate",
    "query": {
      "t": "eq",
      "a": "email",
      "v": "admin@citeck.ru"
    },
    "page": {
      "maxItems": 1
    }
  },
  "attributes": {
    "userId": "?localId"
  }
}

27 / 36HTTP API: Suchantwort

Originalfolie 27: HTTP API: Suchantwort
Originalfolie · RU Diese Stelle im Video

Vortragstext

Das Ergebnis ist eine Liste von Records. Jeder enthält immer eine globale id und die angeforderten attributes. Links stehen unsere Aliasse, rechts die Werte. Bei diesem Benutzer ist userId gleich admin. totalCount gibt die Anzahl gefundener Records an.

Beispiel aus der Folie

{
  "records": [
    {
      "id": "emodel/person@admin",
      "attributes": {
        "userId": "admin"
      }
    }
  ],
  "totalCount": 1
}

28 / 36HTTP API: Attribute abrufen

Originalfolie 28: HTTP API: Attribute abrufen
Originalfolie · RU Diese Stelle im Video

Vortragstext

Sind die Kennungen bereits bekannt, senden wir sie als records-Array und geben die Attribute an. Das Prinzip entspricht dem der Suche.

Hier fordern wir die E-Mail-Adressen zweier Benutzer an. email!'undefined' definiert einen Ersatzstring für den Fall, dass die E-Mail-Adresse fehlt.

Beispiel aus der Folie

POST /gateway/records/query

{
  "records": [
    "emodel/person@admin",
    "emodel/person@pushkin"
  ],
  "attributes": {
    "email": "email!'undefined'"
  }
}

29 / 36HTTP API: Antwort mit Attributen

Originalfolie 29: HTTP API: Antwort mit Attributen
Originalfolie · RU Diese Stelle im Video

Vortragstext

Die Antwort enthält zwei Records mit id und berechneten Attributen. admin hat eine E-Mail-Adresse. Im zweiten Beispiel erscheint der String "undefined", der in der Anfrage als Ersatzwert festgelegt wurde.

Beispiel aus der Folie

{
  "records": [
    {
      "id": "emodel/person@admin",
      "attributes": {
        "email": "admin@citeck.ru"
      }
    },
    {
      "id": "emodel/person@pushkin",
      "attributes": {
        "email": "undefined"
      }
    }
  ]
}

30 / 36HTTP API: Records ändern

Originalfolie 30: HTTP API: Records ändern
Originalfolie · RU Diese Stelle im Video

Vortragstext

Änderungen verwenden eine andere URL: /gateway/records/mutate. Wir geben den Record und neue Attributwerte an. Im Beispiel ändern wir email und address von admin.

Beispiel aus der Folie

POST /gateway/records/mutate

{
  "records": [
    {
      "id": "emodel/person@admin",
      "attributes": {
        "email": "new-email@citeck.ru",
        "address": "Moscow, Pushkina st."
      }
    }
  ]
}

31 / 36HTTP API: Änderungsantwort

Originalfolie 31: HTTP API: Änderungsantwort
Originalfolie · RU Diese Stelle im Video

Vortragstext

Bei Änderungen wird normalerweise dieselbe Kennung zurückgegeben. Hier ist attributes leer. In einer Mutationsanfrage kann neben records aber auch angegeben werden, welche Attribute aus dem Ergebnis zurückkommen sollen. Das wird unterstützt und an einigen Stellen genutzt; dann wäre attributes gefüllt.

Beim Anlegen eines Records würde die Antwort die Kennung der neuen Entität enthalten.

Beispiel aus der Folie

{
  "records": [
    {
      "id": "emodel/person@admin",
      "attributes": {}
    }
  ]
}

32 / 36HTTP API: Records anlegen

Originalfolie 32: HTTP API: Records anlegen
Originalfolie · RU Diese Stelle im Video

Vortragstext

Das Anlegen nutzt denselben Mutationsendpoint. Bei emodel/person@ steht nach @ nichts: Das kennzeichnet eine neue Entität.

Wird eine lokale id als Attribut angegeben und existiert bereits ein Record mit dieser Kennung, ist eine Aktualisierung möglich; andernfalls wird er mit der angegebenen Kennung angelegt. Hier übergeben wir pk.simonov, email und address.

Beispiel aus der Folie

POST /gateway/records/mutate

{
  "records": [
    {
      "id": "emodel/person@",
      "attributes": {
        "id": "pk.simonov",
        "email": "pavel.simonov@citeck.ru",
        "address": "Tomsk, Pushkina st."
      }
    }
  ]
}

33 / 36HTTP API: Antwort auf das Anlegen

Originalfolie 33: HTTP API: Antwort auf das Anlegen
Originalfolie · RU Diese Stelle im Video

Vortragstext

Die Antwort ist wie bei einer Änderung aufgebaut, enthält aber die Kennung des angelegten Records: hier emodel/person@pk.simonov.

Beispiel aus der Folie

{
  "records": [
    {
      "id": "emodel/person@pk.simonov",
      "attributes": {}
    }
  ]
}

34 / 36HTTP API: Records löschen

Originalfolie 34: HTTP API: Records löschen
Originalfolie · RU Diese Stelle im Video

Vortragstext

Zum Löschen senden wir eine Liste von Kennungen. Für jeden Record enthält die Antwort einen Operationsstatus: OK oder ein anderes Ergebnis.

Beispiel aus der Folie

POST /gateway/records/delete

{
  "records": [
    "emodel/person@admin",
    "emodel/person@pushkin"
  ]
}

{
  "statuses": [
    "OK",
    "OK"
  ]
}

35 / 36Das Demoprojekt

Originalfolie 35: Das Demoprojekt
Originalfolie · RU Diese Stelle im Video

Vortragstext

Damit endet der Hauptteil. Es gibt ein Demoprojekt zum Herunterladen und Ausführen. Es ist unabhängig von unserer Plattform und enthält die Records-API-Bibliothek sowie eine Testdatenquelle, die Daten im Speicher hält.

Wir sind im Frontend-Track, aber die API bietet auch im Backend interessante Aspekte. Sie ist für beide Seiten geeignet: Der Client fordert eine Darstellung an, die Quelle liefert ihre eigene, und die Attributsyntax verbindet beides. Bei Bedarf konvertiert die Engine Zeichenfolgen in Zahlen oder umgekehrt.

https://github.com/Citeck/ecos-records-demo

Fragen nach dem Vortrag

Gibt es einen Frontend-Client?

Frage: Gibt es einen fertigen Client, oder sollen die Anfragen direkt, etwa mit Axios, versendet werden?

Antwort: Unsere Plattform hat einen Client, hauptsächlich zur Optimierung. Wenn mehrere Komponenten Benutzerdaten oder Attribute verschiedener Entitäten benötigen, fasst er die Anfragen zusammen. Aber gerade darin liegt der Vorteil: Man kann normales JSON erzeugen und jeden HTTP-Client verwenden. Eine zusätzliche Hülle gibt es innerhalb der Plattform.

Kann eine universelle API alle Daten lesen?

Frage: Wenn der Client nicht an Geschäftsobjekte gebunden ist, kann er dann sämtliche Informationen sammeln?

Antwort: Ich meinte, dass das Frontend nicht von bestimmten Geschäftsobjekten abhängen sollte, weil sie dynamisch sind und zur Laufzeit entstehen. Die Rechte-Engine der Plattform begrenzt den Zugriff: Records ohne Leseberechtigung können nicht abgefragt werden. Zusätzlich lassen sich Lesen und Ändern einzelner Attribute verbieten.

Die Zugriffskontrollen gehören zur Implementierung von Record Source oder Records DAO. Wir haben gemeinsame Implementierungen für unterschiedliche Typen; das liegt jedoch außerhalb des eigentlichen API-Protokolls.

Wie funktionieren Filter und Suchen in verknüpften Daten?

Frage: Gibt es Auswahlbedingungen und Suchmöglichkeiten, auch in verschachtelten Referenzdaten?

Antwort: Skalare können Argumente haben, oft für Suchvorgänge. Ihre Verarbeitung bestimmt die jeweilige Implementierung. Für allgemeine Suchen gibt es Records Query: Quelle, Sprache, Bedingungen und Paginierung. Eine universelle Tabelle sendet ein einheitliches Anfrageformat an die ausgewählte Quelle und erhält unabhängig vom Datentyp eine Antwort.

Auch die Suche über Assoziationen wurde ergänzt. Liegen die Daten im selben Datenbankschema, kann ein Pfad wie user.email verwendet werden. Das Backend erkennt user als Assoziation, ermittelt Typ und sourceId der verknüpften Entität, findet ihre Tabelle und fügt der SQL-Abfrage einen JOIN hinzu.

Wie komplex dürfen Abfragen sein?

Frage: Gibt es eine Grenze für die Komplexität von Abfragen?

Antwort: Die gemeinsame Engine deckt nicht jede denkbare Abfrage ab. Sie unterstützt Grundoperationen wie gleich, ungleich, beginnt mit und endet mit sowie die erwähnten Suchen in verknüpften Tabellen. Für komplexere Aufgaben kann eine eigene Datenquelle erforderlich sein.