Interoperabilität hat bei co*pilot einen sehr hohen Stellenwert. Anstatt einer vollgestopften und überfrachteten all-in-one Lösung, soll co*pilot ein gut strukturiertes und zielgerichtetes Werkzeug sein, das sich gut mit anderer Software kombinieren und leicht den eigenen Ansprüchen anpassen lässt.
Darum zählen unsere Public-API, der Export und Import von Anfang an zwar zu den wichtigsten und gefragtesten Bausteinen von co*pilot, aber auch zu den aufwändigsten. Alle diese Features sind Querschnittsthemen, die angepasst werden müssen, wenn wir das Datenmodell von co*pilot erweitern oder verändern.
Um dieser Herausforderung in Zukunft besser begegnen zu können führen wir mit GraphQL eine neue Schnittstellentechnologie ein, die sowohl als öffentliche Schnittstelle, als auch als Datenquelle für den Export genutzt werden kann.

Zu den Vorteilen von GraphQL zählen: 

  • Sehr gutes Tooling
  • Selbstdokumentierend
  • Abfragen können an die eigenen Bedürfnisse angepasst werden
  • Tief verschachtelten Daten können in einer Abfrage zusammengefasst werden

Einer der Nachteile einer öffentlichen GraphQL Schnittstelle ist die Gefahr durch sehr komplexe Abfragen eine sehr hohe Last zu erzeugen. Deshalb kann die Schnittstelle nicht mit einer beliebigen Abfrage parametrisiert werden. Stattdessen wird eine Query zentral im Administrationsbereich von co*pilot verwaltet.

Query-Administration

Mit einer GraphQL Schnittstelle kann theoretisch sehr weitreichend auf Daten eines Systems zugegriffen werden. Da wir aber auf ein komplexes Berechtigungs-Feature verzichten wollen, kann in co*pilot eine zentrale query verwaltet werden, die dann wiederum im Export oder in der Public-API verwendet werden kann:

GraphQL Administration

In diesem Beispiel werden allen offenen Aufgaben zurückgeliefert, die in den selektierten Events hinterlegt sind. Der zurückgelieferte Datensatz enthält dann alle wesentlichen Infos zu den Tasks, inklusive des Namens der geordneten Person.

Diese Query-Operation kann nun im Export und in der Public-API verwendet werden. Betrachten wir zunächst die Nutzung im Export.

GraphQL im Export Verwenden

Aktuell folgen die Eingangsdaten im Export einem statischen Schema. Anstatt dieses Schema immer weiter aufzublähen, was gleichzeitig auch eine immer höher werdende Last im Export verursacht, können die Eingangsdaten mit einer GraphQL Abfrage erweitert werden.

Das folgende Preprocessing-Skript erweitert die Events mit Aufgaben und filtert die Events heraus, die keine offenen Aufgaben mehr aufweisen:

  const { keyBy } = deps.lodash;

const eventIds = ctx.events.map(ev => ev.id)

const { data } = queryGraphQL({ 
    operationName: "getEventTasks", 
    variables: { eventIds }
})

const edgesById = keyBy(data.events.edges, e => e.node.id)

function attachTasks(ev) {
  return {
    ...ev,
    tasks: edgesById[ev.id].node.tasks
  }
}

const eventsWithOpenTasks = ctx.events
  .map(attachTasks)
  .filter(e => e.tasks.length > 0)

return {
  ...ctx,
  events: eventsWithOpenTasks
}
  

Ergebnis:

Event Tasks Export

Mit Hilfe von GraphQL können theoretisch auch Exporte für Daten abseits der Events erstellt werden (z.B. Kontakte, Reihen, usw.). Allerdings können diese Exporte aktuell auch nur über Events initiiert werden.
Eine entsprechende Verallgemeinerung der Export-Funktion ist bereits geplant.

GraphQL in der Public-API

Wenn wir auf der Seite “Schnittstellen / Graph” dem Link zur co*pilot Public API folgen, können wir die Operation “getEventTasks” im Browser abrufen. Dafür gibt es zwei Endpunkte:

  • POST /graph
    Dieser Endpunkt kann von GraphQL Clients verwendet werden. Er entspricht einem klassischen GraphQL bis auf eine Ausnahme: Es kann zwar eine “query” übergeben werden, diese wird allerdings ignoriert, da die query bereits im co*pilot Administrationsbereich vordefiniert ist.
  • GET /graph/operations/{name}
    Dieser Endpunkt erlaubt eine einfachere Integration des Endpunkts in andere Anwendungen und kann leichter geteilt werden. Außerdem können GET Enpunkte leichter in Caches wie z.B. AWS Cloudfront verwendet werden.

Der Aufruf der Operation “getEventTasks” liefert über den “POST /graph” Endpunkt, wie erwartet den Event inkl. aller offenen Aufgaben:

Aufruf von “POST /graph” in Swagger UI

GraphQL ist ein sehr mächtiges Tool und erfordert einen verantwortungsvollen Umgang. Ansonsten können ausufernde Queries sehr hohe Lastspitzen erzeugen, die der Performance von co*pilot schaden können.
Wir werden in den kommenden Monaten passende Mechanismen erarbeiten, um unseren Kundinnen eine möglichst flexiblen Umgang mit der Schnittstelle zu ermöglichen. Bis dahin müssen wir unsere Ressourcen mit einem relativ konservativem “Rate-Limiting” schützen.

Rate Limiting

Um die Ressourcen unserer Server zu schonen, ist die Abfragerate je Kunde auf 1 RPS (Request per Second) beschränkt. Werden z.B. gleichzeitig fünf Abfragen ausgeführt, wird die erste Abfrage sofort bearbeitet und die folgenden vier Abfragen in einem Abstand von 1 Sekunde.

Aufgrund dieser Beschränkung ist eine direkte Integration der Schnittstelle z.B. auf Webseiten aktuell nicht zu empfehlen. Wir empfehlen die Nutzung eines Caches (z.B. Cloudflare, AWS Cloudfront, usw.)