API-ARCHITEKTUR
Offene Schnittstellen durch API-Architektur gestalten
Richtiger Schnitt, Wiederverwendbarkeit und Lifecycle durch API-Architektur
Im Umfeld von offenen Schnittstellen muss die API-Architektur und dabei insbesondere nicht-funktionale Anforderungen betrachtet werden. Offene Schnittstellen sind nicht nur eine Frage der Datenstruktur, der abgebildeten Funktion, der Technologie oder Tools. Hier einige Beispiele möglicherweise konkurrierender Anforderungen, deren Priorität abzuwägen ist:
- In welcher Zeit benötige ich spätestens eine Antwort?
- Wie hoch wird die Aufrufrate sein?
- Muss die Datenkonsistenz im Zusammenhang mit den ersten beiden Punkten immer zu 100% gewährleistet sein?
- Wer sind die Konsumenten und wird die API auch durch Externe außerhalb des Unternehmens aufgerufen und sind hierdurch SLAs zu beachten?
- Welche fachliche oder technische Motivation (Zweck) steckt dahinter?
Nachfolgend gehen wir näher auf eine Auswahl von Aspekten der API-Architektur ein, die insbesondere bei der Realisierung von offenen Schnittstellen berücksichtigt werden sollte.
Synchrone oder asynchrone Kommunikation
Die Frage der API-Architektur nach der Synchronität einer Schnittstelle lässt sich nur beantworten, wenn messbare Anforderungen für maximale Antwortzeit im Zusammenhang mit Aufrufrate, Datenmenge und Datenkonsistenz definiert sind. Wenn Statistiken zum Verhalten von Frontend-Benutzern und angefallene Daten der letzten Jahre existieren, so lassen sich davon Anforderungen ableiten.
Sobald die Anforderungen an die Schnittstelle geklärt sind, erfolgt die Analyse der Backendsysteme bezüglich Unterstützung der geforderten Leistung und technischer Machbarkeit.
Wenn das angefragte System nicht alle Anforderungen erfüllt, ist das Szenario insbesondere aus fachlicher Sicht auf Alternativen zu untersuchen.
Der Einsatz einer synchronen oder asynchronen Kommunikation wird im Einzelfall anhand der benötigten Eigenschaften der Schnittstelle entschieden, denn eine hohe Anzahl paralleler synchroner Anfragen kann zu Blockaden/Deadlocks führen. Aus Geschäftsprozess- bzw. Benutzersicht sind blockierende Situationen möglichst auszuschließen. Sie können aber bei synchronem Verhalten mit hoher paralleler Aufrufrate nicht vermieden werden.
Lässt sich ein temporärer Zugriffskonflikt für den Benutzer durch einfache Mechanismen wie sprechende Fehlermeldung und Wiederholung der Anfrage vermeiden?
Hinzu kommt: hohe Anfragerate und Datenkonsistenz konkurrieren bei verteilten Systemen vergleichbar zu den bei Datenbanken bekannten CAP Theorem. In Folge dessen können nur zwei von drei Anforderungen (Konsistenz, Verfügbarkeit und Partitionstoleranz – bei Cloud-Anwendungen vorausgesetzt) zu 100% erfüllt werden. Ist in diesem Zusammenhang eine „schlussendliche“ Konsistenz mit kleinstmöglichem Zeitverzug fachlich vertretbar?
Oft können angefragte Systeme aufgrund ihrer Batch- oder Prozessverarbeitung nicht in der Zeit antworten, die ein Frontenduser beim Klick eines Buttons wartet. Alternativen sind hier eine asynchrone Kommunikation in Form eines Websockets oder ein erneuter Aufruf zu einem späteren Zeitpunkt zur Abfrage des Prozessstatus.
In allen Fällen ist bei asynchronen Aufrufen ein Monitoring notwendig, das auf „Langläufer“ bzw. Abbrüche hinweist, um Blockaden im Geschäftsprozess zu vermeiden und Durchlaufzeiten zu analysieren.
Insgesamt ist ein asynchroner Architekturstil vorzuziehen, da damit parallele Aufrufe zu keinem blockierenden Verhalten führen. Dies ist aber mit höherer Komplexität und Aufwand verbunden.
Wiederverwendbarkeit von Schnittstellen
Bei der Frage, ob eine Schnittstelle wiederverwendbar konzipiert wird, stehen oft Projektinteressen gegen Unternehmensinteressen. Das Projekt benötigt eine Schnittstelle, die genau auf den Kontext zugeschnitten ist. Für eine Analyse, ob es potentiell weitere Nutzer für eine Schnittstelle gibt, besteht in der Regel keine Zeit. Für das Unternehmen ist es aber sinnvoll, Schnittstellen, wo möglich, wiederzuverwenden, weil das sowohl in der Entwicklung als auch im Betrieb Kosten spart.
Anforderungen an Schnittstellen ergeben sich aus jedem individuellen Kontext unterschiedlicher Aufrufe. Beim Begriff des „Kunden“ beispielsweise können aus Sicht des Aufrufers A die Kontaktdaten eines Kunden erforderlich sein. Beim Aufrufer B ist aber die Lieferadresse erforderlich. In beiden Kontexten sind unterschiedliche Anforderungen an die hinter der Schnittstelle umzusetzende Logik erforderlich.
Sind die Anforderungen beider Anwender zeitgleich bekannt, kann die Wiederverwendung oder die separate Umsetzung der Schnittstelle abgewogen werden. Sind die Anforderungen weiterer Konsumenten der Schnittstelle noch nicht bekannt, kann nicht automatisch von einer Wiederverwendung ausgegangen werden.
Eine Hürde für die Wiederverwendbarkeit von Schnittstellen liegt auch in der Begrifflichkeit unterschiedlicher Domänen (z.B. zweier Abteilungen). Trotzdem könnten fachliche Gemeinsamkeiten genutzt werden, beispielsweise durch Nutzung in einer Branche normierter Datenstrukturen.
Ein weiterer Aspekt, der für die Wiederverwendbarkeit spricht, ist die Reduzierung der Komplexität und Vermeidung des Aufblähens vom API Repository. Redundanzen führen meist zu immensen Folgeaufwänden, die im späteren Verlauf wieder schwer abzubauen sind. Aus diesem Grund sollte eine API-Strategie zur Unternehmensstrategie und umgekehrt passen. Eine „API-isierung“ sollte nie aus technischen Gründen allein erfolgen, sondern der Geschäftsstrategie und der daraus resultierenden Motivation folgen.
Dokumentation von Schnittstellen
Damit die gewählte API-Architektur für viele Konsumentengruppen zugänglich ist und auch betreibbar bleibt, muss die Dokumentation einer Schnittstelle für verschiedene Blickwinkel verständlich, inhaltlich vollständig und zugreifbar sein. Der notwendige Aufwand zahlt sich in reduzierten Zeiten aller Beteiligten im Test, im Fehlerfall oder bei Änderungsbedarf aus.
Ein Entwickler versteht die Dokumentation einer Schnittstelle oft als eher technische Dokumentation der einzelnen Methoden, ihrer Aufrufpfade und Datenstrukturen. Diese ist für ihn selbsterklärend, aufwandsschonend und basiert auf Standards wie OpenAPI (vormalig: Swagger) für REST Schnittstellen die von den meisten Tools und Entwicklungsframeworks unterstützt werden. Auch eine Spezification by Example ist ein effektiver Ansatz, um ein gemeinsames Verständnis über APIs herzustellen, hierzu siehe: Consumer Driven Contract Testing.
Schnittstellen richtig schneiden - definiert die Qualität der API-Architektur
Beim Schneiden von Webservice-Schnittstellen gilt es, individuelle Faktoren für eine gute API-Architektur abzuwägen.
Grundsätzlich ist der Funktionsumfang zu beschränken und von anderen Services inhaltlich abzugrenzen, um den Service von einem einzelnen Team unabhängig entwickeln und ausliefern lassen zu können.
Ein weiterer Vorteil für die Größenbeschränkung liegt in der technischen Skalierbarkeit, um die Performance einzelner Funktionsblöcke an den Bedarf anzupassen (Beispiel Produktsuche vs. Produktverkauf).
Meist ergeben sich individuelle Anforderungen wie Datenaggregation oder Antwortzeitverhalten aus dem Datenbedarf eines Geschäftsprozessschrittes oder der Anwendungsmasken. Sind Netzwerkinfrastruktur oder Middleware Zeitfresser, kann ein gröberer Schnittstellenschnitt hilfreich sein. Man sollte aber im Blick behalten, dass ein grober Serviceschnitt die Wahrscheinlichkeit der Wiederverwendbarkeit aller Attribute in einem anderen Kontext verringert.
In allen Fällen zahlt es sich aus, wenn für das Unternehmen ein einheitliches und verbindliches Regelwerk zur API-Entwicklung existiert, an dem sich alle Beteiligten ausrichten können oder sogar müssen. Dies betrifft zum einen die technischen aber auch die fachlichen Aspekte, um den passenden Zuschnitt zu finden. Ein „one-fits-all“ Ansatz kann und wird es aber nicht geben.
Funktionale versus qualitative (nicht-funktionale) Anforderungen
Für das Business stehen die fachlichen Anforderungen normalerweise im Vordergrund. Kleine Budgets und hoher Wettbewerbsdruck führen dazu, dass man technische Aspekte vernachlässigt. Das Team, das eine Schnittstelle betreiben soll, erwartet aber eine robuste und stabile Anwendung, die Instrumente mitbringt, eine schnelle Fehleranalyse und -behebung durchführen zu können.
Projekte müssen für den Erfolg der API-Architektur bereits in der Planung funktionale und nicht-funktionale Anforderungen berücksichtigen und budgetieren.
In einer cloud-basierten Anwendung kommunizieren viele kleine Module miteinander. Es gilt, Fehlersituationen bereits im Design zu berücksichtigen, um blockierendes Verhalten, Gesamtausfall und Instabilität zu vermeiden. Daher ist es sehr zu empfehlen, dass alle Module und ihre Kommunikation untereinander analysierbar sind und in Echtzeit auf Fehler und Einhaltung definierter SLAs überwachbar sind.
Nicht-funktionale Anforderungen wie Security, Logging, Tracing, Monitoring, Service-Kataloge, Routing müssen nicht zwingend in den Services ausprogrammiert werden. Service-Mesh (Service-Netz) Tools wie Istio trennen diese Aspekte, indem Services in Containern (Kubernetes) eingespielt und die genannten nicht-funktionalen Aspekte zur Laufzeit in der Infrastruktur konfigurativ aktiviert werden.
Für nicht-funktionale Anforderungen sind aus fachlicher und technischer Sicht testbare Szenarien mit messbaren Ergebnissen zu erstellen und möglichst in jedem Release-Test zu überprüfen.
Sicherheit und Robustheit - aufgeteilt in der API-Architektur
API-Architektur erfordert Schnittstellen/Webservices für diverse Sicherheitsaspekte abzusichern:
- Authentizität des Aufrufers,
- Autorisierung des Aufrufers,
- Integrität (Unveränderbarkeit der Daten bei der Übermittlung),
- inhaltliche Zugriffsbeschränkung auf Datenelemente,
- DSGVO,
- …
Eine Sicherheitslücke mit entsprechender Eintrittswahrscheinlichkeit ist meist ein finanzielles Risiko in erheblichem Umfang bis zur Existenzbedrohung. Im Einzelfall muss natürlich die Kritikalität und Wahrscheinlichkeit eines möglichen Missbrauchs berücksichtigt werden.
Lebenszyklus und Versionierung - muss die API-Architektur definieren
Bei API-Architektur unterliegt jede Schnittstelle einem Lebenszyklus. Neue Anforderungen bringen Updates mit sich. Hierfür ist eine Strategie notwendig, um die erforderliche Weiterentwicklung mit der Stabilität, die die Schnittstellenpartner zwingend brauchen, in Einklang zu bringen. Es kann dazu erforderlich sein, mehrere unterschiedliche Versionen einer API gleichzeitig anzubieten. Um zu verhindern, dass die Zahl der alten Versionen über Gebühr ansteigt und als Folge sich die Wartung verteuert, gehört auch die Dekommissionierung alter Versionen zum API-Lifecycle-Management.