Abrufen von dynamisch strukturierten Daten in einem CMS mit GraphQL

GraphQL ist die Schicht, die der Anwendung Daten bereitstellt, daher muss sie den Anforderungen der Anwendung gerecht werden. Aber manchmal erzwingt GraphQL seine eigene Arbeitsweise, und die Anwendung muss angepasst werden, um die GraphQL-Art zu arbeiten.

Diese Situation ist am offensichtlichsten, wenn GraphQL mit einem CMS verwendet wird, da CMS von Natur aus eigensinnig sind und GraphQL ihr Verhalten möglicherweise außer Kraft setzt. Wenn wir nicht von einem CMS abhängig sind und stattdessen eine Site von Grund auf neu erstellen, sollten wir die volle Kontrolle über die Architektur der Site haben und möglicherweise bereit sein, sie für GraphQL anzupassen – und dieser Konflikt wird nicht auftreten.

In diesem Artikel werden wir untersuchen, wie GraphQL beim Datenabruf seine eigenen Vorbedingungen auferlegt, warum dies geschieht und welche Optionen wir haben, um es zu umgehen. Als Beispiel-CMS verwenden wir WordPress.

Verschachtelte Datenebenen abrufen

Jede Anwendung kann über Entitäten verfügen, die untergeordnete Elemente desselben Typs enthalten. Ein Menü enthält beispielsweise Elemente, die Unterelemente haben können, und diese Unterelemente können selbst Unterelemente enthalten, und so weiter für mehrere Ebenen. Ebenso kann ein Kommentar Antworten haben, die selbst Antworten haben können.

Sehen wir uns an, wie Sie mit mehrstufigen Menüs in GraphQL arbeiten. Das Abrufen der Menüdaten in GraphQL beinhaltet das Abfragen der Elemente innerhalb des Menüs für alle verschiedenen Ebenen.

Zum Beispiel in diese Abfrage, das Menü hat drei Ebenen und wir verwenden das Fragment MenuItemProps um dieselben Felder zu holen (id, label, und url) für alle Menüpunkte auf allen Ebenen:

query GetMenu {
  menu(id: 176) {
    id
    items {
      ...MenuItemProps
      children {
        ...MenuItemProps
        children {
          ...MenuItemProps
        }
      }
    }
  }
}

fragment MenuItemProps on MenuItem {
  id
  label
  url
}

Wie zu erkennen ist, spiegelt sich die Anzahl der Ebenen in der GraphQL-Abfrage wider. Da das Menü in der Anwendung drei Ebenen hat, hat die GraphQL-Abfrage drei Verschachtelungsebenen.

In WordPress wird die Erstellung des Menüs jedoch nicht im Voraus durch das Thema oder ein Plugin festgelegt, sondern vom Admin der Site über das konfiguriert Menüs Bildschirm und in der Datenbank gespeichert:

Menüs in WordPress erstellen

Dies stellt ein Problem dar: Wenn ein Site-Administrator dem Menü über die Benutzeroberfläche eine zusätzliche Ebene hinzufügt, wird diese neue Ebene nicht auf der Site angezeigt, wenn sie nicht auch in der GraphQL-Abfrage widergespiegelt wird. Ein Entwickler muss beauftragt werden, den Code zu ändern – fügen Sie die zusätzliche Ebene zur GraphQL-Abfrage hinzu, generieren Sie die ZIP-Datei des Themas neu und installieren Sie sie erneut auf der Site. Erst dann wird die neue Ebene in der Abfrage angezeigt.

Sie könnten vermuten, dass der Vorteil der Verwendung eines CMS so gut wie weg ist, wenn Sie all dies tun müssen.

Tatsächlich wird WordPress nicht nur von Entwicklern verwendet, sondern auch von Nicht-Entwicklern, Menschen, die möglicherweise nicht wissen, dass das Thema ihrer Website aktualisiert werden muss, um ein Menü zu aktualisieren. Dies ist nicht intuitiv und widerspricht der Idee eines CMS, sodass GraphQL seinem Host WordPress seine eigene Arbeitsweise aufzwingen würde.

Sehen wir uns drei mögliche Wege an, wie wir dieses Problem umgehen können.

Erstellen einer größeren anfänglichen GraphQL-Abfragestruktur

Eine einfache Möglichkeit, dieses Problem zu umgehen, besteht darin, die GraphQL-Abfrage zunächst so zu strukturieren, dass mehr Ebenen als benötigt abgerufen werden, damit ein Site-Administrator später noch weitere Ebenen hinzufügen kann, ohne dass ein Entwickler erforderlich ist.

Wenn die Anwendung beispielsweise drei Ebenen benötigt, könnte die GraphQL-Abfrage dennoch Daten für sechs Ebenen abrufen:

query GetMenu {
  menu(id:176) {
    id
    items {
      ...MenuItemProps
      children {
        ...MenuItemProps
        children {
          ...MenuItemProps
          children {
            ...MenuItemProps
            children {
              ...MenuItemProps
              children {
                ...MenuItemProps
              }
            }
          }
        }
      }
    }
  }
}

fragment MenuItemProps on MenuItem {
  id
  label
  url
}

Diese Lösung ist nicht sehr elegant, wird aber in den meisten Fällen funktionieren.

GraphQL anweisen, die Unterebenen zu iterieren

Eine bessere Lösung wäre, GraphQL anzuweisen, die Unterebenen so lange abwärts zu iterieren, bis keine mehr vorhanden sind, dh bis das Feld children ist null. Bei der Arbeit mit PHP beispielsweise kann sich eine Funktion rekursiv selbst aufrufen, bis die Eingabe das Grundelement ist:

function filterFieldArgsWithNullValues(array $fieldArgs): array
{
  return array_filter(
    $fieldArgs,
    function (mixed $elem): bool {
      if (is_array($elem)) {
        $filteredElem = $this->filterFieldArgsWithNullValues($elem);
        return count($elem) === count($filteredElem);
      }
      return $elem !== null;
    }
  );
}

Eine Portierung dieser Strategie auf GraphQL würde ein rekursives Fragment erfordern, bei dem das Fragment erneut auf die children Elemente:

fragment MenuItemProps on MenuItem {
  id
  label
  url
  children {
    ...MenuItemProps
  }
}

Allerdings ist die GraphQL-Spezifikation verbietet, dass Fragmente rekursiv sind, daher ist diese Strategie nicht möglich.

Erstellen Sie eine benutzerdefinierte Anweisung

Eine andere Lösung wäre, eine benutzerdefinierte Anweisung zu erstellen, @recursive, das rekursiv ein Fragment auf die Verbindungen eines Felds und seine Unterverbindungen bis hinunter zum Erreichen der Blattknoten im Graphen anwendet:

query GetMenu {
  menu(id:176) {
    id
    items @recursive(field: "children") {
      ...MenuItemProps
    }
  }
}

fragment MenuItemProps on MenuItem {
  id
  label
  url
}

Aber diese Lösung ist auch durch die GraphQL-Spezifikation verboten, weil die Form der Antwort muss der Form der Anfrage entsprechen, und das würde durch die kompromittiert @recursive Richtlinie.

Lockerung der Verschachtelung in der Abfrage

Die letzte Lösung, die mir in den Sinn kommt, besteht darin, die Verwendung der Verschachtelung in der Abfrage zu lockern und ein einzelnes Feld zu erstellen. itemDataEntries, um eine strukturierte JSONObject mit allen Menüdaten, inklusive aller Ebenen und Unterebenen, wie in diese Abfrage:

query GetMenu {
  menu(id:176) {
    id
    itemDataEntries
  }
}

Die Antwort auf diese Abfrage sieht wie folgt aus:

{
  "data": {
    "menu": {
      "id": 176,
      "itemDataEntries": [
        {
          "id": 735,
          "objectID": "6",
          "parentID": null,
          "label": "About The Tests",
          "url": "https://newapi.getpop.org/about/",
          "children": [
            {
              "id": 1451,
              "objectID": "1133",
              "parentID": "735",
              "label": "Page Image Alignment",
              "url": "https://newapi.getpop.org/about/page-image-alignment/",
              "children": []
            },
            {
              "id": 1452,
              "objectID": "1134",
              "parentID": "735",
              "label": "Page Markup And Formatting",
              "url": "https://newapi.getpop.org/about/page-markup-and-formatting/",
              "children": []
            }
          ]
        },
        {
          "id": 739,
          "objectID": "174",
          "parentID": null,
          "label": "Level 1",
          "url": "https://newapi.getpop.org/level-1/",
          "children": [
            {
              "id": 740,
              "objectID": "173",
              "parentID": "739",
              "label": "Level 2",
              "url": "https://newapi.getpop.org/level-1/level-2/",
              "children": [
                {
                  "id": 741,
                  "objectID": "172",
                  "parentID": "740",
                  "label": "Level 3",
                  "url": "https://newapi.getpop.org/level-1/level-2/level-3/",
                  "children": []
                },
                {
                  "id": 1453,
                  "objectID": "747",
                  "parentID": "740",
                  "label": "Level 3a",
                  "url": "https://newapi.getpop.org/level-1/level-2/level-3a/",
                  "children": []
                },
                {
                  "id": 1454,
                  "objectID": "748",
                  "parentID": "740",
                  "label": "Level 3b",
                  "url": "https://newapi.getpop.org/level-1/level-2/level-3b/",
                  "children": []
                }
              ]
            }
          ]
        },
        {
          "id": 742,
          "objectID": "146",
          "parentID": null,
          "label": "Lorem Ipsum",
          "url": "https://newapi.getpop.org/lorem-ipsum/",
          "children": []
        }
      ]
    }
  }
}

Diese Methode hat folgende Vorteile:

  • Die abgerufenen Daten werden vollständig von der Benutzeroberfläche gesteuert und spiegeln wider, was in der Datenbank gespeichert ist, wie sie ist
  • Infolgedessen muss die Anwendung nie aktualisiert werden, nachdem ein Benutzer dem Menü eine beliebige Anzahl zusätzlicher Ebenen hinzugefügt hat

Allerdings hat diese Methode auch einen klaren Nachteil: Wir verlieren die starke Typisierung von GraphQL. Anstatt einen Menüpunkt mit stark typisierten Feldern zu erhalten — url haben eine URL, label haben eine String, objectID als ein ID, und so weiter – wir erhalten ein einfaches Objekt, das von den GraphQL-Tools und -Clients wie Apollo oder Relay nicht verstanden wird. Daher werden wir die Vorteile von GraphQL nicht wirklich optimal nutzen.

Dynamische Elemente dem Schema zuordnen

Wir kommen zu einem anderen Problem, wenn wir Entitäten abrufen müssen, die von der Benutzeroberfläche gesteuert und in der Datenbank gespeichert werden. Zum Beispiel ermöglicht WordPress Einstellungsmenüs, deren Optionsnamen dynamisch erstellt werden, und Metawerte, die entweder von Site-Administratoren oder den von ihnen ausgewählten Themen und Plugins dynamisch erstellt werden. Dies bedeutet, dass diese Menüoptionen und Metawerte dem GraphQL-Server nicht bekannt sind oder vorab mit dem GraphQL-Server geteilt werden.

In diesem Abschnitt untersuchen wir, wie die Einstellungen von zwei GraphQL-Servern für WordPress bereitgestellt werden. WPGraphQL und das GraphQL-API für WordPress.

Liefern von Einstellungen in WPGraphQL

WPGraphQL hat die Einstellungen unter einem speziellen Typ abgebildet, GeneralSettings, das diese Felder enthält:

# The general setting type
type GeneralSettings {
  # A date format for all date strings.
  dateFormat: String
  # Site tagline.
  description: String
  # This address is used for admin purposes, like new user notification.
  email: String
  # WordPress locale code.
  language: String
  # A day number of the week that the week should start on.
  startOfWeek: Int
  # A time format for all time strings.
  timeFormat: String
  # A city in the same timezone as you.
  timezone: String
  # Site title.
  title: String
  # Site URL.
  url: String
}

In diesem Zusammenhang ist WPGraphQL rechthaberisch, da es im Voraus entschieden hat, welche Optionen wichtig genug sind, um bereits in sein Schema aufgenommen zu werden. Wenn es eine Option gibt, die die Anwendung benötigt und sie nicht vorhanden ist, z. B. homeurl, die sich von der Site-URL unterscheidet – dann muss der Entwickler das Schema erweitern.

Bereitstellung von Einstellungen mit der GraphQL-API für WordPress

Die GraphQL-API für WordPress verfolgt einen anderen Ansatz: Die Optionen sind selbst nicht im Schema fest kodiert, sondern werden über ein einziges aufgerufen option Feld, das den Namen der Option erhält:

type Root {
  option(name: String): AnyScalar
}

Nicht alle Optionen sollen über die API verfügbar gemacht werden; Der Administrator der Site muss sie in den Plugin-Einstellungen explizit auf die sichere Liste setzen, entweder mit ihrem vollständigen Namen oder einer Regex:

Optionen für sichere Listen der GraphQL-API

Jetzt kann die Abfrage die Optionen auf der sicheren Liste abrufen:

{
  siteURL: option(name: "home")
  siteName: option(name: "blogname")
  siteDescription: option(name: "blogdescription")
}

Dieser Ansatz ist nicht im Voraus meinungswürdig. Wenn die Anwendung eine zusätzliche Option benötigt, kann sie der API als Konfiguration zur Verfügung gestellt und in der Datenbank gespeichert werden, sodass der Code nicht geändert werden muss, um das GraphQL-Schema zu erweitern.

Der Nachteil dieser Vorgehensweise besteht darin, dass die strikte Typisierung dennoch verloren geht. Im Ansatz von WPGraphQL ist das Feld description hat den Typ String und startOfWeek hat Typ Int, aber im GraphQL-API für WP-Ansatz ist die option Feld ist immer vom Typ AnyScalar, ein benutzerdefinierter Typ, der verwendet wird, um alle eingebauten Typen darzustellen (String, Int, Float, Boolean, und ID).

Eine mögliche Lösung: Automatische Generierung der GraphQL-API

Bisher haben wir gesehen, dass die Lösungen für die beiden oben aufgezeigten unterschiedlichen Anwendungsfälle mehrere Nachteile haben:

  • Es muss eine größere Abfrage erstellt werden, um für die Zukunft zu planen
  • Das GraphQL-Schema ist eigenwillig und entscheidet im Voraus, welche Felder verfügbar sind
  • Das GraphQL-Schema hat keine starke Typisierung

Eine Lösung, um diese Nachteile zu vermeiden, besteht darin, die GraphQL-API automatisch aus den verschiedenen Eingaben zu generieren, einschließlich der Daten, die vom Administrator der Site in das CMS eingegeben wurden. Mit einer solchen Methode wäre es nicht erforderlich, einen Entwickler zu beschäftigen, um das Schema zu erweitern, und wir verlieren nicht die Vorteile der strengen Typisierung von GraphQL.

Wie würde das aussehen? In Bezug auf das Beispiel zur Bereitstellung von Einstellungen würde der GraphQL-Server es uns ermöglichen, die Optionen auszuwählen, die in der API über eine Konfigurationsseite bereitgestellt werden müssen, und die Definition ihrer Typen anzufordern. Mit dieser Eingabe könnte der GraphQL-Server dann das GraphQL-API-Schema generieren und ein Feld pro ausgewählter Option hinzufügen, das vom erwarteten Typ wäre.

Optionen für die Zuordnung von DB-Optionen

Diese Lösung ist jedoch nicht einfach zu implementieren.

SDL-erste GraphQL-Server

Insbesondere bei der Arbeit mit SDL-first GraphQL-Servern müssen wir das GraphQL-Schema über SDL bereitstellen, was der Vorstellung widerspricht, dass der Server das Schema automatisch generieren lässt. Bei Code-First-Servern gibt es keinen inhärenten Konflikt, aber die Implementierung wird wahrscheinlich dennoch nicht trivial sein, da wir die Funktionen des CMS erfüllen müssen, mit dem wir arbeiten.

Unsere beste Wahl ist, dass der GraphQL-Server diesen Ansatz bereits implementiert und für ein bestimmtes CMS angepasst ist. Da beispielsweise sowohl WPGraphQL als auch die GraphQL-API für WordPress maßgeschneidert für WordPress sind, könnten sie das Schema unter Berücksichtigung aller Eingaben von diesem CMS, einschließlich der vom Benutzer bereitgestellten Menüs und der Optionen aus der DB, sehr gut automatisch erstellen.

JavaScript-basierte Server

Für JS-basierte Server gibt es bereits Dienste, die das GraphQL-Schema automatisch generieren, wie z PostGraphile und Hasura. Diese Dienste verwenden jedoch die DB als Eingabe zum Generieren des GraphQL-Schemas (PostgreSQL für PostGraphile; PostgreSQL und einige andere für Hasura) und kein CMS. Daher erfordern benutzerdefinierte Funktionen, die von einigen bestimmten CMS angeboten werden, wie beispielsweise das Konfigurieren, welche Optionen verfügbar gemacht werden sollen oder wie viele Ebenen verschachtelte Kommentare umfassen können, eine benutzerdefinierte Entwicklung.

Wenn die API von Drittanbietern verwendet werden soll, sollte die automatische Generierung des GraphQL-Schemas sorgfältig erfolgen und jeweils eine bestimmte Funktion angehen. Dies liegt daran, dass dieser Ansatz produziert möglicherweise keine gut gestaltete API, die Ihre Benutzer verwirren und vor allem Informationen preisgeben könnten, die die API unsicher machen. Wir müssen sicherstellen, dass die Eingabedaten, die admin-seitige Informationen (wie den Namen einer Tabellenspalte) enthalten, nicht an die öffentlich zugängliche Schicht gelangen.

Fazit

Wir haben zwei unterschiedliche Umstände untersucht, unter denen GraphQL eine Arbeitsweise auferlegt, die möglicherweise der Funktionsweise einer Anwendung widerspricht, insbesondere solchen, die auf einem CMS wie WordPress basieren.

Der erste Fall betrifft die Verschachtelung von Menüpunkten und Kommentaren. Der beste Weg, damit umzugehen, besteht darin, der GraphQL-Abfrage für Menüelemente und Kommentare zusätzliche Verschachtelungsebenen hinzuzufügen, auch wenn diese zusätzlichen Ebenen anfänglich nicht benötigt werden, damit der Administrator der Site diese Anzahl von der Benutzeroberfläche aus erhöhen kann, ohne dass dies erforderlich ist einen Entwickler zu beschäftigen, um Code zu aktualisieren und die Änderungen bereitzustellen.

Im zweiten Fall werden dynamisch erstellte Daten abgefragt, wie es bei Einstellungen und Metawerten der Fall ist. Wir haben zwei Ansätze untersucht, die von WPGraphQL und der GraphQL-API für WP implementiert wurden, die beide Nachteile haben. Die beste Lösung wäre, sie zu kombinieren, indem das GraphQL-Schema dynamisch aus der Konfiguration generiert wird, die vom Administrator der Site über eine Benutzeroberfläche bereitgestellt wird.

Überwachen Sie fehlgeschlagene und langsame GraphQL-Anfragen in der Produktion

Obwohl GraphQL einige Funktionen zum Debuggen von Anfragen und Antworten bietet, wird es schwieriger, sicherzustellen, dass GraphQL Ressourcen zuverlässig für Ihre Produktions-App bereitstellt. Wenn Sie sicherstellen möchten, dass Netzwerkanfragen an das Back-End oder die Dienste von Drittanbietern erfolgreich sind, Probiere LogRocket.https://logrocket.com/signup/

LogRakete ist wie ein DVR für Web-Apps, der buchstäblich alles aufzeichnet, was auf Ihrer Website passiert. Anstatt zu raten, warum Probleme auftreten, können Sie problematische GraphQL-Anfragen aggregieren und melden, um die Ursache schnell zu verstehen. Darüber hinaus können Sie den Status des Apollo-Clients verfolgen und die Schlüssel-Wert-Paare von GraphQL-Abfragen überprüfen.

LogRocket instrumentiert Ihre App, um grundlegende Performance-Timings wie Seitenladezeit, Zeit bis zum ersten Byte, langsame Netzwerkanforderungen aufzuzeichnen und auch Redux-, NgRx- und Vuex-Aktionen/-Status zu protokollieren. .

Thanks for Reading

Enjoyed this post? Share it with your networks.

Get more stuff

Subscribe to our mailing list and get interesting stuff and updates to your email inbox.

Thank you for subscribing.

Something went wrong.

Leave a Feedback!