API First mit Swagger und SpringFox

Auf der EUROPACE Plattform setzen wir schon seit langem APIs ein, um die verschiedenen Module und Services miteinander kommunizieren zu lassen. Einige APIs können bereits öffentlich verwendet werden, wie PEX (HTTP/JSON) oder BEX (SOAP/XML). Genauere Informationen können in unserem GitHub-Projekt eingeholt werden.

Wir, Peter Sauer und Daniel Ranke, sind Entwickler des Marktplatzes für Immobilienfinanzierung und möchten unsere Erkenntnisse bei der Entwicklung einer neuen API (Vorgänge und Anträge aus BaufiSmart auslesen) teilen. Wir setzen zunehmend Microservices ein und haben uns für REST via HTTP und JSON entschieden.

Code first

Unsere bisherigen APIs wurden im Code begonnen und stark an dem internen Model ausgerichtet. Bei der Wiederverwendung haben wir oft den Schmerz gespürt, dass die Abbildung nicht zum neuen Anwendungsfall gepasst hat. Daher wird die API stark clientgetrieben erweitert und verliert ihren Fokus.

API First

Dementsprechend haben wir uns auf die Fahnen geschrieben dem "API First"-Ansatz zu folgen. Wir versprechen uns davon, frei von jeglichen Implementierungen, die neue API zu entwerfen und weiter zu entwickeln. Aus fachlicher Sicht sind wir mit dem Ziel gestartet, eine leicht zu verstehende (gute Dokumentation) und leicht zu implementierende (Client per Codegenerierung) API zu designen.

Swagger

Nachdem wir uns existierende Werkzeuge wir RAML, ApiGee und Swagger angesehen haben, sind wir bei Swagger geblieben. Die API wird mittels YAML-Datei beschrieben. Offen bleibt die Entscheidung, ob wir die YAML-Datei per Hand schreiben, oder ob wir ein Java-Model nutzen, aus dem sie erzeugen wird.

Was passiert mit YAML

Unabhängig von ihrer Erzeugung ist die YAML-Datei die zentrale Spezifikation unserer API und dient als Grundlage für alle weiteren Schritte.

Fachliche Dokumentation

Neben der technischen Spezifikation in der YAML-Datei, hatten wir auch die Anforderung eine für Menschen gut lesbare fachliche Dokumentation der API bereitzustellen. Die Swagger-Spezifikation kam uns dabei sehr entgegen, da neben den rein technischen Aspekten (welche Endpunkte existieren und welche Parameter werden unterstützt) auch die fachlichen Aspekte (Bedeutung der Parameter) im Prosatext beschrieben werden können.

Swagger UI

Die Dokumentation mit der Swagger UI darzustellen, haben wir verworfen. Wir haben ein sehr großes Modell, weshalb der Betrachter exzessiv scrollen muss, um das gesamte Modell betrachten zu können.

ApiDocs

Als Alternative zur Swagger UI hatten wir uns auch den Service von ApiDocs angeschaut. Die generierte Dokumentation sah auf den ersten Blick ganz gut aus. Leider war die Dokumentation nicht vollständig. In der Dokumentation fehlten der Abschnitt zur Security sowie die Typen an den Referenzen.

Des Weiteren waren die eingebauten Clients zum Ausprobieren der dokumentierten Endpunkte nicht nutzbar, da diese die benötigten Credentials für die Authentifzierung nicht mitgeschickt haben.

Statische HTML Seiten

Am Ende sind wir bei einer konventionellen Lösung gelandet. Wir generieren aus der YAML statische HTML Seiten. Hierbei funktioniert die Navigation zwischen den Entitäten sehr gut und die fachliche Dokumention ist klar erkennbar. Auf die interaktiven Elemente haben wir verzichtet. Stattdessen dokumentieren wir, wie ein Client für die API einfach erzeugt werden kann, um die API auszuprobieren.

CSV und Excel-Dateien

Zur Unterstützung von Mappings durch die Anwender generieren wir CSV- und Excel-Dateien aus der YAML-Datei mit. Dies ermöglicht eine interaktive Arbeit mit der API-Dokumentation.

Generierung von Clients

Ein großer Vorteil des API-first Ansatzes und die entsprechende Dokumentation der API in einer YAML-Datei ist die Möglichkeit, Clients zur Verwendung der API automatisch zu generieren. Hierfür haben wir das Tool swagger-codegen verwendet, welches aus einer YAML-Datei sowohl Client als auch Server in einer Vielzahl unterschiedlicher Sprachen und Frameworks generieren kann.

Um einen Java-Client zu erzeugen reicht die Ausführung folgenden Befehls:

java -jar swagger-codegen-2.2.2.jar -i api.yaml -l java

Die Generierung lässt sich auch hervorragend in Buildtools wie Maven (Maven-Plugin) oder Gradle einbinden. Wir nutzten dies aktiv um unsere API per Contract-Test regressionssicher zu machen.

YAML erzeugen

Wie bereits erwähnt, kann die YAML-Datei sowohl per Hand geschrieben als auch generiert werden.

YAML manuell erzeugen

Zum manuellen Schreiben von Swagger-YAML-Dateien gibt es zahlreiche Tools zur Unterstützung. In früheren Projekten haben wir sehr gute Erfahrungen mit dem Swagger Editor und auch dem entsprechenden IntelliJ Idea Plugin gemacht. Ausgehend von der Annahme, ein möglichst vollständiges Modell für die ersten Anwender zu erzeugen, schätzten wir den Aufwand für die manuelle Erzeugung als sehr hoch ein.

YAML aus JAVA-Klassen erzeugen - SpringFox

Somit haben wir bestehende Modelle in ein neues Projekt kopiert. Das weitere Vorgehen bestand darin, die Repräsentation sowie Bezeichner zu überarbeiten. Unsere Kernanwendung basiert auf JAVA, weshalb ein entsprechendes Framework hilfreich ist. Das SpringFox-Projekt erfüllt diesen Anspruch. Mit generierten Zufallswerten und einer einfachen "Spring Boot"-Anwendung habe wir somit sehr schnell einen Mock-Service bereitstellen können. Jetzt hatten wir ein Setup, mit dem wir unter relativ kurzen Turnaround-Zeiten die API entwickeln konnten.

YAML mit HAL

Von Anfang an war gesetzt, dass wir unseren Anwendern eine navigierbare API anbieten, welche URLs enthält um auf weitere mögliche Aktionen hinzuweisen. Der Nutzer sollte sich möglichst wenig feste URLs merken müssen und die API über HAL-Verlinkungen erkunden können. Die technische Realisierung dazu hat sich im Zusammenspiel mit SpringFox als etwas knifflig erwiesen. Zunächst sah die Spring-HATEOS Biblothek vielversprechend aus. Sie liefert aber Resourcen und Relationen nur zur Laufzeit. Für SpringFox ist es aber notwendig zur Kompilezeit alle Ressourcen und Relationen zu kennen, damit die YAML-Datei und die daraus generierte Dokumentation diese Elemente enthält und darstellen kann.

Zusammenfassung

Wenn man Swagger zur Dokumentation der API verwendet, dann ist es sinnvoll die YAML-Datei als Basis für die API zu verwenden. Damit geht einher diese Datei aktuell zu halten. Jeder Change wird damit über die YAML-Datei angestoßen.
Dabei kann man durchaus die YAML aus dem Servercode erzeugen. Eine Testabdeckung zur Vermeidung von breaking changes ist dabei einfach realisierbar, alte Clients können aus alten YAML Versionen erzeugt werden und die Regressionstests durchlaufen.

Learnings

fachliche Vorgehensweise

Ist die API im API-First Ansatz erst einmal vollständig spezifiziert, ist die Implementierung der API weitestgehend ohne fachlichen Support durch Business Analysten möglich. Bei unserem sehr großen Modell war es von Nachteil die gesampte API in einem großen Wurf auf einmal spezifizieren zu wollen.
Zum einen sind die Reviewzyklen sehr arbeitsaufwändig. Und zum anderen geht Wissen bis zur Implementierung des Mappings verloren. Das hat bei uns dazu geführt, dass wir eine neue Sicht auf das Modell bekommen haben, in dem Moment als wir die Quelldaten mappen wollten.
Unser Learning dazu ist, jede API in kleineren Schritten zu spezifizieren und auch gleich zu mappen. Idealerweise hat man dann von Anfang an eine Anbindung an das Produktionssystem. Das Hinzufügen von neuen Feldern ist immer abwärtskompatibel.
Hilfreich ist der Einsatz und Dokumentation von Patterns, um wiederkehrende Probleme wie z.B. Polymorphien einheitlich zu lösen.

technische Aspekte

Die von uns verwendeten Tools SpringFox und swagger-codegen hängen der Swagger-Spezifikation etwas hinterher. So ist es zum Beispiel ohne Weiteres möglich, Vererbungshierachien in Swagger-YAML zu spezifizieren, aber sowohl SpringFox als auch swagger-codegen stoßen hier an ihre Grenzen. Allerdings ist auch die Swagger-Spezifikation nicht ganz perfekt. So lassen sich Referenzen nicht um fachliche Beschreibungen ergänzen.

Da wir die YAML-Datei aus JAVA Klassen generieren, erhalten wir eine einzige sehr große YAML-Datei. Eine manuell geschriebene YAML-Datei kann effizienter aufgebaut sein. Des Weiteren können YAML-Dateien auch importiert werden, so dass eine modulare Struktur möglich wäre.

Bewährt hat sich auch, dass wir am Anfang recht viel Zeit in die Evaluation des Toolings gesteckt haben. Dadurch haben wir ein gutes Verständnis über die Frameworks erhalten und ein stabiles, zukunftssicheres Projektsetup erzeugt.