Erste Schritte mit audioXchange GraphQL

Allgemeiner Überblick

GraphQL ähnelt im Prinzip SQL, ist aber nicht auf den Einsatz in Datenbanken ausgerichtet, sondern ist eine Abfragesprache für APIs im Web und in der Cloud. Die Sprache ist unabhängig von einer bestimmten Programmiersprache, einer bestimmten Plattform oder bestimmten Protokollen und kann daher universell und technologieübergreifend eingesetzt werden. Bei GraphQL gibt der Client an, welche Daten er vom Server abrufen möchte, was die Sprache wesentlich schlanker macht als REST, da keine unnötigen Daten übertragen werden müssen. Obwohl das Lesen von Daten über sogenannte “Queries” unterstützt wird, können Daten auch durch “Mutationen” verändert werden.

Es ist wichtig zu wissen, dass GraphQL mit anderen Statuscodes antwortet als eine REST-API. In den meisten Fällen wird GraphQL einen Statuscode 200 zurückgeben. Andere Statuscodes können Server-Probleme (5xx HTTP-Codes, 1xxx WebSocket-Codes) oder Client-Probleme wie GraphQL-Syntax oder Autorisierung (4xx HTTP-Codes) beinhalten.

Für den allgemeinen Wissensaufbau empfehlen wir diese Seiten:

Zentrale Konzepte

GraphQL

GraphQL ist eine Spezifikation dafür, wie man mit einer API kommuniziert. Sie wird in der Regel über HTTP verwendet, wobei die Hauptidee darin besteht, eine “Abfrage” an einen HTTP-Endpunkt zu stellen, anstatt verschiedene HTTP-Endpunkte für verschiedene Ressourcen anzusteuern.

GraphQL wurde für Entwickler von Web-/Mobilanwendungen (HTTP-Clients) entwickelt, damit sie API-Aufrufe tätigen können, um genau die Daten abzurufen, die sie von ihren Backend-APIs benötigen.

GraphQL-Dokument

Der Inhalt eines GraphQL-Query-Strings wird als GraphQL-Dokument bezeichnet. Hier ist ein Beispiel:

query {
  campaignBuyer {
    findCampaigns {
      id
      name
      rowVersion
    }
  }
}

GraphQL-Operation

Eine GraphQL-Operation kann vom Typ

  • query (ein reiner Lesezugriff)
  • mutation (ein Schreibvorgang gefolgt von einem Abruf)

Hier sind zwei Beispiele:

query {
  campaignBuyer {
    findCampaigns {
      id
      name
      rowVersion
      advertiser {
        id
      }
      campaignElements {
        id
        name
      }
    }
  }
}

Im obigen Beispiel wählt die Query Informationen über campaigns mit den Attributen id, name, rowVersion, advertiserId, campaignElements.id und campaignElements.name aus.

mutation {
  campaignBuyer {
    createCampaign(campaign: {
      advertiserId: "xxxxxxxx-advertiserId-xxxxxxxxxxxx"
      buyerId: "xxxxxxxx-buyerId-xxxxxxxxxxxx"
      name: "Eine Kampagne"
    }) {
      id
      name
      advertiser {
        id
      }
      rowVersion
    }
  }
}

Im obigen Beispiel wird durch die Mutation eine campaign mit den genannten Attributen erstellt.

OAuth

OAuth 2.0 ist ein Autorisierungsprotokoll. Es ist in erster Linie als Mittel zur Gewährung des Zugriffs auf eine Reihe von Ressourcen, z. B. entfernte APIs oder Benutzerdaten, konzipiert.

OAuth 2.0 verwendet Zugriffstoken. Ein Access Token ist ein Datenelement, das die Berechtigung zum Zugriff auf Ressourcen im Namen des Endbenutzers darstellt. OAuth 2.0 definiert kein spezielles Format für Access Tokens. In einigen Kontexten wird jedoch häufig das JSON-Web-Token-Format (JWT) verwendet. Dies ermöglicht es den Token-Ausstellern, Daten in den Token selbst aufzunehmen. Außerdem können Access Tokens aus Sicherheitsgründen mit einem Ablaufdatum versehen sein.

Wie funktioniert das?

Bevor OAuth 2.0 verwendet werden kann, muss der Client seine eigenen Anmeldedaten, eine Client-ID und ein Client-Geheimnis, vom Autorisierungsserver erhalten, um sich zu identifizieren und zu authentifizieren, wenn er ein Zugriffstoken anfordert.

Bei der Verwendung von OAuth 2.0 werden die Zugriffsanfragen vom Client initiiert, z. B. von einer mobilen App, einer Website, einer Smart-TV-App, einer Desktop-Anwendung usw. Die Token-Anforderung, der Austausch und die Antwort folgen diesem allgemeinen Ablauf:

Der Client fordert beim Autorisierungsserver eine Autorisierung an (Autorisierungsanfrage) und gibt dabei die Client-ID und das Geheimnis zur Identifizierung an; außerdem stellt er die Bereiche und einen Endpunkt-URI (Redirect-URI) bereit, an den das Zugriffstoken oder der Autorisierungscode gesendet werden soll.

Der Autorisierungsserver authentifiziert den Client und prüft, ob die angeforderten Bereiche zulässig sind.

Der Eigentümer der Ressource interagiert mit dem Autorisierungsserver, um den Zugriff zu gewähren.

Der Autorisierungsserver leitet den Client entweder mit einem Autorisierungscode oder einem Zugriffstoken zurück, je nach Art der Gewährung, wie im nächsten Abschnitt erläutert wird. Es kann auch ein Refresh Token zurückgegeben werden.

Mit dem Access Token fordert der Client den Zugriff auf die Ressource vom Ressourcenserver an.

Keycloak

Keycloak ist eine Open-Source-Lösung für das Identitäts- und Zugriffsmanagement. Benutzer können sich mit Keycloak und nicht mit einzelnen Anwendungen authentifizieren. So müssen sich die Anwendungen nicht mit Anmeldeformularen, der Authentifizierung von Benutzern und der Speicherung von Benutzern befassen. Einmal bei Keycloak eingeloggt, müssen sich die Benutzer nicht erneut anmelden, um auf verschiedene Anwendungen zuzugreifen.

Wie funktioniert das?

Der Benutzer klickt auf eine Anmeldeschaltfläche in der Anwendung.

Die Anwendung generiert eine Authentifizierungsanfrage.

Die Authentifizierungsanforderung wird an den Benutzer mit 302 redirect gesendet.

Der Benutzer wird an den Autorisierungsendpunkt weitergeleitet und Keycloak zeigt dem Benutzer die Anmeldeseite an. Der Benutzer gibt seinen Benutzernamen und sein Passwort ein und schickt das Formular ab.

Nachdem keycloak die Anmeldedaten des Benutzers überprüft hat, erstellt es einen Autorisierungscode, der an die Anwendung zurückgegeben wird.

Die Anwendung tauscht den Autorisierungscode gegen das ID-Token und das Refresh-Token aus. Das ID Token ist standardmäßig ein signiertes JSON Web Token (JWT).

audioXchange Playground

GraphQL ist eine beliebte und weit verbreitete Abfragesprache, die sich als Alternative zum REST-Ansatz versteht. Der GraphQL Playground ist ein Tool mit einer integrierten GraphQL-Entwicklungsumgebung (IDE), das zur Verbesserung der Entwicklungsabläufe beiträgt.

Autorisierung

Playground_Login Im Playground gibt es einen Anmeldebildschirm. Sie authentifizieren sich, indem Sie sich als Käufer oder als Verkäufer anmelden. Es wird ein Bearer Token generiert und in den Playground übernommen, dies gilt auch für alle offenen Tabs auf dieser Seite.

Unter “HTTP HEADERS” sehen Sie das Bearer-Token. Dieser würde wie folgt aussehen:

{
  "authorization": "Bearer <TOKEN>"
}

Anleitung

Wie man sich das aktuelle Schema herunterlädt

Das aktuelle Schema kann über die folgenden Links für die Umgebungen heruntergeladen werden

Wie man mit dem audioXchange Playground arbeitet

Um die Umgebung einzurichten, müssen Sie zunächst einen gültigen Endpunkt für die Interaktion mit der API angeben. In diesem Fall ist es: https://int-api.audioxchange.de/ui/playground/

Versuchen Sie, die folgende Abfrage in den Playground zu schreiben, um ein besseres Gefühl für die GraphQL-Syntax zu bekommen. Sie werden schnell feststellen, dass der Playground mit seiner Autovervollständigung versucht zu helfen. Natürlich können Sie die Attribute variieren. Fügen Sie nur die Attribute ein, die für Sie wichtig sind. Hier ist ein Beispiel:

query {
  campaignBuyer {
    findCampaigns {
      id
      rowVersion
      name
      shortName
      state
      regionalBookingAllowed
      state

      campaignElements {
        id
        name
        periodBegin
        periodEnd
        advertiserProduct {
            id
            name
        }

        plans {
          id
          name
          state
          periodBegin
          periodEnd
          comment

          planElements {
            id
          }
        }
      }
    }
  }
}

Wie man mit Variablen arbeitet

Das folgende Beispiel zeigt, wie man einen Request, genauer eine Mutation, erstellt.

mutation {
  campaignBuyer {
    createRequest (
      request: {
        buyerFreeSpotSpreadingExpectation: [
          {
            freeSpotCalcType: NT_ON_TOP
            freeSpotReference: FREE_SPOT_01
            percentage: 0.05
          }
        ]
        buyerPriceCalcDetails: [
          {
            index: 0
            name: "Ein Preis"
            type: CASH_DISCOUNT
          }
        ]
        requestStreamId: "xxxxxxxx-requestStreamId-xxxxxxxxxxxx"
      }
    ) {
      requestStreamSequenceNumber
      progressState
      createOfferOnly
      buyerBusinessKey
      buyerPriceCalcDetails {
        index
        name {
          value
        }
        calculationType {
          value
        }
        percentage {
          value
        }
        calculationRule {
          value
        }
      }
      id
    }
  }
}

Dies sind die Attribute, die angezeigt werden sollen, aber wir müssen auch die Variablen übergeben, damit es funktioniert. Variablen vereinfachen GraphQL-Queries und -Mutationen, da Sie Daten separat übergeben können. Eine GraphQL-Query kann in zwei Abschnitte unterteilt werden: einen für die Query oder Mutation und einen für die Variablen. Variablen können nach der Query oder Mutation deklariert werden und werden wie Argumente an eine Funktion übergeben und beginnen mit $.

Um eine Query erfolgreich zu erstellen, wird die requestStreamId für die Mutation createRequest benötigt. Sie sieht wie folgt aus:

{
  "request":
  {
    "requestStreamId": "xxxxxxxx-requestStreamId-xxxxxxxxxxxx",
    "buyerBusinessKey": "0057",
    "comment": "",
    "createOfferOnly": true
  }
}

Abfragen und Filterfunktion

Sie können die Abfragen mit einer beliebigen Anzahl von Feldern erweitern und diese so oft abfragen, wie Sie möchten. In den audioXchange Playground Docs können Sie alle Felder sehen, die in die Abfragen aufgenommen werden können.

Dies gilt auch für Filter. Wie Sie Abfragen mit Filtern erstellen können, sehen Sie hier in den folgenden Beispielen.

Im ersten Schritt beginnen wir mit einer Abfrage, die nach einem planElement filtert:

query {
  campaignBuyer {
    findRequestStreams(
      filter: {
        planElementId: "xxxxxxxx-planElementId-xxxxxxxxxxxx"
      }
    ) {
      id
    }
  }
}

Mit dieser Abfrage erhalten Sie alle IDs des planElement, für das wir einen Filter gesetzt haben. Natürlich kann die Abfrage mit weiteren Feldern erweitert werden, um die benötigten Informationen zu erhalten.

Lassen Sie uns ein wenig tiefer eintauchen und einige weitere Filter in diese Abfrage einbauen:

query {
  campaignBuyer {
    findRequestStreams(
      filter: {
        planElementId: "xxxxxxxx-planElementId-xxxxxxxxxxxx"
        requests: {
          answer: {
            state: IS_OFFER
          }
        }
      }
    ) {
      id
    }
  }
}

Mit dieser Abfrage erhalten Sie alle IDs des planElement, bei denen die Query eine Antwort enthält, die den state IS_OFFER haben.

Nun möchten wir detailliertere Informationen über die Queries des planElement erhalten, also fügen wir weitere Felder hinzu:

query {
  campaignBuyer {
    findRequestStreams(
      filter: {
        planElementId: "xxxxxxxx-planElementId-xxxxxxxxxxxx"
        requests: {
          answer: {
            state: IS_OFFER
          }
        }
      }
    ) {
      id
      requests(
        filter: {
          answer: {
            state: IS_OFFER
          }
        }
      ) {
        id
        progressState
        requestSpots {
          id
        }
      }
    }
  }
}

Zusätzlich zu der obigen Abfrage wollen wir nun alle Queries filtern, die eine Antwort mit dem state IS_OFFER von einem bestimmten planElement enthalten. Die Informationen, die wir zurückerhalten, sind aufgrund der Filter, die wir den Queries hinzugefügt haben, detaillierter.

Wir fügen weitere Felder zu dieser Abfrage hinzu, um mehr nützliche Informationen zu erhalten. Sie wird wie die folgende Abfrage aussehen:

query {
  campaignBuyer {
    findRequestStreams(
      filter: {
        planElementId: "xxxxxxxx-planElementId-xxxxxxxxxxxx"
        requests: {
          answer: {
            state: IS_OFFER
          }
        }
      }
    ) {
      id
      requests(
        filter: {
          answer: {
            state: IS_OFFER
          }
        }
      ) {
        id
        progressState
        requestSpots {
          id
        }
        answer {
          id
          state
        }
      }
    }
  }
}

Jetzt erhalten wir weitere Informationen über die Queries, die Antworten enthalten.

GraphQL verfügt über eine hilfreiche Funktion, die es uns erlaubt, die Filterblöcke zu benennen, um einen besseren Überblick über die Informationen zu behalten, die wir erhalten. Es ist also erlaubt, sie so zu benennen, wie es uns hilft, zum Beispiel haben wir hier Bezeichnungen hinzugefügt:

myAwesomeId: id
istRequest: requests(filter: {...
sollRequest: requests(filter: {...

Eine mögliche Abfrage kann wie folgt aussehen:

query {
  campaignBuyer {
    findRequestStreams(
      filter: {
        planElementId: "xxxxxxxx-planElementId-xxxxxxxxxxxx"
        requests: {
          answer: {
            state: IS_OFFER
          }
        }
      }
    ) {
      myAwesomeId: id
      istRequest: requests(
        filter: {
          answer: {
            state: IS_OFFER
          }
        }
      ) {
        id
        progressState
        requestSpots {
          id
        }
        answer {
          id
          state
        }
      }
      sollRequest: requests(
        filter: {
          answer: {
            state: CONFIRMED
          }
        }
      ) {
        id
        progressState
        requestSpots {
          id
        }
      }
    }
  }
}

Zum besseren Verständnis sehen Sie hier die vollständige Ausgabe, die wir von GraphQL erhalten, wenn wir diese Query übergeben:

{
  "data": {
    "campaignBuyer": {
      "findRequestStreams": [
        {
          "id": "xxxxxxxx-requestStreamId-xxxxxxxxxxxx",
          "istRequest": [
            {
              "id": "xxxxxxxx-requestId-xxxxxxxxxxxx",
              "progressState": "SELLER_COMMIT",
              "requestSpots": [
                {
                  "id": "xxxxxxxx-requestSpotId-xxxxxxxxxxxx"
                },
                {
                  "id": "xxxxxxxx-requestSpotId-xxxxxxxxxxxx"
                },
                {
                  "id": "xxxxxxxx-requestSpotId-xxxxxxxxxxxx"
                }
              ],
              "answer": {
                "id": "xxxxxxxx-answerId-xxxxxxxxxxxx",
                "state": "IS_OFFER"
              }
            },
            {
              "id": "xxxxxxxx-requestId-xxxxxxxxxxxx",
              "progressState": "SELLER_COMMIT",
              "requestSpots": [
                {
                  "id": "xxxxxxxx-requestSpotId-xxxxxxxxxxxx"
                },
                {
                  "id": "xxxxxxxx-requestSpotId-xxxxxxxxxxxx"
                },
                {
                  "id": "xxxxxxxx-requestSpotId-xxxxxxxxxxxx"
                }
              ],
              "answer": {
                "id": "xxxxxxxx-answerId-xxxxxxxxxxxx",
                "state": "IS_OFFER"
              }
            }
          ],
          "sollRequest": []
        },
        {
          "id": "xxxxxxxx-requestStreamId-xxxxxxxxxxxx",
          "istRequest": [
            {
              "id": "xxxxxxxx-requestId-xxxxxxxxxxxx",
              "progressState": "SELLER_COMMIT",
              "requestSpots": [
                {
                  "id": "xxxxxxxx-requestSpotId-xxxxxxxxxxxx"
                }
              ],
              "answer": {
                "id": "xxxxxxxx-answerId-xxxxxxxxxxxx",
                "state": "IS_OFFER"
              }
            }
          ],
          "sollRequest": []
        },
        {
          "id": "xxxxxxxx-requestStreamId-xxxxxxxxxxxx",
          "istRequest": [
            {
              "id": "xxxxxxxx-requestId-xxxxxxxxxxxx",
              "progressState": "SELLER_COMMIT",
              "requestSpots": [
                {
                  "id": "xxxxxxxx-requestSpotId-xxxxxxxxxxxx"
                },
                {
                  "id": "xxxxxxxx-requestSpotId-xxxxxxxxxxxx"
                },
                {
                  "id": "xxxxxxxx-requestSpotId-xxxxxxxxxxxx"
                }
              ],
              "answer": {
                "id": "xxxxxxxx-answerId-xxxxxxxxxxxx",
                "state": "IS_OFFER"
              }
            }
          ],
          "sollRequest": []
        }
      ]
    }
  },
  "extensions": {}
}

Info Hinweis:
Hinweis: Im Moment ist sollRequest absichtlich leer, da keines der Beispiele bestätigte Daten enthält. Es diente nur dazu, Filtermöglichkeiten aufzuzeigen.

Dokumentations- und Schemareiter

Der API Dokumentationsreiter ist ein sehr hilfreiches Feature von GraphQL Playground. Er ermöglicht eine Vorschau auf GraphQL-Queries, Mutationen, GraphQL-Typ Details und ein einzelnes Feld eines bestimmten Schemas.

Nicht löschbare Werte

Wenn Sie sich die Dokumentation ansehen, werden Sie feststellen, dass einige Attribute ein Ausrufezeichen haben und andere nicht. Zum Beispiel:

type Campaign {
  name: String!
  shortName: String
  state: CampaignState!
  advertiserId: IdOrg!
}

Die Werte, neben denen ein Ausrufezeichen steht, dürfen niemals null, also leer, sein. Ein Attribut ohne Ausrufezeichen kann einen Wert enthalten, muss es aber nicht.

Weiter unten befinden sich die Argumente. Klickt man darauf, öffnet sich eine Leiste mit den Typdetails. Hier sind alle Attribute aufgelistet, die übergeben werden müssen. Als Beispiel wollen wir eine campaign erstellen. Unter campaign: CampaignInput! sehen wir alle Attribute, die übergeben werden müssen, um eine campaign erfolgreich zu erstellen. Playground_TypeDetailsHighlighted