Umsetzung von REST-Services
1. Technologieauswahl
Die IsyFact legt folgende Frameworks für die Umsetzung von REST-Services fest.
| Framework | Technologie-Stack | Beschreibung |
|---|---|---|
Spring Webflux |
Java/Spring |
Umsetzung von REST-Clients (unter Nutzung von Apache HTTPComponents) |
Spring MVC |
Java/Spring |
Umsetzung von REST-Services (zur Nutzung mit Tomcat) |
Angular Framework Modul: @angular/common/http |
TypeScript/Angular |
Umsetzung von REST-Clients |
OpenAPI |
technologie-unabhängig |
Dokumentation der Service-Schnittstelle und Erzeugung von Service und Client |
2. Spring MVC im Vergleich zu Spring Webflux
Das Spring Framework stellt seit Version 5 Spring Webflux als reaktive Alternative zu Spring MVC bereit.
Mit Webflux ist es möglich, reaktive REST APIs zu implementieren. Beim reaktiven Programmierparadigma geht es um die Reaktion auf Veränderungen (Observer Pattern), im Fall von REST APIs um die Reaktion auf gesendete Daten. Das ist hilfreich, wenn man große Datenmengen (z. B. Mediendateien oder größere Ergebnismengen aus einer Datenbankabfrage) über das REST API übertragen möchte. Die Daten werden dann in mehreren Paketen gesendet, der Empfänger reagiert jeweils auf den Erhalt eines Pakets.
Der Hauptvorteil von Webflux liegt aber im geringeren Ressourcenverbrauch auf der Server Seite. Webflux basiert auf einem Event-Loop Mechanismus, während Spring MVC auf Thread Pools basiert. Der Performance Vorteil von Webflux macht sich insbesondere bei vielen gleichzeiten Service-Aufrufen und/oder der Übertragung größerer Datenmengen bei einem Service-Aufruf bemerkbar.
Trotzdem empfiehlt die IsyFact grundsätzlich, Spring MVC für die Umsetzung von REST-Services einzusetzen. Die Vorteile des reaktiven Programmierparadigmas erfüllen sich nur, wenn alle Teile der Umsetzung entsprechend gestaltet sind. Dies ist aktuell nicht der Fall.
Für REST-Clients wiederum empfiehlt die IsyFact die Verwendung des WebClient anstatt des RestTemplate, da letzteres nicht mehr aktiv weiterentwickelt wird.
Beim gleichzeitigen Einsatz von isy-logging, isy-security und isy-task sind die bekannten Einschränkungen reaktiver Verarbeitung zu beachten.
Für die Anbindung von Angular-Clients enthält der Baustein Angular eine entsprechende Komponente.
3. Einschränkungen reaktiver Verarbeitung mit IsyFact-Querschnittsbausteinen
Der WebClient basiert auf Project Reactor und verarbeitet HTTP-Anfragen in einem reaktiven, nicht-blockierenden Ausführungsmodell.
Innerhalb einer reaktiven Pipeline kann Project Reactor zwischen Threads wechseln:
Ein Request wird auf einem Thread begonnen und nach einer asynchronen Operation auf einem anderen Thread fortgesetzt.
Die IsyFact setzt grundsätzlich auf dem imperativen Programmiermodell auf und nutzt`ThreadLocal`-Mechanismen zur Propagierung von Kontext-Informationen.
Diese Mechanismen funktionieren im reaktiven Modell nicht zuverlässig, da ThreadLocal-Werte beim Thread-Wechsel auf dem neuen Thread nicht zur Verfügung stehen.
|
Reaktive Verarbeitung wird durch die IsyFact aktuell nicht vollständig unterstützt.
Fachverfahren, die den |
3.1. Einschränkungen bei isy-logging
Der Baustein isy-logging basiert auf dem ThreadLocal-basierten Mapped Diagnostic Context (MDC) von Logback.
Der MDC speichert Kontextinformationen wie die Korrelations-ID und die System-ID individuell pro Thread.
Beim Einsatz des WebClient wechselt Project Reactor bei asynchronen Operationen den ausführenden Thread.
Die MDC-Map des neuen Threads ist leer oder enthält Werte eines anderen Requests, der zufällig auf demselben Thread verarbeitet wird.
Dies hat Konsequenzen:
-
Die Korrelations-ID und alle weiteren MDC-Felder gehen bei Thread-Wechseln verloren oder enthalten falsche Werte.
-
Dieser Datenverlust tritt ohne Fehler oder Exception auf und ist im Logging nicht erkennbar.
-
Die Logging-Nachvollziehbarkeit über Komponentengrenzen hinweg ist also nicht gewährleistet.
Darüber hinaus basiert der LogInterceptor des Bausteins isy-logging auf Spring AOP.
Spring-AOP-Aspekte sind an den aufrufenden Thread gebunden und können reaktive Pipelines daher nur eingeschränkt instrumentieren.
3.2. Einschränkungen bei isy-security
Der Baustein isy-security nutzt den SecurityContextHolder von Spring Security.
Dieser speichert den SecurityContext in einem ThreadLocal.
In diesem sind Authentifizierungsinformationen, zugewiesene Rollen und Behördenkennzeichen enthalten.
Bei reaktiver Verarbeitung ist der SecurityContext nach einem Thread-Wechsel auf dem neuen Thread nicht mehr verfügbar.
Dies hat folgende Auswirkungen:
-
Wird eine durch isy-security abgesicherte Methode nach einem Thread-Wechsel aufgerufen, schlägt die Autorisierung mit einer
OAuth2AuthenticationExceptionfehl. -
Werden keine abgesicherten Methoden aufgerufen, verhalten sich diese wie für einen anonymen Aufrufer. Das Fehlen des
SecurityContextfällt in diesem Fall nicht auf.
Die Propagierung des SecurityContext in reaktiven Pipelines über den ReactiveSecurityContextHolder von Spring Security ist aktuell nicht Bestandteil der IsyFact.
3.3. Einschränkungen bei isy-task
Der Baustein isy-task integriert isy-logging und isy-security in das Spring Scheduling.
Wird innerhalb von Tasks reaktiver Code ausgeführt z.B. durch Nutzung des WebClient – werden die oben beschriebenen Einschränkungen von isy-logging und isy-security geerbt.
Zusätzlich ist die Task-Überwachung von isy-task für den imperativen Ausführungsfall konzipiert.
Im reaktiven Modell endet die gemessene Ausführungsdauer eines Tasks bereits beim Aufruf von subscribe() und nicht erst nach Abschluss der reaktiven Pipeline.
Die tatsächliche Laufzeit eines reaktiven Tasks wird damit nicht korrekt gemessen.
3.4. Architekturelle Einordnung
Die fehlende Unterstützung reaktiver Verarbeitung durch die IsyFact stellt eine bekannte architekturelle Lücke dar.
Wird der WebClient eingesetzt, können die beschriebenen Probleme nicht eigenständig behoben werden:
Eigene AOP-Aspekte sind gemäß den Vorgaben der IsyFact nicht zulässig, und aktuell werden keine reaktiven Alternativen bereit gestellt.
Technische Lösungsansätze existieren grundsätzlich:
-
Project Reactor unterstützt in Kombination mit Micrometer die automatische Propagierung von Kontext-Informationen in reaktiven Pipelines (
Hooks.enableAutomaticContextPropagation()). Darüber ließe sich die MDC-Propagierung von isy-logging reaktiv-kompatibel gestalten. -
Spring Security stellt mit dem
ReactiveSecurityContextHoldereine reaktive Alternative zumSecurityContextHolderbereit, die denSecurityContextim Reactor-Kontext verwaltet. -
Der AOP-Aspekt der Task-Überwachung in isy-task müsste für reaktive Rückgabetypen angepasst werden, sodass der Kontext erst nach Abschluss der reaktiven Pipeline bereinigt wird.
Diese Lösungsansätze sind aktuell nicht Bestandteil der IsyFact.
4. Schnittstellendokumentation
Alle auf IsyFact basierten Anwendungen müssen ihre REST-Services mit der OpenAPI 3.0 Spezifikation beschreiben. Sowohl YAML als auch JSON sind als Format der Schnittstellendokumentation zulässig. Die IsyFact empfiehlt zur Erstellung der Schnittstellendokumentation den OpenAPI (Swagger) Editor.
4.1. Code-Generierung
Bei der Erstellung von REST-APIs gibt es grundsätzlich 2 Ansätze: Contract/API First oder Code First. Bei Contract/API First wird zunächst die Schnittstellenbeschreibung erstellt und daraus der Code (Server und Client) generiert. Bei Code First wird zuerst die API implementiert und mit Annotationen für die Schnittstellenbeschreibung versehen. Aus den Annotationen wird dann die Schnittstellenbeschreibung generiert.
Die IsyFact empfiehlt den Contract/API First Ansatz und sieht hierzu die Erstellung einer OpenAPI-Spezifikation vor.
Für die Generierung von Code empfiehlt die IsyFact den OpenAPI Generator.
4.2. Bereitstellen von generierten Clients
Neben dem Server können aus einer OpenAPI-Spezifikation auch Clients für diverse Technologie-Stacks generiert werden. Aus Convenience-Gründen wird empfohlen, dass Anbieter von Services fertig generierte Clients für die gängigen Technologie-Stacks zur Verfügung stellen: Java (Spring) und Typescript (Angular).
5. Verwendung von Transportobjekten
Die DTOs werden ebenfalls innerhalb der Schnittstellenbeschreibung schematisch beschrieben.
Der Code für die DTOs wird daraus generiert.
Innerhalb der Schnittstellenbeschreibung des Service werden die Schemata der DTOs üblicherweise als Referenz mittels #ref eingebunden.
Die Referenz kann sich dabei auf eine Beschreibung innerhalb derselben Datei beziehen oder auch auf eine externe Datei.
Die Datei kann dabei sowohl über einen Dateipfad als auch über eine URL adressiert werden.
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
# [...]
components:
schemas:
Pet:
name:
type: string
example: doggie