1. Einleitung

Die CaaS-Plattform stellt das Bindeglied zwischen FirstSpirit und der Endanwendung des Kunden dar. Die REST-Schnittstelle empfängt Informationen und aktualisiert diese in der internen Persistenzschicht der CaaS-Plattform. Eine Aktualisierung der Daten in der Endanwendung des Kunden erfolgt durch Requests an die REST-Schnittstelle.

Die CaaS-Plattform umfasst folgende Komponenten, die als Docker-Container vorliegen:

REST-Schnittstelle (caas-rest-api)

Die REST-Schnittstelle dient sowohl der Übertragung als auch der Abfrage von Daten in das bzw. aus dem CaaS-Repository. Dafür stellt sie einen REST-Endpoint bereit, der von beliebigen Diensten verwendet werden kann. Sie unterstützt zudem Authentifizierung und Autorisierung.

Zwischen CaaS Version 2.11 und 2.13 (inklusive) wurde die Authentifizierungs- und Autorisierungsfunktionalität durch einen separaten Security Proxy bereitgestellt.

CaaS-Repository (caas-mongo)

Das CaaS-Repository ist nicht vom Internet aus erreichbar und ist innerhalb der Plattform nur von der REST-Schnittstelle aus erreichbar. Es dient als Ablage aller Projektdaten sowie interner Konfiguration.

2. Technische Voraussetzungen

Der Betrieb der CaaS-Plattform ist mit Kubernetes zu realisieren.

Sollten Sie sich nicht in der Lage fühlen, einen Kubernetes-Cluster betreiben, konfigurieren, überwachen und Betriebsprobleme der Cluster-Infrastruktur entsprechend analysieren und beheben zu können, so raten wir ausdrücklich von einem On-Premises Betrieb ab und verweisen auf unser SaaS-Angebot.

Da die CaaS-Plattform als Helm-Artefakt ausgeliefert wird, muss Helm als Client verfügbar sein.

Es ist wichtig, dass Helm auf sichere Art und Weise installiert ist. Nähere Informationen dazu sind in der Helm-Installationsanleitung enthalten.

Für die Systemvoraussetzungen konsultieren Sie bitte das technische Datenblatt der CaaS-Plattform .

3. Installation und Konfiguration

Die Einrichtung der CaaS-Plattform für den Betrieb mit Kubernetes erfolgt über den Einsatz von Helm-Charts. Diese sind Bestandteil der Auslieferung und enthalten bereits alle erforderlichen Komponenten.

Die nachfolgenden Unterkapitel beschreiben die erforderlichen Installations- und Konfigurationsschritte.

3.1. Import der Images

Die Einrichtung der CaaS-Plattform erfordert im ersten Schritt den Import der Images in Ihre zentrale Docker-Registry (z. B. Artifactory). Die Images sind in der Auslieferung in der Datei caas-docker-images-20.0.0.zip enthalten.

Die Credentials für den Zugriff des Clusters auf die Registry müssen bekannt sein.

Die für den Import notwendigen Schritte entnehmen Sie bitte der Dokumentation der von Ihnen eingesetzten Registry.

3.2. Konfiguration des Helm-Charts

Nach dem Import der Images ist die Konfiguration des Helm-Charts notwendig. Dieser ist Bestandteil der Auslieferung und in der Datei caas-20.0.0.tgz enthalten. Eine Standardkonfiguration des Charts ist bereits in der values.yaml-Datei vorgenommen. Alle Parameter, die in dieser values.yaml angegeben sind, können mit einer manuell anzulegenden custom-values.yaml durch einen spezifischen Wert überschrieben werden.

3.2.1. Authentifizierung

Alle Authentifizierungs-Einstellungen zur Kommunikation mit bzw. innerhalb der CaaS-Plattform werden im credentials-Block der custom-values.yaml festgelegt.

So finden sich hier Benutzernamen und Standard-Passwörter sowie der CaaS-Master API Key. Es wird dringend empfohlen, die Standard-Passwörter und den CaaS-Master API Key anzupassen.

Alle gewählten Passwörter müssen alphanumerisch sein. Andernfalls treten Probleme in Verbindung mit dem CaaS auf.

Der CaaS-Master API Key wird während der Installation der CaaS-Plattform automatisch angelegt und ermöglicht demnach die direkte Benutzung der REST-Schnittstelle.

3.2.2. CaaS-Repository (caas-mongo)

Die Konfiguration des Repositories umfasst zwei Parameter:

storageClass

Die Möglichkeit des Überschreibens von Parametern aus der values.yaml-Datei betrifft vor allem auch den Parameter mongo.persistentVolume.storageClass.

Aus Performance-Gründen empfehlen wir, dass das unterliegende Dateisystem der MongoDB mit XFS provisioniert ist.

clusterKey

Für den Authentifizierungsschlüssel des Mongo-Cluster wird eine Standardkonfiguration mit ausgeliefert. Der Schlüssel kann im Parameter credentials.clusterKey festgelegt werden. Es wird dringend empfohlen, für den Produktivbetrieb mit dem folgenden Befehl einen neuen Schlüssel zu erzeugen:

openssl rand -base64 756

Dieser Wert darf nur während der initialen Installation verändert werden. Wird er zu einem späteren Zeitpunkt geändert, kann dies zu einer dauerhaften Nichtverfügbarkeit der Datenbank führen, die nur noch manuell reparierbar ist.

3.2.3. Docker-Registry

Eine Anpassung der Parameter imageRegistry und imageCredentials ist notwendig, um die verwendete Docker-Registry zu konfigurieren.

Beispielkonfiguration in einer custom-values.yaml
imageRegistry: docker.company.com/e-spirit

imageCredentials:
   username: "username"
   password: "special_password"
   registry: docker.company.com
   enabled: true

3.2.4. Ingress Konfigurationen

Ingress-Definitionen steuern den eingehenden Datenverkehr auf die jeweilige Komponente. Die im Chart enthaltenen Definitionen werden mit der Standardkonfiguration jedoch nicht erstellt. Die Parameter restApi.ingress.enabled und restApi.ingressPreview.enabled erlauben die Ingress-Konfiguration für die REST-Schnittstelle.

Die Ingress-Definitionen des Helm-Charts setzen den NGINX Ingress Controller voraus, da Annotationen sowie die Klasse dieser konkreten Implementierung verwendet werden. Sollten Sie eine abweichende Implementierung einsetzen, so müssen Sie die Annotationen und das Attribut spec.ingressClassName der Ingress-Definitionen in Ihrer custom-values.yaml-Datei entsprechend an Ihre Gegebenheiten anpassen.

Ingress-Erzeugung in einer custom-values.yaml
restApi:
   ingress:
      enabled: true
      hosts:
         - caas.company.com
   ingressPreview:
      enabled: true
      hosts:
         - caas-preview.company.com

Sind die Einstellungsmöglichkeiten für den spezifischen Anwendungsfall nicht ausreichend, lässt sich der Ingress auch selbst eigenständig erzeugen. In diesem Fall ist der entsprechende Parameter auf den Wert enabled: false zu setzen. Das folgende Codebeispiel bietet eine Orientierung für die Definition.

Ingress-Definition für die REST-Schnittstelle
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
   labels:
   name: caas
spec:
   rules:
   - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: caas-rest-api
            port:
              number: 80
   host: caas-rest-api.mydomain.com
   ingressClassName: my-ingress-caas

3.3. Installation des Helm-Charts

Nach der Konfiguration des Helm-Charts ist dieser in den Kubernetes-Cluster zu installieren. Die Installation erfolgt über die nachfolgenden Befehle, die im Verzeichnis des Helm-Charts ausgeführt werden müssen.

Installation des Chart
kubectl create namespace caas
helm install RELEASE_NAME . --namespace=caas --values /path/to/custom-values.yaml

Der Name des Release kann frei gewählt werden.

Soll der Namespace einen anderen Namen tragen, müssen Sie die Angaben innerhalb der Befehle entsprechend ersetzen.

Soll ein bereits existierender Namespace verwendet werden, so entfällt die Erstellung und der gewünschte Namespace ist innerhalb des Installationsbefehls anzugeben.

Da die Container zunächst von der verwendeten Image-Registry heruntergeladen werden, kann die Installation einige Minuten in Anspruch nehmen. Im Idealfall sollte eine Zeitspanne von fünf Minuten jedoch nicht überschritten werden, bevor die CaaS-Plattform einsatzfähig ist.

Der Status der einzelnen Komponenten ist mit dem folgenden Befehl abrufbar:

kubectl get pods --namespace=caas

Sobald alle Komponenten den Status Running besitzen, ist die Installation abgeschlossen.

NAME                                 READY     STATUS        RESTARTS   AGE
caas-mongo-0                         2/2       Running       0          4m
caas-mongo-1                         2/2       Running       0          3m
caas-mongo-2                         2/2       Running       0          1m
caas-rest-api-1851714254-13cvn       1/1       Running       0          5m
caas-rest-api-1851714254-13cvn       1/1       Running       0          4m
caas-rest-api-1851714254-xs6c0       1/1       Running       0          4m

3.4. TLS

Die Kommunikation der CaaS-Plattform nach außen ist standardmäßig nicht verschlüsselt. Soll sie per TLS geschützt werden, existieren zwei Konfigurationsmöglichkeiten:

Verwendung eines offiziell signierten Zertifikats

Für die Verwendung eines offiziell signierten Zertifikats wird ein TLS-Secret benötigt, das zunächst zu erzeugen ist. Dieses muss die Schlüssel tls.key und das Zertifikat tls.crt enthalten.

Die für die Erzeugung des TLS-Secrets notwendigen Schritte sind in der Kubernetes Ingress-Dokumentation beschrieben.

Automatisierte Zertifikatsverwaltung

Alternativ zur Verwendung eines offiziell signierten Zertifikats ist es möglich, die Verwaltung mithilfe des Cert-Managers zu automatisieren. Dieser ist innerhalb des Clusters zu installieren und übernimmt die Erzeugung, Verteilung sowie Aktualisierung aller benötigten Zertifikate. Die Konfiguration des Cert-Managers ermöglicht dabei beispielsweise die Verwendung und automatische Erneuerung von Let’s-Encrypt-Zertifikaten.

Die notwendigen Schritte zur Installation sind in der Cert-Manager-Dokumentation erläutert.

3.5. Skalierung

Um die in den CaaS übertragenen Informationen schnell verarbeiten zu können, muss die CaaS-Plattform jederzeit eine optimale Lastverteilung gewährleisten. Aus diesem Grund sind die REST-Schnittstelle und die Mongo-Datenbank skalierbar und hinsichtlich des Aspekts der Ausfallsicherheit bereits so konfiguriert, dass jeweils mindestens drei Instanzen deployed werden. Diese Mindestanzahl von Instanzen ist insbesondere für den Mongo-Cluster zwingend erforderlich.

3.5.1. REST-Schnittstelle

Die Skalierung der REST-Schnittstelle erfolgt mithilfe eines Horizontal Pod Autoscalers. Dessen Aktivierung sowie Konfiguration ist in der custom-values.yaml-Datei vorzunehmen, um die in der values.yaml-Datei definierten Standard-Werte zu überschreiben.

Standard-Konfiguration der REST-Schnittstelle
restApi:
  horizontalPodAutoscaler:
    enabled: false
    minReplicas: 3
    maxReplicas: 9
    targetCPUUtilizationPercentage: 50

Der Horizontal Pod Autoscaler ermöglicht die Herunter- bzw. Heraufskalierung der REST-Schnittstelle in Abhängigkeit der aktuellen CPU-Last. Der Parameter targetCPUUtilizationPercentage gibt dabei an, ab welchem Prozentwert eine Skalierung stattfinden soll. Gleichzeitig definieren die Parameter minReplicas und maxReplicas die minimale und maximal Anzahl der möglichen REST-Schnittstellen-Instanzen.

Der Schwellwert für die CPU-Last ist mit Bedacht zu wählen:
Ist ein zu niedriger Prozentwert gewählt, skaliert die REST-Schnittstelle im Fall ansteigender Last zu früh hoch. Ist ein zu hoher Prozentwert gewählt, kann die Skalierung der REST-Schnittstelle im Fall ansteigender Last nicht schnell genug erfolgen.

Eine falsche Konfiguration kann somit die Stabilität des Systems gefährden.

Die offizielle Kubernetes Horizontal Pod Autoscaler-Dokumentation sowie die in ihr aufgeführten Beispiele enthalten weitere Informationen zum Einsatz eines Horizontal Pod Autoscalers.

3.5.2. Mongo-Datenbank

Wir unterscheiden hier die horizontale Skalierung von der vertikalen. Horizontale Skalierung bedeutet zusätzliche Instanzen, welche den Traffic übernehmen. Vertikale Skalierung bedeutet existierenden Instanzen mehr CPU/RAM zuweisen.

Horizontale Skalierung

Im Gegensatz zur REST-Schnittstelle ist die horizontale Skalierung der Mongo-Datenbank nur manuell möglich. Sie kann daher nicht automatisch mithilfe eines Horizontal Pod Autoscalers durchgeführt werden.

Die Skalierung der Mongo-Datenbank erfolgt über den Parameter replicas. Dieser ist in die custom-values.yaml-Datei einzutragen, um den in der values.yaml-Datei definierten Standard-Wert zu überschreiben.

Für den Betrieb des Mongo-Clusters sind mindestens drei Instanzen notwendig, da sonst kein Primary-Knoten zur Verfügung steht und die Datenbank nicht schreibbar ist. Unterschreitet die Anzahl verfügbarer Instanzen einen Wert von 50 % der konfigurierten Instanzen, kann kein Primary-Knoten mehr gewählt werden. Dieser ist für die Funktionsfähigkeit der REST-Schnittstelle jedoch unerlässlich.

Das Kapitel Consider Fault Tolerance der MongoDB-Dokumentation beschreibt, wie viele Knoten explizit ausfallen können, bis die Bestimmung eines neuen Primary-Knoten unmöglich ist. Die in der Dokumentation enthaltenen Informationen sind bei der Skalierung der Installation zu berücksichtigen.

Weitere Informationen zur Skalierung und Replizierung der Mongo-Datenbank sind in den Kapiteln Replica Set Deployment Architectures und Replica Set Elections enthalten.

Definition des Replica-Parameters
mongo:
  replicas: 3

Skalieren Sie das StatefulSet nicht direkt in K8s. Sollten Sie dies tun, werden bestimmte Verbindungsurls nicht korrekt sein und die zusätzlichen Instanzen werden nicht richtig verwendet. Verwenden Sie stattdessen die Custom Helm Values.

Ein Herunterskalieren der Mongo-Datenbank ist nicht ohne einen direkten Eingriff möglich und erfordert eine manuelle Verkleinerung des Replicasets der Mongo-Datenbank. Die MongoDB-Dokumentation beschreibt die dafür notwendigen Schritte.
Außerdem empfehlen wir, nach dem Entfernen der gelöschten Instanzen in der Replicaset-Konfiguration auch die entsprechenden Persistent Volume Claims zu löschen. Andernfalls besteht die Gefahr, dass die Instanzen bei einer zukünftigen Hochskalierung nicht mehr automatisch zum Replicaset hinzugefügt werden.

Ein solcher Eingriff erhöht das Risiko eines Ausfalls und wird daher nicht empfohlen.

Vertikale Skalierung

Die vertikale Skalierung erfolgt mithilfe eines Vertical Pod Autoscalers. Vertical Pod Autoscaler sind Custom Resources in Kubernetes, daher müssen Sie als Erstes die Unterstützung in Ihrem Cluster gewährleisten.

Anschließend können Sie folgende Parameter in Ihrer custom-values.yaml konfigurieren:

Konfiguration des Vertical Pod Autoscaler
mongo:
  verticalPodAutoscaler:
    enabled: false
    apiVersion: autoscaling.k8s.io/v1beta2
    updateMode: Auto
    minAllowed:
      cpu: 100m
      memory: 500Mi
    maxAllowed:
      cpu: 1
      memory: 2000Mi

Übernahme der Konfiguration

Die aktualisierte custom-values.yaml-Datei muss nach den Konfigurationsänderungen für die REST-Schnittstelle bzw. die Mongo-Datenbank mit dem folgenden Befehl übernommen werden.

Upgrade-Befehl
helm upgrade -i RELEASE_NAME path/to/caas-<VERSIONNUMBER>.tgz --values /path/to/custom-values.yaml

Der Release-Name ist mit dem Befehl helm list --all-namespaces ermittelbar.

3.6. Monitoring

Die CaaS-Plattform ist eine Microservice-Architektur und setzt sich daher aus unterschiedlichen Komponenten zusammen. Um ihren Status jederzeit ordnungsgemäß überwachen und im Fehlerfall kurzfristig reagieren zu können, ist für den Betrieb mit Kubernetes die Einbindung in ein clusterweites Monitoring zwingend notwendig.

Die CaaS-Plattform ist für ein Monitoring mit Prometheus-Operator bereits vorkonfiguriert, da dieses Szenario im Kubernetes-Umfeld weit verbreitet ist. Es sind sowohl Prometheus-ServiceMonitors zum Erfassen von Metriken, Prometheus-Alerts zur Benachrichtigung bei Problemen als auch vordefinierte Grafana-Dashboards zur Visualisierung der Metriken enthalten.

3.6.1. Voraussetzungen

Es ist unerlässlich, ein Monitoring und eine Persistenz der Logs für den Kubernetes-Cluster einzurichten. Ohne diese Voraussetzungen sind im Fehlerfall kaum Analyse-Möglichkeiten gegeben und dem Technical Support fehlen wichtige Informationen.

Metriken

Zur Installation des Prometheus-Operator nutzen Sie bitte das offizielle Helm-Chart, so dass darauf aufbauend das Cluster-Monitoring eingerichtet werden kann. Weitere Informationen entnehmen Sie bitte den entsprechenden Dokumentationen.

Sollten Sie keinen Prometheus-Operator betreiben, so müssen Sie die Prometheus-ServiceMonitors sowie die Prometheus-Alerts abschalten.

Logging

Mit dem Einsatz von Kubernetes ist es möglich, diverse Container bzw. Dienste automatisiert sowie skalierbar bereitzustellen. Damit die Logs in einem solchen dynamischen Umfeld auch nach der Beendung einer Instanz bestehen bleiben, muss eine Infrastruktur eingebunden werden, die diese zuvor persistiert.

Daher empfehlen wir die Verwendung eines zentralen Logging-Systems, wie zum Beispiel des Elastic-Stacks. Der Elastic- bzw. ELK-Stack ist eine Sammlung von Open-Source-Projekten, die dabei helfen, Log-Daten in Echtzeit zu persistieren, durchsuchen sowie analysieren.

Auch hierfür können Sie für die Installation auf ein bestehendes Helm-Chart zurückgreifen.

3.6.2. Prometheus-ServiceMonitors

Das Deployment des von der CaaS-Plattform mitgelieferten ServiceMonitors für die REST-Schnittstelle und die Mongo-Datenbank, wird über die custom-values.yaml-Datei des Helm-Charts gesteuert.

Der Zugriff auf die Metriken der REST-Schnittstelle ist mittels API Key und der Zugriff auf die Metriken der MongoDB über einen entsprechenden MongoDB-Benutzer gesichert. Die jeweiligen Zugangsdaten sind im Credentials-Block der values.yaml-Datei des Helm-Charts enthalten.

Bitte passen Sie die Zugangsdaten aus Sicherheitsgründen in ihrer custom-values.yaml-Datei an.

Typischerweise ist Prometheus so konfiguriert, dass nur ServiceMonitors mit bestimmten Labels berücksichtigt werden. Die Labels können daher in der custom-values.yaml-Datei konfiguriert werden und gelten für alle ServiceMonitors des CaaS Helm-Charts. Des Weiteren ermöglicht der Parameter scrapeInterval eine Definition der Häufigkeit, mit welcher die jeweiligen Metriken abgerufen werden.

monitoring:
  prometheus:
    # Prometheus service monitors will be created for enabled metrics. Each Prometheus
    # instance has a configured serviceMonitorSelector property, to be able to control
    # the set of matching service monitors. To allow defining matching labels for CaaS
    # service monitors, the labels can be configured below and will be added to each
    # generated service monitor instance.
    metrics:
      serviceMonitorLabels:
        release: "prometheus-operator"
      mongo:
        enabled: true
        scrapeInterval: "30s"
      caas:
        enabled: true
        scrapeInterval: "30s"

Die Metriken der MongoDB werden über einen Sidecar-Container bereitgestellt und mit Hilfe eines separaten Datenbank-Benutzers abgerufen. Die Konfiguration des Datenbank-Benutzers können Sie im credentials-Block der custom-values.yaml vornehmen. Der Sidecar-Container ist mit folgender Standard-Konfiguration hinterlegt:

mongo:
  metrics:
    image: mongodb-exporter:0.11.0
    syncTimeout: 1m

3.6.3. Prometheus-Alerts

Das Deployment der von der CaaS-Plattform mitgelieferten Alerts wird über die custom-values.yaml-Datei des Helm-Charts gesteuert.

Typischerweise ist Prometheus so konfiguriert, dass nur Alerts mit bestimmten Labels berücksichtigt werden. Die Labels können daher in der custom-values.yaml-Datei konfiguriert werden und gelten für alle Alerts des CaaS Helm-Charts:

caas-common:
  monitoring:
    prometheus:
      alerts:
        # Labels for the PrometheusRule resource
        prometheusRuleLabels:
          app: "prometheus-operator"
          release: "prometheus-operator"
        # Additional Prometheus labels to attach to alerts (or overwrite existing labels)
        additionalAlertLabels: {}
        caas:
          enabled: true
          useAlphaAlerts: false
          # Namespace(s) that should be targeted by the alerts (supports Go template and regular expressions)
          targetNamespace: "{{ .Release.Namespace }}"

3.6.4. Grafana-Dashboards

Das Deployment der von der CaaS-Plattform mitgelieferten Grafana-Dashboards wird über die custom-values.yaml-Datei des Helm-Charts gesteuert.

Typischerweise ist der Grafana Sidecar Container so konfiguriert, dass nur Configmaps mit bestimmten Labels und in einem definierten Namespace berücksichtigt werden. Die Labels der Configmap sowie der Namespace, in welchen diese deployed wird, können daher in der custom-values.yaml-Datei konfiguriert werden:

caas-common:
  monitoring:
    grafana:
      dashboards:
        enabled: true
        # Namespace that the ConfigMap resource will be created in (supports Go template and regular expressions)
        configmapNamespace: "{{ .Release.Namespace }}"
        # Additional labels to attach to the ConfigMap resource
        configMapLabels: {}
        overviewDashboardsEnabled: false

4. Entwicklungsumgebung

Kubernetes und Helm bilden die Grundlage aller Installationen der CaaS-Plattform. Bei Entwicklungsumgebungen empfehlen wir die Installation der CaaS-Plattform in einem separaten Namespace auf Ihrem Produktionscluster oder einem ähnlich konfigurierten Cluster. Wir raten davon ab, lokale Instanzen der CaaS-Plattform zu verwenden, auch für die Entwicklung.

Wenn Sie eine lokale Umgebung auf den Entwicklungsmaschinen benötigen, müssen Sie einen lokalen Kubernetes-Cluster zur Verwendung erstellen. Dazu kann eines der folgenden Projekte verwendet werden:

Diese Liste erhebt keinen Anspruch auf Vollständigkeit. Vielmehr soll sie einige Beispiele aufführen, von denen wir wissen, dass der Betrieb generell möglich ist, ohne dass wir diese Projekte selbst permanent nutzen.

Jedes dieser Projekte kann verwendet werden, um Kubernetes-Cluster lokal zu verwalten. Wir sind jedoch nicht in der Lage, Ihnen Unterstützung für eines dieser spezifischen Projekte zu geben. Die CaaS Plattform verwendet nur Standardfunktionen von Helm und Kubernetes und ist daher unabhängig von einer bestimmten Kubernetes Distribution.

Bitte stellen Sie sicher, dass die folgenden Funktionen korrekt konfiguriert sind, wenn Sie einen lokalen Kubernetes-Cluster verwenden:

  • Kubernetes Image Pull Secrets zum Auflösen der Docker-Images aus Ihrer lokalen oder firmeneigenen Docker-Registry

  • Deaktivieren des Monitorings in der custom-values.yaml oder Installieren der benötigten Voraussetzungen

  • Anpassen der DNS-Einstellungen des Hostsystems, um mit Kubernetes Ingress-Ressourcen arbeiten zu können, oder Verwendung lokaler Port-Weiterleitungen in den lokalen Cluster

5. REST-Schnittstelle

5.1. Authentifizierung

Jede Anfrage an die REST-Schnittstelle muss authentifiziert werden, ansonsten wird sie abgewiesen. Die verschiedenen Möglichkeiten der Authentifizierung werden nachfolgend erläutert.

5.1.1. Authentifizierung mit API Key

Anfragen mit API Key müssen einen HTTP-Header mit Bearer Token enthalten: Authorization: Bearer <key>. Als Wert von key wird der Wert des key-Attributs des entsprechenden API Keys erwartet.

Weitere Informationen finden Sie im Abschnitt Validierung von API Keys.

5.1.2. Authentifizierung mit Sicherheitstoken

Es ist möglich, zu einem API Key ein kurzlebiges (bis zu 24 Stunden) Sicherheitstoken zu generieren. Das Token beinhaltet dieselben Berechtigungen wie der API Key, zu dem es generiert wurde. Es gibt zwei Möglichkeiten, diese Token zu generieren und zu nutzen:

Query Parameter

Eine mit API Key authentifizierte GET-Anfrage auf /_logic/securetoken?tenant=<db> generiert ein Sicherheitstoken. Solch ein Token kann nur für eine konkrete Datenbank ausgestellt werden, unabhängig davon ob der API Key Berechtigungen auf mehrere Datenbanken hat. Ein Parameter &ttl=<Lebensdauer in Sekunden> wird unterstützt und ist optional. Das Sicherheitstoken steht in der JSON-Rückgabe.

Jede Anfrage an die REST-Schnittstelle kann optional über einen Query-Parameter ?securetoken=<token> authentifiziert werden.

Eine mit API Key authentifizierte GET-Anfrage auf /_logic/securetokencookie?tenant=<db> generiert ein Sicherheitstoken-Cookie. Solch ein Cookie kann nur für eine konkrete Datenbank ausgestellt werden, unabhängig davon, ob der API Key Berechtigungen auf mehrere Datenbanken hat. Ein Parameter &ttl=<Lebensdauer in Sekunden> wird unterstützt und ist optional. Die Rückgabe beinhaltet einen Set-Cookie-Header mit dem Sicherheitstoken.

Alle Anfragen mit diesem Cookie werden automatisch authentifiziert.

5.1.3. Authentifizierungsreihenfolge

Sollten mehrere Authentifizierungsmechanismen gleichzeitig in einer Anfrage verwendet werden, so wird nur der erste von ihnen evaluiert. Die Reihenfolge ist wie folgt:

  1. Der Query Parameter securetoken.

  2. Der Authorization Header.

  3. Das Cookie securetoken.

5.2. Abfrage von Dokumenten und Medien

Über die REST-Schnittstelle lassen sich Inhalte in Form von JSON Dokumenten über HTTP verwalten und abfragen. Sie werden in sogenannten Collections abgelegt, die Datenbanken unterstellt sind. Es gilt dabei das folgende dreiteilige URL-Schema:

https://REST-HOST:PORT/<database>/<collection>/<document>

database

Dieser Teil der URL beinhaltet die Tenant ID.

collection

Die Collection setzt sich aus der FirstSpirit-Projekt UUID sowie den jegeiligen Vorschau- bzw. Freigabestand zusammen.

document

Hier wird die UUID des FirstSpirit-Elements zusammen mit der Sprachlocale verwendet.

Binäre Inhalte (Medien) bilden insofern eine Ausnahme, als sie in sogenannten Buckets gespeichert werden. Die zugehörigen Collections enden immer mit das Suffix .files:

https://REST-HOST:PORT/<tenant>/<project>.<release|preview>.files/<document>

Bitte beachten Sie, dass binäre Inhalte bei unserem Cloud-Offering nicht in die CaaS-Buckets übertragen werden.

5.2.1. HAL-Format

Die Schnittstelle liefert alle Ergebnisse im HAL-Format zurück. Es handelt sich bei ihnen somit nicht um schlichte Rohdaten, wie zum Beispiel traditionell unstrukturierte Inhalte im JSON-Format.

Das HAL-Format bietet den Vorteil einer einfachen, aber mächtigen Strukturierung. Neben den geforderten Inhalten enthalten die Ergebnisse zusätzliche Meta-Informationen zur Struktur dieser Inhalte.

Beispiel

{  "_size": 5,
   "_total_pages": 1,
   "_returned": 3,
   "_embedded": { CONTENT }
}

In diesem Beispiel wurde eine gefilterte Abfrage abgesetzt. Ohne die genauen Inhalte zu kennen, ist ihre Struktur aufgrund der Meta-Informationen direkt ablesbar. Die REST-Schnittstelle liefert an dieser Stelle aus einer Menge von fünf Dokumenten drei den Filterkriterien entsprechende Ergebnisse zurück und stellt diese auf einer einzigen Seite dar.

Handelt es sich bei einem angefragten Element um ein Medium, ermittelt die URL lediglich dessen Metadaten. Das HAL-Format enthält entsprechende Links, welche auf die URL mit den eigentlichen Binärdaten verweisen. Weitere Informationen entnehmen Sie bitte der Dokumentation.

5.2.2. Seitengröße der Abfragen

Die Ergebnisse der REST-Schnittstelle werden immer paginiert ausgeliefert. Zur Steuerung der Seitengröße und der angefragten Seite können bei GET-Anfragen die HTTP-Query-Parameter pagesize und page verwendet werden. Der Standardwert für den pagesize-Parameter ist in der CaaS-Plattform auf 20 gesetzt und das Maximum auf 100. Diese Werte können im Falle einer On-Premises-Installation in der Datei custom-values.yaml geändert werden. Weitere Informationen finden Sie in der RESTHeart-Dokumentation.

5.2.3. Einsatz von Filtern

Filter kommen immer dann zum Einsatz, wenn Dokumente nicht über ihre Id, sondern über deren Inhalt ermittelt werden sollen. Auf diesem Weg lassen sich sowohl einzelne als auch mehrere Dokumente abrufen.

Die Abfrage aller englischsprachiger Dokumente aus der Collection products besitzt beispielsweise folgenden Aufbau:

https://REST-HOST:PORT/<database>/products?filter={fs_language:"EN"}

Über dieses Beispiel hinaus existieren weitere Filtermöglichkeiten. Weitere Informationen hierzu finden Sie in der Query-Dokumentation.

Bitte beachten Sie, dass der Query-Parameter np automatisch zu Filterabfragen auf Collections hinzugefügt wird. Dadurch enthält die Antwort keine Collection-Metadaten, da diese in der Regel für diesen Abfragetyp irrelevant sind. Dieses Verhalten kann in den Custom Helm Values deaktiviert werden, indem der Wert restApi.additionalConfigOverrides./noPropertiesInterceptor/enabled: false gesetzt wird.

5.2.4. Auflösen von Referenzen

CaaS-Dokumente können andere CaaS-Dokumente referenzieren. Bei der Verarbeitung von Dokumenten werden die referenzierten Inhalte häufig direkt benötigt. Um sequentielle Abfragen in diesen Fällen zu verhindern, kann der Query Parameter resolveRef verwendet werden.

Die folgenden zwei JSON-Dokumente zeigen dies beispielhaft:

{
  "_id": "my-document",
  "fsType": "ProjectProperties",
  "formData": {
    "ps_audio": {
      "fsType": "FS_REFERENCE",
      "value": {
        "fsType": "PageRef",
        "url": "https://REST-HOST:PORT/my-db/col/my-referenced-document"
      }
    }
  }
}
{
  "_id": "my-referenced-document",
  "fsType": "PageRef",
  "name": "audio"
}

Im ersten Dokument steht im JSON unter formData.ps_audio.value.url eine absolute URL auf ein anderes Dokument im CaaS. Folgender Request zeigt beispielhaft, wie diese Referenz im gleichen Request aufgelöst wird, der das Dokument ausliest.

curl -X GET --location "https://REST-HOST:PORT/my-db/col/my-document?resolveRef=formData.ps_audio.value.url" \
    -H "Authorization: Bearer my-api-key"

Der Wert des Query-Parameters muss dabei den Pfad im JSON zu der URL angeben. Die Antwort enthält dann ein zusätzliches Attribut _resolvedRefs:

{
  "_id": "my-document",
  "fsType": "ProjectProperties",
  "formData": {
    "ps_audio": {
      "fsType": "FS_REFERENCE",
      "value": {
        "fsType": "PageRef",
        "url": "https://REST-HOST:PORT/my-db/col/my-referenced-document"
      }
    }
  },
  "_resolvedRefs": {
    "https://REST-HOST:PORT/my-db/col/my-referenced-document": {
      "_id": "my-referenced-document",
      "fsType": "PageRef",
      "name": "audio"
    }
  }
}

Die Auflösung von Referenzen wird beeinflusst durch die Konfiguration und Einschränkungen.

Der Parameter resolveRef wird auch für Collection-Anfragen unterstützt. In diesem Fall werden die Referenzen in allen zurückgegebenen Dokumenten aufgelöst. Die Dokumente der aufgelösten Referenzen werden dabei in einem neuen, zusätzlichen Dokument gesammelt, welches wiederum zum Array der Antwort hinzugefügt wird.

curl -X GET --location "https://REST-HOST:PORT/my-db/col?resolveRef=formData.ps_audio.value.url" \
    -H "Authorization: Bearer my-api-key"
[
  {
    "_id": "my-document",
    "fsType": "ProjectProperties",
    "formData": {
      "ps_audio": {
       "fsType": "FS_REFERENCE",
       "value": {
         "fsType": "PageRef",
         "url": "https://REST-HOST:PORT/my-db/col/my-referenced-document"
       }
      }
    }
  },
  {
    "_id": "_resolvedRefs",
    "https://REST-HOST:PORT/my-db/col/my-referenced-document": {
      "_id": "my-referenced-document",
      "fsType": "PageRef",
      "name": "audio"
    }
  }
]

Dieses zusätzliche Dokument ist immer das letzte Element im Array der Antwort und kann durch die ID _resolvedRefs identifiziert werden.

Der Query Parameter pagesize hat keine Auswirkung auf dieses Dokument, sodass die tatsächliche Größe des Arrays pagesize + 1 betragen kann.

Transitive Referenzen

Möglicherweise enthalten referenzierte Dokumente ihrerseits weitere Referenzen. Auch diese können im ursprünglichen Request aufgelöst werden. Dafür muss der Pfad zur nächsten Referenz ebenfalls in der Anfrage angegeben werden, inklusive des Prefixes $i., wobei i die Tiefe der Referenzauflösungskette beschreibt.

Folgender Request zeigt dies bezogen auf das vorherige Beispiel, anhand einer Referenz in dem Attribut page.url des Dokumentes my-referenced-document:

curl -X GET --location "https://REST-HOST:PORT/my-db/col?resolveRef=formData.ps_audio.value.url&resolveRef=$1.page.url" \
    -H "Authorization: Bearer my-api-key"
Referenzpfad Syntax

Tiefe 0 beschreibt die Dokumente, die der ursprüngliche Request in der Antwort bereits enthalten wird. Für die Tiefe 0 ist der Prefix $0. optional. Tiefe 1+ beschreibt alle Dokumente, die durch erfolgreiches Auflösen der Referenzen auf der jeweils vorherigen Tiefe gefunden wurden.

Tiefe JSON Dokument resolveRef Erklärung

0

{
  "data": {
    "url": "<url>"
  }
}

data.url

In allen Dokumenten der Tiefe 0 wird unter dem Pfad data.url nach Referenzen gesucht und diese aufgelöst.

$0.data.url

{
  "data": [
    { "url": "<url1>" },
    { "url": "<url2>" }
  ]
}

data[*].url

In allen Dokumenten der Tiefe 0 wird das Array mit dem Namen data durchsucht. * bedeutet, dass alle Objekte im Array durchsucht werden. In diesen Objekten wird der Wert von url als Referenz aufgelöst.

data[0].url

Nur im ersten Objekt des Arrays wird url aufgelöst.

data[1].url

Nur im zweiten Objekt des Arrays wird url aufgelöst.

{
  "data": [
    [ {"url": "<url1>"} ],
    [ {"url": "<url2>"} ]
  ]
}

data[*][*].url

Es werden alle Arrays im data Array durchsucht. In diesen Objekten wird der Wert von url als Referenz aufgelöst.

data[0][*].url

Es wird das erste Array im data Array durchsucht. In allen Objekten des Arrays wird url aufgelöst.

1

<JSON Dokument>

$1.<path>

Die Pfade müssen mit $1. anfangen. Ansonsten können sie genauso wie bei Tiefe 0 angegeben werden.

n

<JSON Dokument>

$<n>.<path>

Die Pfade müssen mit $<n>. anfangen, wobei <n> der Tiefe der Auflösungskette entspricht. Ansonsten können sie genauso wie bei Tiefe 0 angegeben werden.

Konfiguration und Einschränkungen
  • Es können nur absolute URLs aufgelöst werden.

  • Es werden beim Auflösen der Referenzen keine Fehler geworfen, sofern inkorrekte Pfade oder URLs angegeben werden. Inkorrekte Pfade und Referenzen werden still ignoriert.

  • Pfade müssen gemäß der Referenzpfad Syntax angegeben werden. Die Syntax orientiert sich an JsonPath, unterstützt werden aber nur genau die dokumentierten Operatoren.

  • Die URLs werden in dem _resolvedRefs Dokument nicht normalisiert. URL Referenzen, die nicht exakt identisch sind, werden als unterschiedliche Dokumente gewertet, auch wenn sie auf dasselbe Dokument im CaaS zeigen. Dies kann z. B. durch ein zusätzliches / am Ende der URL oder durch unterschiedliche Query Parameter ausgelöst werden.

  • Standardmäßig ist die maximale Tiefe der Referenzauflösung 3. Somit kann maximal $2. als Pfad-Präfix für Referenzen benutzt werden. Dies kann in den Custom Helm Values mit dem Value restApi.additionalConfigOverrides./refResolvingInterceptor/max-depth konfiguriert werden.

  • Es können in einem Request maximal 100 unterschiedliche Referenzen aufgelöst werden. Sobald dieses Limit erreicht ist, werden keine weiteren Referenzen gesammelt und damit auch nicht Teil der Response sein. Die maximale Anzahl kann in den Custom Helm Values mit dem Value restApi.additionalConfigOverrides./refResolvingInterceptor/limit konfiguriert werden.

  • Die Referenzauflösung ist standardmäßig aktiviert. Sie kann in den Custom Helm Values mit dem Value restApi.additionalConfigOverrides./refResolvingInterceptor/enabled: false deaktiviert werden.

5.3. Speicherung von Dokumenten

Zur Speicherung von Dokumenten können die HTTP Methoden POST, PUT und PATCH benutzt werden. Dokumente können darüber hinaus mit dem Verb DELETE gelöscht werden.

Der folgende Ausschnitt zeigt das Anlegen eines Dokumentes my-document innerhalb der Collection my-collection, die sich in der Datenbank my-db befindet.

curl --location --request PUT 'https://REST-HOST:PORT/my-db/my-collection/my-document' \
--header 'Authorization: Bearer my-api-key' \
--header 'Content-Type: application/json' \
--data-raw '{
    "data": "some-data"
}'

Weitere Informationen zur Speicherung von Dokumenten können den entsprechenden Abschnitten in der RESTHeart- Dokumentation entnommen werden.

Bei der Speicherung von Dokumenten mittels der Verben POST, PUT oder PATCH wird abweichend zu RESTHeart als default der Write Mode upsert benutzt. Weitere Informationen zu dem Write Mode können der RESTHeart- Dokumentation entnommen werden.

5.4. Verwaltung von Datenbanken und Collections

Im Gegensatz zur Speicherung von Dokumenten ist die Verwaltung von Datenbanken und Collections lediglich auf die HTTP Methoden PUT und DELETE beschränkt.

Folgender Ausschnitt zeigt das Anlegen der Datenbank my-db mit einem PUT Request.

curl --location --request PUT 'https://REST-HOST:PORT/my-db' \
--header 'Authorization: Bearer my-api-key'

Es gibt reservierte Datenbanken, die nicht zur Speicherung normaler Inhalte genutzt werden können. Zu den reservierten Datenbanknamen zählen caas_admin, _logic und graphql. Für genauere Informationen zu dem Verwendungszweck der reservierten Datenbank graphql siehe Abschnitt Verwalten von GraphQL-Applikationen.

Weitere Informationen zur Verwaltung von Datenbanken können den entsprechenden Abschnitten in der RESTHeart-Dokumentation entnommen werden.

Das Verwalten von Datenbanken ist unserem SaaS-Angebot aufgrund von eingeschränkter Zugriffsberechtigung nicht unterstützt.

Eine Collection my-collection kann wie folgt in der Datenbank my-db mit einem PUT Request angelegt werden.

curl --location --request PUT 'https://REST-HOST:PORT/my-db/my-collection' \
--header 'Authorization: Bearer my-api-key'

Es gibt reservierte Collections, die nicht zur Speicherung normaler Inhalte genutzt werden können. Zu den reservierten Collectionnamen zählen apikeys und gql-apps. Für genauere Informationen zu den Verwendungszwecken der reservierten Collections siehe Abschnitt Verwaltung von API Keys → REST Endpunkte und Verwalten von GraphQL-Applikationen.

Weitere Informationen zur Verwaltung von Collections können den entsprechenden Abschnitten in der RESTHeart-Dokumentation entnommen werden.

5.5. Verwaltung von API Keys

API Keys können, wie alle anderen Ressourcen im CaaS auch, über REST-Endpunkte verwaltet werden. Generell gilt es dabei zu unterscheiden, dass API Keys auf zwei Ebenen verwaltet werden können: global oder lokal pro Datenbank. Globale API Keys unterscheiden sich von den lokalen API Keys durch ihren Gültigkeitsbereich.

Bei der Authentifizierung mit API Key durchsucht die CaaS-Plattform immer zunächst die lokalen API Keys. Wird kein passender API Key gefunden, so werden danach die globalen API Keys evaluiert.

5.5.1. Globale API Keys

Globale API Keys sind datenbankübergreifend und werden in der apikeys Collection der caas_admin Datenbank verwaltet. Im Gegensatz zu lokalen API Keys ermöglichen sie es, Berechtigungen für mehrere oder gar alle Datenbanken zu definieren.

5.5.2. Lokale API Keys

Lokale API Keys sind pro Datenbank definiert und werden dementsprechend in der apikeys Collection einer beliebigen Datenbank verwaltet. Anders als globale API Keys können lokale API Keys nur Berechtigungen für Ressourcen innerhalb derselben Datenbank definieren.

5.5.3. Autorisierungsmodell

Die Autorisierung eines API Keys wird auf Basis jeder Berechtigung ausgeführt, die Teil des API Keys sind. Die Berechtigungen werden im permissions Attribut des API Keys definiert.

Bei der Prüfung einer Berechtigung wird das url-Attribut herangezogen. Dessen Wert wird gegen den URL-Pfad eines eingehenden Requests geprüft. Die Art der Prüfung ist abhängig vom Modus der Berechtigung.

Es gibt drei verschiedene Modi:

  • PREFIX und REGEX

    Im Modus PREFIX wird geprüft, ob der Wert des url Attributs ein Präfix des URL-Pfades eines eingehenden Requests ist.

    Im REGEX Modus muss ein regulärer Ausdruck im url Attribut hinterlegt werden. Die Prüfung erfolgt dann durch den Abgleich, ob der URL Pfad eines eingehenden Requests dem Muster des regulären Ausdrucks entspricht.

    Darüber hinaus existiert für die Berechtigungsmodi PREFIX und REGEX eine grundlegende Unterscheidung zwischen der Funktionsweise von globalen und lokalen API Keys. Globale API Keys prüfen immer gegen den gesamten Pfad des Requests, lokale API Keys hingegen gegen den Teil des Pfads nach der Datenbank. Weitere Informationen zu globalen und lokalen API Keys können dem Beispiel Unterscheidung lokaler und globaler API Key oder dem Kapitel Verwaltung von API Keys entnommen werden.

  • GRAPHQL
    Der Modus GRAPHQL erlaubt die Berechtigung zur Ausführung einer GraphQL-Applikation. Dabei wird der Wert des url Attributs mit der URI einer GraphQL-Applikation verglichen, die ein eingehender Request benutzt. Die URI einer GraphQL-Applikation wird im Attribut descriptor.uri der App-Definition angegeben. Weitere Details sind im Kapitel GraphQL zu finden.

Unterscheidung lokaler und globaler API Key

Die folgende Tabelle zeigt beispielhaft den Unterschied bei der Autorisierung von lokalen oder globalen API Keys mit der Verwendung des Berechtigungsmodus PREFIX oder REGEX.

Tabelle 1. API Key Autorisierung
Berechtigung im API Key (Attribut url) Typ des API Keys Request URL-Pfad Erlaubt

/

global

/

ja

/project/

ja

/project/content/

ja

/other-project/

ja

/other-project/content/

ja

/project/

global

/

nein

/project/

ja

/project/content/

ja

/other-project/

nein

/other-project/content/

nein

/

lokal in project

/

nein

/project/

ja

/project/content/

ja

/other-project/

nein

/other-project/content/

nein

/content/

lokal in project

/

nein

/project/

nein

/project/content/

ja

/other-project/

nein

/other-project/content/

nein

5.5.4. REST Endpunkte

Die folgenden Endpunkte stehen für die Verwaltung von API Keys zur Verfügung:

  • GET /<database>/apikeys

  • POST /<database>/apikeys
    zu beachten: die Parameter _id und key müssen übergeben werden und identische Werte besitzen

  • PUT /<database>/apikeys/{id}
    zu beachten: der Parameter key muss denselben Wert haben wie die {id} in der URL

  • DELETE /<database>/apikeys/{id}

Zur Verwaltung von API Keys kann auch ein API Key als Autorisierungsmethode eingesetzt werden. In dem Fall muss der verwendete API Key Schreibrechte auf der entsprechenden API Keys-Collection besitzen. Dies gilt insbesondere auch bei nur lesenden Anfragen und dient dazu, eine Rechteausweitung zu verhindern.

Die Datenbank richtet sich nach dem Typ des API Keys.

Der folgende Ausschnitt zeigt das beispielhafte Anlegen von einem lokalen API Key.

curl "https://REST-HOST:PORT/<tenant>/apikeys" \
     -H 'Content-Type: application/json' \
     -u '<USER>:<PASSWORD>' \
     -d $'{
  "_id": "1e0909b7-c943-45a5-ae96-79f294249d48",
  "key": "1e0909b7-c943-45a5-ae96-79f294249d48",
  "name": "New-Apikey",
  "description": "Some descriptive text",
  "permissions": [
    {
      "url": "/<collection>",
      "permissionMode": "PREFIX",
      "methods": [
        "GET",
        "PUT",
        "POST",
        "PATCH",
        "DELETE",
        "HEAD",
        "OPTIONS"
      ]
    }
  ]
}'

In diesem Beispiel wird über curl ein neuer ApiKey erstellt, welcher im Permissions-Array entsprechend eingestellte Zugriffsrechte (über das Attribut url) auf die angegebene Collection besitzt.

Um einen API Key mit dem Modus REGEX zu erstellen, muss das obige Beispiel wie folgt angepasst werden:

"url": "<regex>",
"permissionMode": "REGEX",

Die apikeys Collections sind für API Keys reserviert und können nicht für normale Inhalte genutzt werden. Sie werden zusammen mit einem Validierungsschema automatisch beim Start der Anwendung zu bestehenden Datenbanken hinzugefügt sowie zudem während des Betriebes bei der Anlage/Aktualisierung von Datenbanken erstellt.

5.5.5. Validierung von API Keys

Jeder API Key wird bei Anlage und Aktualisierung gegen ein hinterlegtes JSON-Schema validiert. Das JSON-Schema sichert die grundlegende Struktur von API Keys ab und kann unter /<database>/_schemas/apikeys abgefragt werden.

Weitere Validierungen stellen sicher, dass keine zwei API Keys mit demselben key angelegt werden können. Ebenso darf ein API Key eine URL nicht mehrfach enthalten.

Genügt ein API Key den Anforderungen nicht, so wird der entsprechende Request mit HTTP-Status 400 abgewiesen.

Sollte das JSON-Schema zuvor nicht erfolgreich in der Datenbank hinterlegt worden sein, so werden Requests mit HTTP-Status 500 beantwortet.

Das key-Attribut eines API Keys sollte eine valide UUID enthalten. Das Format einer UUID ist nach RFC 4122 strikt vorgegeben. Dazu gehört insbesondere auch das Vorliegen von Kleinbuchstaben. Auch wenn die CaaS-Plattform diese Eigenschaft aktuell noch nicht validiert, behalten wir uns vor diese Einschränkung in Zukunft zu aktivieren.

5.6. Indizes für eine effiziente Abfrageausführung

Die Laufzeit von Abfragen mit Filtern kann sich bei steigender Anzahl an Dokumenten in einer Collection vergrößern. Übersteigt sie einen bestimmten Wert, so wird die Abfrage von der REST-Schnittstelle mit HTTP-Status 408 beantwortet. Eine effizientere Ausführung lässt sich erreichen, indem auf den Attributen, die in den betroffenen Filterabfragen verwendet werden, ein Index erstellt wird.

Detaillierte Informationen zu Datenbank-Indizes entnehmen Sie bitte der Dokumentation der MongoDB.

5.6.1. Vordefinierte Indizes

Falls Sie CaaS Connect im Einsatz haben, werden bereits vordefinierte Indizes erstellt, die einige häufig vorkommende Filterabfragen unterstützen.

Die genauen Definitionen können unter https:/REST-HOST:PORT/<database>/<collection>/_indexes/ abgerufen werden.

5.6.2. Kundenindividuelle Indizes

Sollten die vordefinierten Indizes Ihre Anwendungsfälle nicht abdecken und Sie bei Abfragen lange Antwortzeiten bis hin zu Request Timeouts beobachten, so können Sie eigene Indizes erstellen. Zum Management der gewünschten Indizes kann die REST-Schnittstelle genutzt werden. Die Vorgehensweise ist in der RESTHeart-Dokumentation beschrieben.

Bitte legen Sie nur die Indexe an, die Sie benötigen.

5.7. Verwalten von GraphQL-Applikationen

Mit der REST-Schnittstelle ist es möglich, GraphQL-Applikationen zu erstellen, zu aktualisieren und zu löschen.

Eine Kurzanleitung, die als Einstieg dient, ist im Anhang unter Quickstart: GraphQL-Applikationen zu finden.

Diese Applikationen bestehen aus einer Definition und einem GraphQL-Schema. Die Definition muss zunächst in der reservierten Collection gql-apps für jede gewünschte GraphQL-Applikation erstellt werden. Die Collection wird automatisch bei der Anlage/Aktualisierung von Datenbanken erzeugt. Sollte sie jedoch trotzdem noch nicht in der Datenbank existieren, so muss sie zuvor mit einem PUT-Request angelegt werden.

Beim Bearbeiten der GraphQL-App-Definition (z.B. Erstellen/Aktualisieren einer Applikation) werden die Berechtigungen des API Key überprüft. Eine Operation kann nur durchgeführt werden, wenn der API Key Zugriff auf alle in der Definition aufgeführten Datenbanken und Collections hat.

Die gql-apps Collections aller Datenbanken sind für GraphQL-Zwecke reserviert und können nicht für normale Inhalte verwendet werden.

Mutations werden derzeit nicht unterstützt.

5.7.1. Erstellen/Aktualisieren einer Applikation

Um eine GraphQL-Applikation zu erstellen oder zu aktualisieren, wird eine PUT-Anfrage mit der App-Definition an die folgende URL gestellt:
https://REST-HOST:PORT/<tenant>/gql-apps/<tenant>___<name>

Eine GraphQL-Applikation kann die Daten mittels einer MongoDB Query oder Aggregation abfragen. Das Kapitel Object Mappings der RestHeart Dokumentation enthält genauere Informationen. Ein Beispiel einer GraphQL-Applikation, die eine Aggregation nutzt, ist im Anhang zu finden.

Variablen innerhalb von berechtigungsrelevanten Attributen von Aggregation Stages (wie z.B. Datenbank- oder Collection-Namen) sind nicht erlaubt.

Analog zu Seitengrößen Limits für Abfragen für die REST-Schnittstelle existiert auch für GraphQL-Abfragen eine Beschränkung der Ergebnismenge. Der Standardwert beträgt 20 Dokumente. Bitte nutzen Sie Pagination Argumente, sofern mehr Dokumente abgefragt werden müssen. Weitere Informationen können dem Bereich Field to Field mapping aus dem Object Mappings Kapitel der RestHeart Dokumentation entnommen werden.

Durch das Erstellen der Definition wird ein Endpunkt unter folgender URL provisioniert:
https://REST-HOST:PORT/graphql/<tenant>___<name>

Weitere Informationen über die Ausführung von GraphQL-Abfragen befinden sich im Kapitel GraphQL-API.

Bei komplexen Mappings mit mehreren Fremdschlüsselbeziehungen kann es bei Abfragen zu erhöhten Antwortzeiten kommen. Für eine effizientere Abfrageausführung empfehlen wir die Verwendung von Indizes. Auch die Konfiguration von Batching und Caching kann bei der Optimierung von Antwortzeiten helfen. Details dazu finden Sie in dieser Dokumentation.

Alle Operationen an GraphQL-Applikationen über die REST-Schnittstelle werden im Hintergrund aus technischen Gründen in die globale caas_admin/gql-apps Collection gespiegelt.

Das Kapitel Re-Synchronisierung der vorhandenen GraphQL-Applikationen enthält weitere Informationen, wie dieser Mechanismus auch manuell ausgeführt werden kann.

Zur Erstellung einer GraphQL-Applikation bestehen zwei Möglichkeiten:

Verwendung des JSON Body ("application/json")

Die App-Definition mit den Bereichen descriptor, schema und mapping wird mit dem Request-Body (Content-Type application/json) übergeben. Der Parameter descriptor.uri muss mit dem letzten Pfadsegment der URL übereinstimmen (d.h. <tenant>___<name>) und ist im CaaS - abweichend von den Angaben in der RESTHeart Dokumentation - nicht optional.

Ein Beispiel für eine solche GraphQL-App-Definition ist im Kapitel GraphQL Beispiel-Anwendung zu finden.

Verwendung des Datei-Uploads ("multipart/form-data")

Die App-Definition und das Schema können auch mit einem Multipart-Upload (Content-Type multipart/form-data) erstellt/aktualisiert werden. Dies ermöglicht die Speicherung der App-Definition und des Schemas als separate Dateien und, was noch wichtiger ist, das Schema muss bei der Erstellung/Aktualisierung der Applikation nicht in JSON kodiert werden.

Um die App-Definition und das Schema hochzuladen, müssen beide als einzelne Teile im Request enthalten sein:

  • Teil: app

    • Beinhaltet die App-Definition als JSON.

  • Teil: schema

    • Enthält die Rohtextversion des Schemas in der Schemadefinitionssprache GraphQL.

Hochladen der Dateien mittels curl
curl -i -X PUT \
  -H "Authorization: Bearer $API_KEY" \
  -F app=@my-app-def.json \
  -F schema=@my-schema.gql \
  https://REST-HOST:PORT/<tenant>/gql-apps/<tenant>___<name>

Für schnelle Feedback-Zyklen während der Entwicklung kann dies mit einem Tool zur Überwachung von Datei-Änderungen kombiniert werden, um die GraphQL-Applikation kontinuierlich zu aktualisieren:

Kontinuierliches Hochladen der Dateien mittels fswatch und curl
fswatch -o my-app-def.json my-schema.gql | xargs -n1 -I{} \
  curl -i -X PUT \
  -H "Authorization: Bearer $API_KEY" \
  -F app=@my-app-def.json \
  -F schema=@my-schema.gql \
  https://REST-HOST:PORT/<tenant>/gql-apps/<tenant>___<name>

5.7.2. Löschen der Applikation

Um eine GraphQL-Applikationen zu löschen, wird eine DELETE-Anfrage an die folgende URL gestellt:
https://REST-HOST:PORT/<tenant>/gql-apps/<tenant>___<name>

5.7.3. Re-Synchronisierung der vorhandenen GraphQL-Applikationen

Unter bestimmten Bedingungen, wie zum Beispiel nach der Wiederherstellung einzelner Collections aus einem Backup, kann es vorkommen, dass die zuvor erstellten GraphQL-Applikationen nicht mehr synchronisiert sind. In so einem Fall müssen alle Tenant-spezifischen GraphQL-Applikationen neu synchronisiert werden.

Um alle vorhandenen GraphQL-Applikationen aller Tenants neu zu synchronisieren, ist eine POST-Anfrage gegen den HTTP-Endpunkt /_logic/sync-gql-apps auszuführen.

5.8. Push-Benachrichtigungen (Change Streams)

Es ist oft gewünscht, über Änderungen in der CaaS-Plattform informiert zu werden. Hierzu bietet die CaaS-Plattform Change Streams. Dieses Feature ermöglicht es eine Websocket-Verbindung zur CaaS-Plattform aufzubauen, worüber Events zu den verschiedenen Änderungen publiziert werden.

Change Streams werden angelegt, indem eine Definition in den Metadaten einer Collection abgelegt wird. Falls Sie CaaS Connect nutzen, so werden für Sie bereits einige vordefinierte Change Streams erstellt. Sie haben auch die Möglichkeit eigene Change Streams zu definieren.

Das Format der Events entspricht den Standard MongoDB Events.

Wir empfehlen bei der Arbeit mit Websockets möglicherweise auftretende Verbindungsabbrüche zu berücksichtigen. Regelmäßige ping Nachrichten und ein Mechanismus zur automatischen Wiederherstellung der Verbindung sollten in Ihrer Implementierung eingebaut werden.

Sie finden ein Beispiel für die Nutzung von Change Streams im Browser im Appendix.

5.9. Zusätzliche Informationen

Weitere Informationen über die Funktionalität der REST-Schnittstelle finden Sie in der offiziellen RESTHeart-Dokumentation.

6. GraphQL-API

Jede der über die Management-API definierten GraphQL-Applikationen (siehe Verwalten von GraphQL-Applikationen) stellt einen GraphQL API-Endpunkt bereit. Dieser Endpunkt kann zum Abrufen von Daten verwendet werden (siehe Daten abrufen).

Mutations werden derzeit nicht unterstützt.

6.1. Authentifizierung und Autorisierung

Der Authentifizierungs- und Autorisierungsprozess für GraphQL-Abfragen funktioniert anders als Abfragen für die REST-Schnittstelle. Anders als Abfragen für die REST-Schnittstelle, bei der der Request Path der Abfrage gegen eine Liste von erlaubten URLs geprüft wird, werden GraphQL-Abfragen mit einer der folgenden Zugriffschecks autorisiert.

Für lokale API Keys gibt es zusätzlich die Bedingung, dass diese nur auf GraphQL-Applikationen der gleichen Datenbank zugreifen dürfen.

  1. Explizite Ausführungserlaubnis
    Prüft, ob der API Key eine explizite Erlaubnis hat (GRAPHQL Berechtigung des API Keys), Abfragen an die GraphQL-Applikation zu senden.

  2. Implizite Ausführungserlaubnis
    Prüft, ob der API Key Zugriff zu allen Datenbanken und Collections hat. Dies wird geprüft, indem die URL Berechtigungen (PREFIX und REGEX) des API Keys mit den Pfaden aller Datenbanken und Collections abgeglichen werden, auf die die GraphQL-Applikation den Zugriff ermöglicht.

Weitere Informationen zu den verschiedenen Arten von Berechtigungen eines API Keys können dem Kapitel Autorisierungsmodell entnommen werden.

6.2. Daten abrufen

Die GraphQL-API kann über folgende HTTP-Endpunkte abgefragt werden:

https://REST-HOST:PORT/graphql/<app-uri>

Um Daten abzufragen, senden Sie einen POST-Request mit JSON an den gewünschten Endpunkt und geben die Abfrage im Request Body an, z. B.:

Abfrage der Daten via GraphQL mittels curl
curl -i -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -d '{"query": "query($lang: [String!]){products(_language: $lang) {name description categories {name} picture {name binaryUrl width height}}}", "variables": {"lang": ["EN"]}}' \
  https://REST-HOST:PORT/graphql/<app-uri>

Ein ausführlicheres Beispiel für die Verwendung von GraphQL finden Sie im Anhang .

7. Metriken

Metriken dienen der Überwachung (Monitoring) und der Fehleranalyse der CaaS-Komponenten im laufenden Betrieb und sind über HTTP-Endpunkte abrufbar. Sofern Metriken im Prometheus-Format vorliegen, werden dafür entsprechende ServiceMonitors erzeugt, siehe auch Prometheus-ServiceMonitors.

7.1. REST-Schnittstelle

Healthcheck

Der Healthcheck-Endpunkt stellt Informationen über die Funktionsfähigkeit der entsprechenden Komponente in Form eines JSON-Dokuments bereit. Dieser Zustand wird aus mehreren Prüfungen berechnet. Sind alle Prüfungen erfolgreich, hat die JSON-Antwort den HTTP-Status 200. Sobald mindestens eine Prüfung den Wert false besitzt, erfolgt eine Antwort mit dem HTTP-Status 500.

Die Abfrage erfolgt unter der URL: http://REST-HOST:PORT/_logic/healthcheck

Die Funktionsfähigkeit der REST-Schnittstelle hängt sowohl von der Erreichbarkeit des MongoDB-Clusters als auch von der Existenz eines Primary Nodes ab. Besitzt der Cluster keinen Primary Node, ist die Durchführung von Schreiboperationen auf der MongoDB nicht möglich.

HTTP-Metriken

Metriken zu der Performance der REST-Schnittstelle können im Prometheus-Format unter den folgenden URLs abgerufen werden:

  • http://REST-HOST:PORT/_logic/metrics

  • http://REST-HOST:PORT/_logic/metrics-caas

  • http://REST-HOST:PORT/_logic/metrics-jvm

7.2. MongoDB

Die Metriken der MongoDB werden über einen Sidecar Container zu Verfügung gestellt. Dieser greift mit einem separaten Datenbank-Benutzer auf die Metriken der MongoDB zu und stellt diese über HTTP bereit.

Die Metriken können unter der folgenden URL abgerufen werden: http://MONGODB-HOST:METRICS-PORT/metrics

Bitte beachten Sie, dass die Metriken der MongoDB über einen separaten Port ausgeliefert werden. Dieser Port ist nicht von außerhalb des Clusters zugreifbar und daher auch nicht durch Authentifizierung geschützt.

8. Wartung

Die Übertragung von Daten in den CaaS kann nur funktionieren, wenn die einzelnen Komponenten einwandfrei arbeiten. Treten Störungen auf oder ist eine Aktualisierung notwendig, sind daher stets alle CaaS-Komponenten zu betrachten. Die nachfolgenden Unterkapitel beschreiben die notwendigen Schritte einer Fehleranalyse bei einer vorliegenden Störung sowie die Durchführung eines Backups bzw. Updates.

8.1. Fehleranalyse

Der CaaS ist ein verteiltes System und basiert auf dem Zusammenspiel unterschiedlicher Komponenten. Jede einzelne dieser Komponente kann potenziell Fehler erzeugen. Tritt während der Verwendung des CaaS eine Störung auf, können dieser daher verschiedene Ursachen zugrunde liegen. Nachfolgend werden die grundlegenden Analyseschritte zur Ermittlung von Störungsursachen erläutert.

Zustand der Komponenten

Der Zustand der einzelnen Komponenten der CaaS-Plattform lässt sich mithilfe des Befehls kubectl get pods --namespace=<namespace> überprüfen. Weicht der Status einer Instanz vom Status Running bzw. ready ab, ist es empfehlenswert, die Fehlersuche an dieser Stelle zu beginnen und die zugehörigen Logfiles zu überprüfen.

Bestehen Probleme mit der Mongo-Datenbank, ist zu prüfen, ob ein Primary-Knoten existiert. Unterschreitet die Anzahl verfügbarer Instanzen einen Wert von 50 % der konfigurierten Instanzen, kann kein Primary-Knoten mehr gewählt werden. Dieser ist für die Funktionsfähigkeit der REST-Schnittstelle jedoch unerlässlich. Das Fehlen eines Primary-Knotens führt dazu, dass die Pods der REST-Schnittstelle nicht mehr den Status ready besitzen und somit unerreichbar sind.

Das Kapitel Consider Fault Tolerance der MongoDB-Dokumentation beschreibt, wie viele Knoten explizit ausfallen können, bis die Bestimmung eines neuen Primary-Knoten unmöglich ist.

Analyse der Logs

Im Problemfall sind die Logfiles einen guten Ausgangspunkt für die Analyse. Sie bieten die Möglichkeit, alle Vorgänge auf den Systemen nachzuvollziehen. Auf diesem Weg werden eventuelle Fehler und Warnungen ersichtlich.

Aktuelle Logfiles der CaaS-Komponenten sind mittels kubectl --namespace=<namespace> logs <pod> einsehbar, beinhalten jedoch nur Ereignisse, die innerhalb der Lebenszeit der aktuellen Instanz stattgefunden haben. Um dennoch nach einem Absturz bzw. Neustart einer Instanz die Logfiles sinnvoll analysieren zu können, empfehlen wir die Einrichtung eines zentralen Logging-Systems.

Die Protokolldateien können nur für den aktuell laufenden Container eingesehen werden. Aus diesem Grund ist es notwendig, einen persistenten Speicher einzurichten, um auf die Protokolldateien von bereits beendeten oder neu gestarteten Containern zugreifen zu können.

8.2. Backup

Die Architektur des CaaS besteht aus verschiedenen, voneinander unabhängigen Komponenten, die unterschiedliche Informationen erzeugen und verarbeiten. Besteht die Notwendigkeit einer Datensicherung, muss diese daher in Abhängigkeit der jeweiligen Komponente erfolgen.

Ein Backup der im CaaS gespeicherten Informationen muss über die Standardmechanismen der Mongo-Datenbank erfolgen. Dabei kann entweder eine Kopie der zugrundeliegenden Dateien erstellt oder mongodump verwendet werden.

8.3. Update

Der Betrieb der CaaS-Plattform mit Helm in Kubernetes bietet die Möglichkeit einer Aktualisierung auf den neuen Stand, ohne dass dabei eine Neuinstallation notwendig ist.

Vor der Aktualisierung der Mongo-Datenbank wird ein Backup dringend empfohlen.

Der Befehl helm list --all-namespaces liefert zunächst eine Liste aller bereits installierten Helm-Charts. Diese enthält sowohl die Version als auch den Namespace des entsprechenden Releases.

Beispielliste installierter Releases
\$ helm list --all-namespaces
NAME            NAMESPACE    REVISION  UPDATED             STATUS    CHART        APP VERSION
firstinstance   integration  1         2019-12-11 15:51..  DEPLOYED  caas-2.10.4  caas-2.10.4
secondinstance  staging      1         2019-12-12 09:31..  DEPLOYED  caas-2.10.4  caas-2.10.4

Für die Aktualisierung eines Releases sind nacheinander die folgenden Schritte durchzuführen:

Übernahme der Einstellungen

Um einen Verlust der bisherigen Einstellungen zu vermeiden, ist es notwendig, die custom-values.yaml-Datei, mit der die initiale Installation des Helm-Charts durchgeführt wurde, vorliegen zu haben.

Übernahme weiterer Anpassungen

Bestehen Anpassungen an Dateien (z. B. im config-Verzeichnis), sind diese ebenfalls zu übernehmen.

Aktualisierung

Nach der Durchführung der vorherigen Schritte kann anschließend die Aktualisierung gestartet werden. Sie ersetzt die bestehende Installation durch die neue Version, ohne dass eine Downtime entsteht. Dafür ist der folgende Befehl auszuführen, durch den der Vorgang gestartet wird:

helm upgrade RELEASE_NAME caas-20.0.0.tgz --values /path/to/custom-values.yaml

9. Appendix

9.1. Beispiele

9.1.1. Change Stream Beispiel

Nutzung von Change Streams mit Javascript und Browser-API
<script type="module">
  import PersistentWebSocket from 'https://cdn.jsdelivr.net/npm/pws@5/dist/index.esm.min.js';

  // Replace this with your API key (needs read access for the preview collection)
  const apiKey = "your-api-key";

  // Replace this with your preview collection url (if not known copy from CaaS Connect Project App)
  // e.g. "https://REST-HOST:PORT/my-tenant-id/f948bb48-4f6b-4a8a-b521-338c9d352f2b.preview.content"
  const previewCollectionUrl = new URL("your-preview-collection-url");

  const pathSegments = previewCollectionUrl.pathname.split("/");
  if (pathSegments.length !== 3) {
    throw new Error(`The format of the provided url '${previewCollectionUrl}' is incorrect and should only contain two path segments`);
  }

  (async function(){
    // Retrieving temporary auth token
    const token = await fetch(new URL(`_logic/securetoken?tenant=${pathSegments[1]}`, previewCollectionUrl.origin).href, {
      headers: {'Authorization': `Bearer ${apiKey}`}
    }).then((response) => response.json()).then((token) => token.securetoken).catch(console.error);

    // Establishing WebSocket connection to the change stream "crud"
    // ("crud" is the default change stream that the CaaS Connect module provides)
    const wsUrl = `wss://${previewCollectionUrl.host + previewCollectionUrl.pathname}`
      + `/_streams/crud?securetoken=${token}`;
    const pws = new PersistentWebSocket(wsUrl, { pingTimeout: 60000 });

    // Handling change events
    pws.onmessage = event => {
      const {
        documentKey: {_id: documentId},
        operationType: changeType,
      } = JSON.parse(event.data);
      console.log(`Received event for '${documentId}' with change type '${changeType}'`);
    }
  })();
</script>

9.1.2. GraphQL Beispiel-Anwendung

In diesem Kapitel wird ein Anwendungsfall für eine GraphQL-Applikation exemplarisch beschrieben. Dazu werden die einzelnen Schritte skizziert, die zum Anlegen einer GraphQL-Applikation und der späteren Nutzung gehören.

Anlegen der GraphQL-App-Definition

In dem Beispielszenario wird eine GraphQL-Applikation angelegt, mit der man im CaaS liegende Datensätze abfragen kann. Als Datensätze werden hier die Produkte aus dem Beispielprojekt der fiktiven Firma “Smart Living” verwendet. In den Datensätzen befindliche Bildreferenzen und Produktkategorien werden dabei direkt aufgelöst.

Der gesamte Aufruf zur Erstellung der GraphQL-App-Definition für dieses Beispielszenario sieht wie folgt aus.

Beispiel einer vollständigen GraphQL-App-Definition
curl --location --request PUT 'https://REST-HOST:PORT/mycorp-dev/gql-apps/mycorp-dev___products' \
--header 'Authorization: Bearer <PERMITTED_APIKEY>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "descriptor": {
        "name": "products",
        "description": "example app to fetch product relevant information from SLG",
        "enabled": true,
        "uri": "mycorp-dev___products"
    },

    "schema": "type Picture{ name: String! identifier: String! binaryUrl: String! width: Int! height: Int! } type Category{ name: String! identifier: String! } type Product{ name: String! identifier: String! description: String categories: [Category] picture: Picture } type Query{ products(_language: [String!] = [\"DE\", \"EN\"]): [Product] }",

    "mappings": {
        "Category": {
            "name": "displayName",
            "identifier": "_id"
        },
        "Picture": {
            "name": "displayName",
            "identifier": "_id",
            "binaryUrl": "resolutionsMetaData.ORIGINAL.url",
            "width": "resolutionsMetaData.ORIGINAL.width",
            "height": "resolutionsMetaData.ORIGINAL.height"
        },
        "Product": {
            "name": "displayName",
            "identifier": "_id",
            "description": "formData.tt_abstract.value",
            "picture": {
                "db": "mycorp-dev",
                "collection": "d8db6f24-0bf8-4f48-be47-5e41d8d427fd.preview.content",
                "find": {
                    "identifier": {
                        "$fk": "formData.tt_media.value.0.formData.st_media.value.identifier"
                    },
                    "locale.identifier": {
                        "$fk": "locale.identifier"
                    }
                }
            },
            "categories": {
                "db": "mycorp-dev",
                "collection": "d8db6f24-0bf8-4f48-be47-5e41d8d427fd.preview.content",
                "find": {
                    "identifier": {
                        "$in": {
                            "$fk": "formData.tt_categories.value.identifier"
                        }
                    },
                    "locale.identifier": {
                        "$fk": "locale.identifier"
                    }
                }
            }
        },
        "Query": {
            "products": {
                "db": "mycorp-dev",
                "collection": "d8db6f24-0bf8-4f48-be47-5e41d8d427fd.preview.content",
                "find": {
                    "locale.identifier": { "$in": { "$arg": "_language" } },
                    "entityType": "product"
                }
            }
        }
    }
}'

Bei der Erstellung einer GraphQL-App-Definition muss das Schema als JSON-String angegeben werden. Zur besseren Lesbarkeit empfehlen wir eine geeignetere Formatierung des Schemas.

Schema der GraphQL-App-Definition
Das Schema, welches für das Beispiel verwendet wird, enthält folgende Definitionen.

Beispiel eines formatierten GraphQL Schemas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Picture {
    name: String!
    identifier: String!
    binaryUrl: String!
    width: Int!
    height: Int!
}

type Category {
    name: String!
    identifier: String!
}

type Product {
    name: String!
    identifier: String!
    description: String
    categories: [Category]
    picture: Picture
}

type Query {
    products(_language: [String!] = ["DE", "EN"]): [Product]
}

In den Zeilen 1, 9 und 14 des Schemas beginnen die Typdefinitionen der Objekte, die in der GraphQL-Applikation verwendet werden. Zudem enthält jedes GraphQL-Schema einen Query Type (Zeile 22), in dem definiert wird, welche Daten von einer GraphQL-Applikation abgefragt werden können. Mehr Details zu Schemas in GraphQL können der GraphQL-Dokumentation entnommen werden.

In diesem Beispiel werden wir Produktdatensätze abfragen. Das wird in Zeile 23 des Schemas durch den Typen [Product] ermöglicht. Bei den Abfragen sollen die Sprachen angegeben werden können, für die wir Produkte erhalten wollen. Die Angabe der Sprache soll in diesem Beispiel optional sein. Diese Anforderung wird durch die Angabe einer Variablen mit dem Namen _language ermöglicht, die mit dem Standardwert ["DE", "EN"] vorbelegt ist.

Mapping der GraphQL-App-Definition
Das Mapping der GraphQL-App-Definition stellt die Verbindung zwischen dem Schema und den Daten in der Datenbank dar. Jeder Type, welcher im Schema beschrieben ist, benötigt im Allgemeinen einen expliziten Eintrag, daher ist dieser Teil einer GraphQL-App-Definition meist der längste. Es kann Situation geben, in denen die Felder im Type exakt so heißen sollen, wie die Schlüssel der Daten. In diesem Spezialfall ist kein expliziter Eintrag im Mapping notwendig. Details zu dem Mapping in GraphQL-App-Definition finden Sie in dem entsprechenden Kapitel in der RESTHeart-Dokumentation.
Das folgende Beispiel ist ein Auszug aus dem Anlegen einer GraphQL-App-Definition und klärt einige Anwendungsfälle auf.

Beispiel eines GraphQL-Mappings
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
{
  "Category": {
    "name": "displayName",
    "identifier": "_id"
  },
  "Picture": {
    "name": "displayName",
    "identifier": "_id",
    "binaryUrl": "resolutionsMetaData.ORIGINAL.url",
    "width": "resolutionsMetaData.ORIGINAL.width",
    "height": "resolutionsMetaData.ORIGINAL.height"
  },
  "Product": {
    "name": "displayName",
    "identifier": "_id",
    "description": "formData.tt_abstract.value",
    "picture": {
      "db": "mycorp-dev",
      "collection": "d8db6f24-0bf8-4f48-be47-5e41d8d427fd.preview.content",
      "find": {
        "identifier": {
          "$fk": "formData.tt_media.value.0.formData.st_media.value.identifier"
        },
        "locale.identifier": {
          "$fk": "locale.identifier"
        }
      }
    },
    "categories": {
      "db": "mycorp-dev",
      "collection": "d8db6f24-0bf8-4f48-be47-5e41d8d427fd.preview.content",
      "find": {
        "identifier": {
          "$in": {
            "$fk": "formData.tt_categories.value.identifier"
          }
        },
        "locale.identifier": {
          "$fk": "locale.identifier"
        }
      }
    }
  },
  "Query": {
    "products": {
      "db": "mycorp-dev",
      "collection": "d8db6f24-0bf8-4f48-be47-5e41d8d427fd.preview.content",
      "find": {
        "locale.identifier": {
          "$in": {
            "$arg": "_language"
          }
        },
        "entityType": "product"
      }
    }
  }
}

Der erste Anwendungsfall, der betrachtet wird, ist das sogenannte “Field to Field mapping”. Bei dieser Art Mapping wird einem Feld im Type ein entsprechendes Attribut der Daten zugewiesen. Ein Beispiel dafür ist in Zeile 3 zu sehen, bei dem das Feld Category.name aus dem Schema auf Attribut displayName aus den Daten verweist.

Der zweite Anwendungsfall ist das “Field to Query mapping”. Hierbei wird einem Feld im Type das Ergebnis einer Datenabfrage zugewiesen. Ein Beispiel für ein solches Mapping ist in Zeile 45ff. zu finden: das Feld Query.products wird abgebildet durch die Daten, die in der REST-Schnittstelle unter /mycorp-dev/d8db6f24-0bf8-4f48-be47-5e41d8d427fd.preview.content zu finden sind und den Filtern entityType": "product" und "locale.identifier": { "$in": { "$arg": "_language" } } entsprechen. Das bedeutet, dass genau die Produkte abgefragt werden, welche in der definierten Quelle liegen, eine Entität von “product” darstellen und eines der Sprachkürzel verwenden, die im Argument “_language” übergeben werden.

Ein weiteres Beispiel für ein “Field to Query mapping” ist ab Zeile 29 zu finden. In dieser Abbildungsdefinition werden die Produktkategorien, welche in separaten Datensätzen gepflegt sind, über eine Fremdschlüsselbeziehung identifiziert. Der komplette Eintrag von Zeile 29-42 sagt aus, dass in dem Feld Product.categories alle Produktkategorien aufgeführt werden, die unter /mycorp-dev/d8db6f24-0bf8-4f48-be47-5e41d8d427fd.preview.content liegen, deren identifier im Feld formData.tt_categories.value.identifier hinterlegt sind und deren locale.identifier exakt dem entspricht, was im Produktdatensatz als locale.identifier vorhanden ist. Da ein Produkt mehrere Kategorien im Datensatz unter formData.tt_categories.value.identifier referenzieren kann, wird hier mit dem Schlüssel $in gearbeitet.

Werden in einem "find" mehrere Filter angegeben, werden diese automatisch verbunden, wodurch ein zusätzlich klammerndes "$and entfällt.

Verwenden der GraphQL-Applikation

Abfragen können nun an die GraphQL-Applikation gesendet werden.

Beispiel einer GraphQL Abfrage
curl --location --request POST 'https://REST-HOST:PORT/graphql/mycorp-dev___products' \
--header 'Authorization: Bearer <PERMITTED_APIKEY>' \
--header 'Content-Type: application/json' \
--data-raw '{"query": "query($lang: [String!]){products(_language: $lang) {name description categories {name} picture {name binaryUrl width height}}}", "variables": {"lang": ["DE"]}}'

Dieses Beispiel zeigt die Abfrage der GraphQL-Applikation mit curl. Die Applikation steht immer unter dem Pfad /graphql/<descriptor.uri> zur Verfügung. Durch diese Query werden Produktdaten abhängig von der Variable $lang abgefragt. Die Variable wird dem im Schema definierten Argument _language als Wert übergeben. Da in der GraphQL-App-Definition ein sinnvoller Standardwert für _language im Schema enthalten ist, ist die Verwendung von Variablen in diesem Szenario optional. Weitere Details zu Argumenten und Variablen können der GraphQL-Dokumentation entnommen werden.

9.1.3. GraphQL-Beispiel mit Aggregations

Durch Aggregationen können komplexere Abfragen an den CaaS gestellt werden. So können z.B. dynamisch Attribute zu den Dokumenten der Antwort hinzugefügt werden. Es können aber noch deutlich komplexere Aggregationen erstellt werden. Eine Liste der zur Verfügung stehenden Aggregation Stages und Operators ist hier zu finden.

Beispiel einer GraphQL-App mit Aggregation im Query-Mapping
{
  "_id": "mytenantid-dev___pagerefs",
  "descriptor": {
    "name": "pagerefs",
    "description": "Query PageRefs",
    "enabled": true,
    "uri": "mytenantid-dev___pagerefs"
  },
  "schema": "type PageRef { _id: String projectId: String } type Query{ pageRefs(projectId: String): [PageRef] }",
  "mappings": {
    "PageRefs": {
      "count": "count"
    },
    "Query": {
      "pageRefs":{
        "db": "mytenantid-dev",
        "collection": "641154a9-b90c-4b10-a5f7-38677cbb5abc.release.content",
        "stages": [
          { "$match": { "fsType":"PageRef" }},
          { "$addFields": { "projectId": { "$arg": "projectId" } } }
        ]
      }
    }
  }
}

9.2. Tutorials

9.2.1. Quickstart: GraphQL-Applikationen

Diese Anleitung beinhaltet das Anlegen und Benutzen von GraphQL-Applikationen. Um die Anleitung abzuschließen, ist ein Grundverständnis der REST-Schnittstelle in Bezug auf Authentifizierung und der Abfrage von Dokumenten notwendig.

GraphQL-Applikationen ermöglichen das Abfragen von CaaS-Dokumenten über eigene GraphQL API-Endpunkte. Dabei stellen die Endpunkte die Dokumentendaten in einem selbst-definierten Format/Schema bereit.

In den folgenden Schritten werden wir beispielhaft eine GraphQL-Applikation erzeugen und benutzen, um Dokumentendaten abzufragen. Zum Senden der HTTP Anfragen wird curl benutzt.

  1. Erzeugen von Beispiel-Dokumenten

    Wir beginnen mit dem Anlegen von Beispieldaten, indem wir zuerst eine Collection anlegen.

    # set these once so you can re-use them for other commands
    TENANT='YOUR-TENANT-ID'
    API_KEY='YOUR-API-KEY'
    
    curl --location --request PUT "https://REST-HOST:PORT/$TENANT/posts" \
    --header "Authorization: Bearer $API_KEY"

    Anschließend erzeugen wird Dokumente in der Collection.

    # you can execute this multiple times to create many documents
    curl --location "https://REST-HOST:PORT/$TENANT/posts" \
    --header "Authorization: Bearer $API_KEY"
    --header 'Content-Type: application/json' \
    --data "{
        \"content\": \"My post created at $(date)..\"
    }"
  2. Definieren des gewünschten GraphQL-Schemas

    Nachdem die Dokumente angelegt sind, müssen wir ein Schema definieren, mit dem wir auf die Daten zugreifen können.

    Wir werden ein simples Modell für die Beispieldokumente aus dem letzten Schritt benutzen. Es können jedoch komplexere Datenmodelle erzeugt werden.

    Speichern der GraphQL-Schema-Definition (schema.gql)
    cat > schema.gql << EOF
    type BlogPost {
      content: String!
    }
    
    type Query {
      posts: [BlogPost!]
    }
    EOF
  3. GraphQL API-Endpunkt anlegen

    Im nächsten Schritt legen wir die GraphQL-Applikation mit unserem Schema an, sodass automatisch ein API-Endpunkt provisioniert wird.

    Zuerst müssen wir jedoch ein paar Details über die GraphQL-Applikation definieren, sodass CaaS weiß, wie der neue Endpunkt provisioniert wird und die Daten dafür bereitgestellt werden. Dazu müssen wir diese Informationen wie folgt in einer GraphQL-App-Definition festhalten.

    Speichern der GraphQL-App-Definition (app.json)
    cat > app.json << EOF
    {
        "descriptor": {
            "name": "myposts",
            "description": "Example app to fetch blog posts.",
            "enabled": true,
            "uri": "${TENANT}___myposts"
        },
        "mappings": {
            "Query": {
                "posts": {
                    "db": "$TENANT",
                    "collection": "posts",
                    "find": {
                        "content": { "\$exists": true }
                    }
                }
            }
        }
    }
    EOF

    Wir haben das Schema (schema.gql) und die dazugehörige App-Definition (app.json) vorbereitet. Diese können wir nun zusammen nutzen, um die GraphQL-Applikation mit der REST-Schnittstelle anzulegen.

    Anlegen der GraphQL-Applikation
    curl -X PUT \
    -H "Authorization: Bearer $API_KEY" \
    -F app=@app.json \
    -F schema=@schema.gql \
    https://REST-HOST:PORT/$TENANT/gql-apps/${TENANT}___myposts
  4. Abfragen von Daten mit dem neuen Endpunkt

    CaaS hat nun automatisch unseren neuen Endpunkt mit unseren Definitionen provisioniert. Der Endpunkt ist unter folgender URL verfügbar:

    https://REST-HOST:PORT/graphql/\{YOUR-TENANT-ID}___myposts

    Wir können nun Dokumentendaten mit diesem Endpunkt abfragen.

    curl --location "https://REST-HOST:PORT/graphql/${TENANT}___myposts" \
    --header "Authorization: Bearer $API_KEY" \
    --header 'Content-Type: application/json' \
    --data '{"query":"{ posts { content } }","variables":{}}'

    Die Antwort sollte ähnlich aussehen wie folgendes JSON:

    {
      "data": {
        "posts": [
          {
            "content": "My post created at Tue Aug  8 17:08:32 CEST 2023.."
          },
          {
            "content": "My post created at Tue Aug  8 17:12:23 CEST 2023.."
          }
        ]
      }
    }

Der Standardwert für die Seitengröße von GraphQL Abfragen beträgt 20. Sollten es notwendig sein, mehr als 20 Dokumente abzufragen, müssen Argumente für Pagination genutzt werden. Weitere Informationen können dem Bereich Field to Field mapping aus dem Object Mappings Kapitel der RestHeart Dokumentation entnommen werden.

9.3. Troubleshooting: Bekannte Fehler

9.3.1. Dateiupload mit PUT Request schlägt fehl

Die Fehlermeldungen

  • E11000 duplicate key error collection: [some-file-bucket].chunks index: files_id_1_n_1 dup key

  • oder error updating the file, the file bucket might have orphaned chunks

deuten darauf hin, dass verwaiste File Chunks in den MongoDB Daten existieren. Die verwaisten Daten können mit folgendem mongo Shell Skript gelöscht werden:

Bereinigung eines Files Buckets durch Löschen verwaister File Chunks.
// Name of the file bucket to clean up (e.g., my-bucket.files)
var filesBucket = "{YOUR_FILE_BUCKET_NAME}";

var chunksCollection = filesBucket.substring(0, filesBucket.lastIndexOf(".")) + ".chunks";
db[chunksCollection].aggregate([
  // avoid accumulating binary data in memory
  { $unset: "data" },
  {
      $lookup: {
        from: filesBucket,
        localField: "files_id",
        foreignField: "_id",
        as: "fileMetadata",
      }
  },
  { $match: { fileMetadata: { $size: 0 } } }
]).forEach(function (c) {
  db[chunksCollection].deleteOne({ _id: c._id });
  print("Removed orphaned GridFS chunk with id " + c._id);
});

10. Hilfe

Der Technical Support der Crownpeak Technology GmbH bietet qualifizierte technische Unterstützung zu allen Themen, die FirstSpirit™ als Produkt betreffen. Weitere Hilfe zu vielen relevanten Themen erhalten und finden Sie in auch in unserer Community.