Avatar of Jochen Christ

This article is also available in English

Um die Softwareentwicklung in mehreren Teams effizient und zielgerichtet durchzuführen, lassen sich Softwaresysteme durch Architekturkonzepte wie Microservices und Self-contained Systems entlang der fachlichen Grenzen in eigenständige Bereiche unterteilen. Im Zuge dieser Trennung entstehen Schnittstellen zwischen diesen Systemen, da der Zugriff auf Daten aus anderen Bereichen wie Bestellungen oder Kundendaten erforderlich ist und gegebenenfalls Nachfolgeaktionen wie Rechnungsstellung oder Reporting auszulösen sind.

Schnittstellen lassen sich synchron oder asynchron anlegen. Synchrone Schnittstellen sind entfernte Funktionsaufrufe, die zu einer Anfrage eine Antwort zurückgeben. Sie sind in der Regel zwar einfach zu implementieren, sollten in unternehmenskritischen Systemkomponenten jedoch besser nicht zum Einsatz kommen, da sich Verfügbarkeiten der beteiligten Systeme multiplizieren und die Latenzzeiten der Aufrufe addieren. So sollte beispielsweise die Verfügbarkeit eines Onlineshops möglichst nicht davon abhängen, ob das Finanzbuchhaltungssystem gerade durch ein Wartungsfenster blockiert ist. Im Gegenzug sollte ein Monatsabschluss der Finanzbuchhaltung auch den Onlineshop nicht durch massenhafte GET-Requests negativ beeinflussen.

Beispiel für einen synchronen GET-Aufruf einer HTTP-API:

GET /api/customers/387484543
200 Ok
{
	"customerId": "387484543",
	"firstname": "Alice",
	"lastname": "Springs",
	"email": "[email protected]",
	"newsletter": true,
	"termsAccepted": ["2018-01-01"],
	"locked": false,
	"created": "2018-04-12T12:13:58.876111Z",
	"updated": "2021-09-29T11:44:46.254440Z"
}

Um die beschriebenen Probleme zu verhindern, sollten Entwicklerinnen und Entwickler auf asynchrone Schnittstellen ausweichen. Traditionell kommen diese häufig im Rahmen der Batch-Verarbeitung zum Einsatz. Dabei wird beispielsweise ein Datei-Export nachts generiert und anderen Systemen anschließend auf einem File-Server zur Verfügung gestellt. Das Hauptproblem hierbei ist jedoch die Aktualität der Daten – niemand möchte mit veralteten Daten arbeiten.

Message-Broker-Systeme führen zu neuen Abhängigkeiten im Gesamtsystem (Abb. 1)
Message-Broker-Systeme führen zu neuen Abhängigkeiten im Gesamtsystem (Abb. 1)

Um Daten auch asynchron nahezu in Echtzeit austauschen zu können, haben sich nachrichtenorientierte Broker-Systeme etabliert. Dabei schickt ein System eine Nachricht über ein Protokoll wie AMQP (Advanced Message Queuing Protocol) an ein Topic und ein registrierter Consumer verarbeitet sie, sobald er dafür bereit ist. Der Broker agiert als Middleware-System, das die Nachrichten zwischenspeichert und verwaltet. Typische Vertreter dieser Art von Middleware sind MQSeries, Apache Kafka und RabbitMQ, aber auch AWS SQS oder das in der Google Cloud verwendete Pub/Sub.

Middleware führt zu Abhängigkeiten

Der systemübergreifende Einsatz solcher Broker-Systeme als Schnittstellentechnologie führt in der Regel zu organisatorischen und technischen Abhängigkeiten. Unter anderem stellt sich die Frage nach der organisatorischen Verantwortung: Welches Team kümmert sich um den Betrieb dieses Systems mit Installation, Betrieb, Bereitschaft und Abstimmung von Versionsupdates? Und wer übernimmt die Kosten? Das betreffende Team ist zudem immer dann involviert, wenn es darum geht, neue Topics oder Berechtigungen einzurichten.

Die Wahl des Broker-Systems zieht auch technische Konsequenzen für die beteiligten Systeme nach sich. Alle Beteiligten müssen versionskompatible Client-Bibliotheken verwenden und es wird ein Serialisierungsformat vorgegeben, das beispielsweise im Falle von Avro oder Protobuf ein spezielles Schema-Management erfordert. Kommt es zu einem Fehler, wenn beispielsweise eine Nachricht nicht an den Broker zugestellt werden kann, hilft häufig nur eine aufwendige, teamübergreifende Fehleranalyse.

HTTP-Feeds als Alternative

Solche durch Middleware verursachten Abhängigkeiten sollten Entwicklerinnen und Entwickler möglichst meiden oder zumindest minimieren. Denn asynchrone Schnittstellen lassen sich auch ganz ohne Middleware mit klassischen HTTP-Endpunkten als Feeds umsetzen. Feeds stellen dabei eine chronologische Folge von Daten zur Verfügung.

Konzeptionelle Darstellung eines Data-Feeds (Abb. 2)
Konzeptionelle Darstellung eines Data-Feeds (Abb. 2)

Die für die Daten verantwortlichen Systeme stellen technisch einen HTTP-Endpoint zur Verfügung, über den sich die Daten abfragen lassen. Consumer greifen per GET-Abfrage darauf zu und können sowohl einmalig historische als auch kontinuierlich neue Daten verarbeiten.

Datenreplikation oder Event-Feeds – oder beides?

HTTP-Feeds lassen sich in unterschiedlichsten Anwendungsszenarien zum Entkoppeln von Systemen einsetzen. Bei der Datenreplikation gilt es, alle Entitäten eines Geschäftsobjekts in einem definierten Format bereitzustellen und von den den Feed empfangenden Systemen (Consumer) in einer lokalen Datenbank in der benötigten Form abzuspeichern. Im Feed sind immer alle Geschäftsobjekte mindestens einmal vorhanden, wobei im Falle einer Änderung der komplette aktuelle Zustand als Feed-Eintrag erneut am Ende hinzukommt. Ältere Einträge, die den gleichen fachlichen Schlüssel haben (zum Beispiel Kundennummer oder Bestellnummer), können dann entfallen. Subscriber des Feeds erhalten so immer den aktuellen Stand aller Geschäftsobjekte und können diesen auch nachträglich erneut einspielen, beispielsweise dann, wenn in der lokalen Datenbank ein weiteres Attribut abgespeichert werden soll. Datenreplikation dient dazu, schnelle Lookups unabhängig von der Verfügbarkeit des Quellsystems durchführen zu können, beispielsweise um Kundendaten anhand einer Kundennummer zu lesen oder um Datenanalysen in einem separaten Analysesystem durchzuführen.

Eine weitere Form der Feeds sind Event-Feeds. Hierbei werden fachliche Ereignisse, genannt Domain Events, in einem Feed publiziert. Sie beschreiben, was zu einem bestimmten Zeitpunkt in einem System passiert ist, enthalten aber nicht alle Informationen des betroffenen Geschäftsobjekts. Zum Beispiel kann eine Betrugsprüfung zu einem negativen Ergebnis führen (Betrugsverdacht) oder eine Kundin kann ihre E-Mail-Adresse geändert haben. Den Feed empfangende Systeme (Consumer) können anhand dieser Events Aktionen auslösen, die in ihrem fachlichen Kontext relevant sind.

Interessanterweise könnten auch Daten-Feeds entsprechende Aktionen auslösen: Wenn eine neue oder aktualisierte Entität im Feed vorhanden ist, lässt sich im Consumer-System eine entsprechende fachliche Aktion auslösen. Beispielsweise lässt sich ein neuer Eintrag in einem Kunden-Feed dazu nutzen, die E-Mail-Adresse einer Kundin oder eines Kunden in das E-Mail-Marketing-System einzutragen.

In beiden Fällen ist es wichtig, stets auf fachliche und technische Idempotenz in den Consumer-Systemen zu achten, da es häufiger vorkommen kann, dass ein Feed aus technischen Gründen neu einzulesen ist. Darüber hinaus muss geregelt sein, wie damit zu verfahren ist, wenn ein Geschäftsobjekt gelöscht werden soll.

Umsetzung von HTTP-Feeds

HTTP-Feeds lassen sich über verschiedene Techniken, Datenformate und Protokolle umsetzen.

Atom

Zu den prominenten Feed-Techniken zählt das Atom-Syndication-Format. Vergleichbar zum RSS-Standard stellen Nachrichtenwebseiten und Blogs per Atom typischerweise neue Artikel in Form einer XML-Schnittstelle bereit. Dieses Format enthält ein spezifiziertes Datenmodell, das für Nachrichten und Blogbeiträge optimiert ist, sich aber grundsätzlich für beliebige Datensätze nutzen lässt.

Das Atom-Format verwendet XML (Abb. 3)
Das Atom-Format verwendet XML (Abb. 3)

Atom beschreibt allerdings nur ein Format, nicht aber das Protokoll, das den Informationsabruf regelt. Daher ist auch das kontinuierliche Lesen neuer Einträge bei Atom nicht spezifiziert.

Polling von REST-APIs

Ein Feed lässt sich auch vollständig auf reinen REST-APIs aufbauen, wobei neue Einträge in einem HTTP-GET-Endpoint chronologisch aufsteigend als Collection bereitstehen. Um bei großen Datenmengen übermäßig lange Antwortzeiten zu vermeiden, empfiehlt es sich, die Anzahl der Einträge pro Request zu limitieren – beispielsweise auf 1000 Einträge. Durch eine Verlinkung lassen sich dann weitere Daten abrufen, bis alle verarbeitet sind.

In der REST-API sind die chronologisch sortierten Einträge durch einen next-Link verkettet (Abb. 4)
In der REST-API sind die chronologisch sortierten Einträge durch einen next-Link verkettet (Abb. 4)

Über ein Polling-Verfahren wird eine Subscription umgesetzt: Sobald alle historischen Einträge verarbeitet sind, erhält der Consumer ein leeres Array als Response. Der Consumer führt nun in einer Endlosschleife einfach weitere Anfragen auf die letzte verlinkte URL durch. Auf leere Responses folgt beim klassischen Polling eine definierte Pause, bevor mit einem weiteren GET-Aufruf die Prüfung auf neue Einträge erfolgt.

Durch ein Long-Polling-Verfahren lässt sich die Latenz bei der Verarbeitung neuer Events minimieren (Abb. 5)
Durch ein Long-Polling-Verfahren lässt sich die Latenz bei der Verarbeitung neuer Events minimieren (Abb. 5)

Alternativ bietet ein Long-Polling-Verfahren dem Server die Möglichkeit, den Request so lange offen zu halten, bis entweder neue Daten vorhanden sind oder ein definiertes Time-out abgelaufen ist. Der Client kann dann sofort eine neue Anfrage stellen. Das Long-Polling-Verfahren minimiert die Latenz beim Verarbeiten neuer Einträge, auf Kosten offener Connections zum Server. Die Open-Source Library http-feeds implementiert ein solches Long-Polling-Verfahren mittels REST-APIs sowohl für Daten-Feeds als auch für Event-Feeds.

Server-Sent Events nach HTML5-Standard

Eine dritte Option zum Umsetzen von HTTP-Feeds bieten Server-sent Events. Wie der Name dieses HTML-Standards andeutet, erfolgt die Kommunikation der Daten hierbei One-Way vom Server zum Client. Das Client-System baut eine dauerhafte Verbindung zu einem HTTP-Endpoint auf, über die der Server Daten im MIME-Type-Format text/event-stream schreibt – ohne die Verbindung zu beenden. Der Client kann die Datensätze, die durch eine Leerzeile voneinander getrennt sind, kontinuierlich lesen und verarbeiten. Kommt es zu einem Verbindungsabbruch, lässt sich die Verarbeitung durch die Angabe der Last-Event-ID als Header-Feld an einem bestimmten Datensatz fortsetzen.

Gestreamte Server-Sent Events sind durch Leerzeilen getrennt (Abb. 6)
)

Server-Sent Events eignen sich für die asynchrone Bereitstellung von Daten und lassen sich in allen relevanten Browsern per JavaScript nutzen. Das auf Langlebigkeit ausgelegte Connection-Handling erschwert allerdings den Betrieb und das Debugging, insbesondere bei Endpoints, die mehrere Millionen Einträge enthalten. Darüber hinaus ist die Response kein reines JSON, was zu Kompatibilitätseinschränkungen beim Tooling führen kann.

Housekeeping und DSGVO-Anforderungen

Feeds enthalten in der Regel historische Daten. Das wirft wichtige Fragen auf: Wie lange sind die Daten vorzuhalten, und wie sind sie zu entfernen, wenn beispielsweise eine Nutzerin oder ein Nutzer sein Recht auf Löschung gemäß der DSGVO geltend macht?

Die minimale Vorhaltedauer fachlicher Events ist zunächst eine fachliche Entscheidung. Grundsätzlich ist es aber ratsam, den Feed kompakt zu halten, damit neue Consumer ihn auch zügig von Anfang bis Ende lesen können. Bei Event-Feeds ist beispielsweise eine Vorhaltedauer von vier Wochen denkbar und sinnvoll; ältere Einträge werden gelöscht.

Bei Data-Feeds, die alle Entitäten enthalten müssen, empfiehlt sich die Verwendung eines fachlichen Schlüssels, der die fachliche Entität referenziert. Das kann beispielsweise eine Kundennummer, eine Bestellnummer oder auch eine entsprechende URI sein. Beim Implementieren des Feeds lassen sich dann alle bisherigen Einträge mit dem gleichen fachlichen Schlüssel entfernen, sobald ein neuer Eintrag mit dem vollständigen aktuellen Zustand dieser Entität hinzukommt. Auf diese Weise sind im Feed langfristig alle Entitäten nur einmal enthalten, und zwar immer auf dem neuesten Stand. Dieser Vorgang nennt sich Compaction.

Durch einen Compaction-Lauf lassen sich ältere Einträge mit dem gleichen fachlichen Schlüssel aus dem Feed entfernen (Abb. 7)
Durch einen Compaction-Lauf lassen sich ältere Einträge mit dem gleichen fachlichen Schlüssel aus dem Feed entfernen (Abb. 7)

Der fachliche Schlüssel kann auch für DELETE-Events zum Einsatz kommen. Dazu fügt man einen leeren Feed-Eintrag mit dem gleichen fachlichen Schlüssel und einer DELETE-Kennzeichnung hinzu. Nach einem Compaction-Lauf sind dann alle vorherigen Einträge entfernt. Consumer des Feeds können auf diese DELETE-Events ebenfalls mit einer Löschaktion in ihren Datenbeständen reagieren.

Fazit

HTTP-Feeds sind eine leichtgewichtige Alternative zu Message Brokern

Als Alternative zu Message-Broker-Systemen eignen sich HTTP-Feeds für den Aufbau asynchroner, Event-getriebener Architekturen und Integrationsmuster, da sie sowohl die Datenreplikation zwischen Systemen als auch nachgelagertes Auslösen von Aktionen durch die Verarbeitung von Domain-Events ermöglichen. Vor allem, wenn team- oder systemübergreifende Schnittstellen notwendig sind, haben reine HTTP-Schnittstellen genauso wie REST-APIs den Vorteil, dass sie ohne ein Middleware-System auskommen und dadurch auch organisatorische und technologische Abhängigkeiten entfallen.