AsyncAPI ganz einfach in 15 Minuten

19.06.2024

Was ist AsyncAPI

AsyncAPI ist eine Spezifikation zur Definition und Dokumentation von asynchronen APIs, wie sie oft in ereignisgetriebenen System eingesetzt werden. Die Spezifikation ist dabei ähnlich wie OpenAPI (Swagger) aufgebaut, das momentan der De-Facto-Standard für synchrone HTTP-APIs ist.

Architekturstiele wie Microservices und Event-gesteuerte Architekturen werden immer häufiger eingesetzt und dabei wird oft auf asynchrone Kommunikation gesetzt. AsyncAPI erfüllt dabei eine wichtige Rolle, indem es diese Kommunikation formal spezifiziert und einen Vertrag definiert.

Asynchrone Kommunikation

Bei asynchroner Kommunikation findet das Senden und Empfangen der Daten zeitversetzt (asynchron) und ohne Blockieren statt. Das bedeutet, dass eine asynchrone Nachricht keine unmittelbare Antwort erwartet. Im Gegensatz dazu wird bei der synchronen Kommunikation eine Antwort erwartet und solange blockiert, bis diese antrifft.

Asynchrone Kommunikation ist gut geeignete um eine lose Koppelung zweier Komponenten zu erreichen. Messaging Systeme wie RabitMQ oder Kafka bauen auf diesen Kommunikationsstiel auf und können Komponenten zeitlich, örtlich und technologisch entkoppeln.

In meine Artikel zu Mustern für ereignisgetriebene Architekturen gehe auf asynchrone Kommunikation und Ereignisse näher ein.

Erste Schritte mit AsyncAPI

AsyncAPI stellt viele Tools zu Verfügung. Insbesondere das AsyncAPI Studio ist ein toller Einstiegspunkt für Neulinge. Wenn man das Studio öffnet, findet man ein Spezifikationsbeispiel, das zum Ausprobieren und Experimentieren einlädt. Man kann direkt aus dem Studio die interaktive Dokumentation ansehen, sowie Code und Dokumentation erzeugen.

AsyncAPI Studio
AsyncAPI Studio mit Standardvorlage

Für das produktive Arbeiten ist jedoch die CLI die besser Wahl. Die CLI kann sowohl als NPM-Paket als auch als Binary heruntergeladen und verwendet werden. Die Installation mittels NPM erfolgt mit:

npm install -g @asyncapi/cli

Binaries können sowohl für Windows (z.B. 64bit) als auch für Linux (z.B. DEB) heruntergeladen werden. Weitere Downloadvarianten findet man auf der CLI-Webpage. Nach der Installation kann man mittels CLI eine neue Spezifikation erzeugen:

asyncapi new

Nach dem Aufruf wird eine Beispielspezifikation erzeugt, die wie folgt aussieht:

asyncapi: 3.0.0
info:
  title: Account Service
  version: 1.0.0
  description: This service is in charge of processing user signups
channels:
  userSignedUp:
    address: user/signedup
    messages:
      UserSignedUp:
        $ref: '#/components/messages/UserSignedUp'
operations:
  onUserSignUp:
    action: receive
    channel:
      $ref: '#/channels/userSignedUp'
    messages:
      - $ref: '#/channels/userSignedUp/messages/UserSignedUp'
components:
  messages:
    UserSignedUp:
      payload:
        type: object
        properties:
          displayName:
            type: string
            description: Name of the user
          email:
            type: string
            format: email
            description: Email of the user

Im nächsten Abschnitt sehen wir uns diese Spezifikation genauer an, und besprechen die verschiedenen Abschnitte.

Die Spezifikation

In der ersten Zeile findet sich die AsyncAPI-Version wieder. Die aktuelle Version vom Dezember 2023 ist 3.0.0, welche aber noch nicht von allen Generatoren unterstützt wirtd. Die letzte Version davor war 2.6.0. Übrigens kann man im Studio sehr komfortabel alte Version auf 3.0.0 konvertieren lassen.

Zeile 2 bis 5 geben die Metainformationen der API wieder. Hier findet sich der Titel, eine menschenlesbare Beschreibung sowie die Version die API (nicht mit der AsyncAPI-Version verwechseln!). In der Metabeschreibung kann man auch Felder für die Lizenz, Terms of Service und mehr hinzufügen.

Im nächsten Abschnitt von Zeile 6 bis 11 folgt die Channel-Spezifikation. Ein Channel ist ein spezifischer Kommunikationskanal der vom Server (=Message Broker System) zur Organisation der Nachrichten verwendet wird. Abhängig von tatsächlich verwendeten Protokoll kann ein Channel ein Topic, ein Subject, eine Queue oder ähnliches sein. Wie das Schlüsselwort channels vermuten lässt, kann man mehrere Kanäle in der Spezifikation definieren.

In unserem Fall gibt es nur einen Channel mit dem Namen userSignedUp und der Adresse user/signedup. Der Name hat keine direkt Bedeutung für den Kommunikationskanal sondern wird nur zur Identifizierung und Referenzierung innerhalb der Spezifikation verwendet. Die Adresse hingegen, entspricht der logischen Adresse am Server (z.B. Topic-Adresse).

Ich hab nun schon ein paar mal den Server erwähnt. Mit Server ist hier immer ein Message Broker gemeint, zu dem sich Clients (Empfänger und Produzenten von Nachrichten) verbinden können. In dieser Beispielspezifikation finden wir kein Server-Objekt. Wir können aber eines Hinzufügen um die Eigenschaften des Message Brokers zu spezifizieren:

servers:
  dev:
    host: localhost:5672
    pathname: /v1
    protocol: amqp
    protocolVersion: "1.0"
    description: RabbitMQ broker for development
    bindings:
      amqp:
        exchange: my-exchange
        queue: my-queue

Hier wird ein RabittMQ Message Broker beschrieben, der über AMQP 1.0 kommuniziert und am Localhost erreichbar ist. Laut Beschreibung wird dieser Server für die lokale Entwicklung verwendet. AsyncAPI erlaubt es mehrere Server zu definieren. Dies kann hilfreich sein, um mehrere Umgebungen (Entwicklung, Testing, Produktion) oder verschiedene Protokolle zu unterstützen.

Zurück in unserer ursprünglichen Spezifikation, sehen wir in Zeile 9 bis 11 die Beschreibung einer Nachricht, welche über diesen Channel geschickt wird. Man könnte die Nachricht hier direkt beschreiben, jedoch wird in der Vorlage über $ref ein Reference Object verwendet, um auf einen anderen Teil der Spezifikation zu verweisen. Einige Objekte müssen in de Spezifikation öfters beschrieben werden. Durch die Referenzierung kann man sich dies ersparen und somit die Datei schlanke halten und Probleme durch Tippfehler vorbeugen.

Im Reference Object #/components/messages/UserSignedUp wird zuerst nach einem Abschnitt components gesucht und dann weiter nach messages und UserSignedUp. Diesen Abschnitt finden wir in unsere Spec ab Zeile 21.

Referenzen können nicht nur auf Objekte innerhalb derselben Datei gesetzt werden, sondern auch auf andere Dateien. Ebenso ist es möglich eine URL in einem Reference Object anzugeben.

In Zeile 12 bis 18 werden operations beschrieben. Hier kann beschrieben werden, welche Operationen unterstütz werden, wie beispielsweise das Senden oder Empfangen bestimmter Nachrichten. Wir unterstützen eine Operation mit Name onUserSignUp. Diese beschreibt das Empfangen einer Nachricht (action: receive) vom Channel #/channels/userSignedUp.

Das Nachrichtenformat wird hier nicht direkt über #/components/message/UserSignedUp adressiert, sondern indirekt über den Channel. Das hat den Vorteil, dass man bei einer Änderung der Nachricht nur die Anpassung in Zeile 11 und nicht auch noch in Zeile 18 machen muss.

Der letzte Abschnitt sind die components (Zeile 19 ff). Hier können wiederverwendbare Strukturen, wie unsere Nachricht, angegeben werden.

Eine vollständige Beschreibung aller Konzepte und Felder der AsyncAPI-Spezifikation findet man in der offiziellen Dokumentation.

Typische Anwendungsfälle

Ereignisgetriebene Architekturen

Ereignisgetriebene Architekturen sind ein zentraler Anwendungsfall für AsyncAPI. In solchen Architekturen arbeitet das Systeme mit Ereignisse, die durch verschiedene Komponenten ausgelöst und durch andere Verarbeitet werden. Diese Ereignisse können alles sein, von Benutzeraktionen bis hin zu Systemereignissen wie Datenänderungen. AsyncAPI hilft dabei, diese komplexen Interaktionen zu standardisieren und zu dokumentieren. Durch die Verwendung von AsyncAPI können Entwickler sicherstellen, dass alle beteiligten Komponenten und Services ein gemeinsames Verständnis davon haben, wie Ereignisse strukturiert und verarbeitet werden. Dies erleichtert nicht nur die Entwicklung, sondern auch die Wartung und Skalierung von ereignisgesteuerten Systemen erheblich.

Komponenten einer Ereignisgetriebenen Architektur
Komponenten einer Ereignisgetriebenen Architektur

Microservices

In Microservice-Architekturen spielen asynchrone Kommunikationsmuster oft eine wichtige Rolle bei der Kommunikation zwischen den Microservices. Microservices sind unabhängige, kleine Dienste, die zusammen eine größere Anwendung bilden. Um eine starke Koppelung zwischen den Dienste ui vermeiden, können asynchrone Nachrichten verwendet werden. AsyncAPI bietet eine standardisierte Möglichkeit, diese Nachrichten und ihre Formate zu definieren. Durch die klare Spezifikation der Kommunikationswege und Nachrichtenformate können wir sicherstellen, dass alle Microservices reibungslos miteinander interagieren. Dies reduziert das Risiko von Missverständnissen und Fehlern bei der Kommunikation zwischen den Diensten und trägt zu einer robusteren und wartbaren Systemarchitektur bei.

IoT

Das Internet of Things (IoT) umfasst eine Vielzahl von Geräten, die über Netzwerke miteinander verbunden sind und Daten austauschen. Diese Geräte kommunizieren häufig asynchron, indem sie Ereignisse senden und empfangen, um auf Umgebungsänderungen zu reagieren oder Steuerbefehle zu erhalten. AsyncAPI kann hier genutzt werden, um die Kommunikation zwischen verschiedenen IoT-Geräten und -Plattformen zu standardisieren. Besonders in heterogenen IoT-Umgebungen, in denen Geräte verschiedener Hersteller zusammenarbeiten müssen, ist eine einheitliche und interoperable Kommunikation wichtig.

Streaming

Streaming-Anwendungen, bei denen kontinuierlich Daten in Echtzeit übertragen werden, sind ein weiteres wichtiges Anwendungsgebiet für AsyncAPI. Typische Beispiele sind Video-Streaming-Dienste, Echtzeit-Datenanalysen und Finanzmarkt-Updates. In solchen Anwendungen ist es entscheidend, dass Daten schnell und zuverlässig von einer Quelle zu vielen Empfängern übertragen werden können. AsyncAPI ermöglicht die präzise Definition der Kommunikationswege und Nachrichtenformate in Streaming-Architekturen. Dies sorgt dafür, dass alle Beteiligten – von Datenproduzenten über Broker bis hin zu Konsumenten – genau wissen, wie die Daten strukturiert sind und wie sie verarbeitet werden müssen. Dadurch wird die Implementierung von Streaming-Anwendungen vereinfacht und deren Zuverlässigkeit erhöht.

Event Streaming
Event Streaming

Vergleich von AsyncAPI und OpenAPI

Wie unterscheidet sich AsyncAPI von OpenAPI? Die kurze Antwort: AsyncAPI ist für asynchrone Kommunikation, OpenAPI für synchrone.

Die etwas längere Antwort: OpenAPI wurde ursprünglich unter den Namen Swagger Specifiatoin von SmartBear veröffentlich. Im Jänner 2016 fand die Trennung der Tools (Swagger) und der Spezifikation (OpenAPI), sowie die Umbenennung statt.

OpenAPI ist in erster Linie für HTTP-APIs gedacht. Oft wird damit REST assoziiert, jedoch muss nicht jede HTTP-basierte API auch REST folgen. Einer der wesentlichen Erfolgstreiber von OpenAPI war im. Er schon das Tooling, mit dem es sehr einfach ist Code, Dokumentation und noch mehr zu erzeugen. Obwohl hier AsyncAPI natürlich auch einen Fokus setzt, so hat OpenAPI aktuelle eine breitere Unterstützung von Tools.

In der nachfolgender Tabelle sind die wichtigsten Merkmale der beiden Spezifikationsstandards gegenüber gestellt.

MerkmalOpenAPIAsyncAPI
ZweckDefiniert synchrone HTTP-APIs (REST-like)Definiert asynchrone APIs (Events, Messaging)
ProtokollHTTP bzw.nHTTPSVerschiedene Messaging-Protokolle, wie MQTT, AMQP, Kafka, WebSockets und mehr
SchwerpunktRequest-Response-ModellPublish-Subscribe-Modell
HauptkomponentenPfade, Operationen (GET, POST, etc.), Anfrageparameter, AntwortenChannels, Nachrichten, Server, Operations (Publish, Subscribe)
SchemadefinitionJSON SchemaJSON Schema
DokumentationSwagger UI, RedocAsyncAPI React, HTML-Vorlage
Tool-UnterstützungGroße Anzahl ToolsWachsende Anzahl an Tools
Erstveröffentlichung20112016
BeispieleInline innerhalb der SpezifikationOft in einem separaten examples Abschnitt
SicherheitOAuth2, API-Schlüssel, Basic Auth, etc.Ähnliche wie OpenAPI, zusätzlich protokollspezifische Sicherheitskonfigurationen
Vergleich zwischen OpenAPI und AsyncAPI

Es sspricht nichts dagegen beide Standards in einem System zu kombinieren, um alle APIs (egal ob synchron oder asynchron) abzudecken.

Einen ausführlichen Vergleich findet man im Artikel AsyncAPI vs OpenAPI: Answers to Your Burning Questions About Two Leading API Specs von Jesse Menning (Solace).

Best Practices

Wiederverwendung

Um Schreibarbeiten zu minimieren, Tippfehler vorzubeugen und die Spezifikationsdateien klein zu halten, empfiehlt es sich Wiederverwendung durch den Einsatz von Reference Object ($ref) umzusetzen. Eine Referenz kann dabei auf ein Objekt innerhalb der selben Datei gesetzt werden, auf eine andere Datei zeigen oder eine URL referenzieren.

Referenz zuSyntax
Selbe Datei$ref: '#/components/schemas/messageSchema'
Andere Datei$ref: './file.yaml#/messageSchema'
URL$ref: http://foo.bar/file.yaml#/messageSchema'

Beim Referenzieren von URL sollte man jedoch aufpassen, dass man die dauerhafte Existenz der URL garantieren kann. Es empfiehlt sich nicht, den erstbeste URL aus einer Googelsiche zu referenzieren, wenn diese in einer Woche vielleicht nicht mehr erreichbar ist.

Lokalität

Die Produzenten und Empfänger von Nachrichten sollten über eine eigene Spezifikationsdatei verfügen, die auch im Code Repository im jeweiligen Projekt abgelegt ist.

Über Reference Objects kann man Nachrichten zwischen verschiedenen Spec Files teilen. Dabei wird meist ein Shared File mit gemeinsamen Definitionen erstellt. Aber Vorsicht, dadurch entsteht eine Koppelung auf dieses geteilte File! Hier muss ein tragfähiger Kompromiss zwischen Koppelung und DRY abgewogen werden.

Es ist auch denkbar, von Sender auf Empfänger oder vice versa zu referenzieren. Im Zweifelsfall empfehle ich die Abhängigkeit vom Empfänger auf den Sender zu legen. So ist das Dependency Inversion Principle (das durch die ereignisbasierte Kommunikation etabliert wird) nicht verletzt.

Größe

Spezifikationsdateien können schnell unübersichtlich werden. Deshalb sollte man die Größe und Komplexität der Dateien stets im Auge behalten.

Dabei hilft es eine Spezifikation pro Schnittstelle statt eine Spezifikation für die gesamte Architektur zu verwenden. Kombiniert man dies mit der Best Practice Lokalität, so erhält man gut handhabbare Spezifikationen.

Einheitlichkeit

Zu guter Letzt sollte man bei den Spezifikationsdateien auf einheitliche Kodierrichtlinien achten. Dadurch wird die Lesbarkeit und Wartbarkeit der Spezifikationen erhöht.

Definieren Sie daher einen Style Guides für Ihr Team, der eine Einheitlichkeit gewährleistet. Dieser Style Guide sollte auch Namenskonventionen für Dateien, Channels, Servers, Operations und weitere Objekte beinhalten. es ist ebenso sinnvoll, eine Konvention für die Struktur innerhalb der Spezifikationsdatei zu erstellen, so dass Inhalte leichter wiedergefunden werden können.

Es kann ebenso sinnvoll sein Regel für die Wiederverwendung von Objekte zu bestimmen. Diese Regeln können beispielsweise beinhalten wann Wiederverwendung eingesetzt werden soll, welche Objekte wiederverwendet werden und wie die Wiederverwendung umgesetzt werden soll.

Die Zukunft von AsyncAPI

Für die Zukunft hat sich das Team von AsyncAPI große Ziele in ihrer Vision gesetzt: AsyncAPI soll die Nummer 1 API Spezifikation werden. Und diese Version soll bis Ende 2026 Wirklichkeit werden.

„Nummer 1 API Spezifikation“ bedeutet, dass existierende Spezifikationen wie OpenAPI, GraphQL oder gRPC integriert und unterstützt werden.

AsyncAPI unifies all the API specifications

Nobody does only event-driven architectures. Most people complement them with REST (OpenAPI), GraphQL, and/or RPC APIs. We want people to use the AsyncAPI specification and tooling together with their existing OpenAPI, GraphQL, and gRPC definitions. For that purpose, our specification and tools will need to understand and leverage many other specifications and tools.

This is not about reinventing the wheel or creating yet another spec to solve the same problems but to integrate with the existing tools and specs instead.

https://asyncapi.com/roadmap

Dieses ambitioniertes erinnert mich immer an folgenden xkcd-Comic:

xkcd Standards
Credits: xkcd

Es wäre wünschenswert, wenn AsyncAPI der neue Standard wird und andere Standards überflüssig werden. Ob es gelingt, werden wir wohl erst Ende 2026 wissen. 😉

Neben dieser ambitionierten Vision ist aktuell viel Bewegung in der Community. Die Popularität steigt stetig und in letzter Zeit sind viele neue Generatoren hinzugekommen. Gerade auch als Community Contribution.

Der Ausblick auf die Zukunft ist also relativ rosig und hoffnungsvoll!

Fazit

Verwenden Sie asynchrone Kommunikation und haben AsyncAPI noch nicht im Einsatz? Dann wird es höchste Zeit Ihre Schnittstellen standardisiert zu beschreiben! Ich helfe Ihnen gerne, diese Projekt umzusetzen und Ihre Architektur zu verbessern.

Raphael Dumhart

Raphael Dumhart ist Berater für Softwareentwicklung und Softwarearchitektur. Neben dem Software Engineering hat er einen Großteil seiner beruflichen Laufbahn mit dem Aufbau von Entwicklungsteams, Unternehmern und Produkten verbracht. Unter anderem hat er 10 Jahre lang die Entwicklungsabteilung eines internationalen Konzerns geführt, ein IT-Startup mitgegründet sowie ein EduTech-Startup gegründet.

Sie wollen mehr zu diesen Thema erfahren?

Lassen Sie uns Wissen in die Tat umsetzen und kontaktieren Sie mich noch heute!

Kontaktieren

Die neusten Blogposts