Swagger

Entdecke die faszinierende Welt der APIs und wie das Open-Source-Projekt Swagger sie revolutioniert hat. Lerne mehr über die Funktionsweise und Vorteile von Swagger, wie es die Erstellung, Wartung und Integration von APIs erleichtert und welche Rolle die OpenAPI-Spezifikation spielt. Dieser ausführliche Artikel beleuchtet alles - von API-Kernelementen bis zu Vor- und Nachteilen von Swagger. Eine unverzichtbare Lektüre für Entwickler und Technikbegeisterte.

Einführung

Einer der größten Vorteile unserer hoch vernetzten digitalen Welt ist die nahezu unerschöpfliche Vielfalt an Informationen, Wissen und praktischen Anwendungen. Für letztgenannte gilt im Besonderen, dass eine moderne Applikation über eine oder mehrere Schnittstellen auf die entsprechenden Ressourcen zugreifen, diese anzeigen und auch bearbeiten kann. Daher gelten für diese APIs auch besondere Vorgaben bei der Projektierung und Entwicklung. Eine derartige Schnittstelle sollte möglichst umfassend und verständlich für alle Beteiligten beschrieben werden. Diese hohe Transparenz hilft auch beim Auffinden von Design- und Programmfehlern.

Bereits 2010 hatte der eifrige Programmierer Tony Tam von Wordnik große Probleme, dass für eine strukturierte Beschreibung einer von Ihm zu programmierenden REST-API-Schnittstelle die reine Aufzählung der verwendeten URLs nicht ausreichen würde. Auch die notwendige Kombination von verschiedenen Programmiersprachen und Technologien wie WSDL oder WADL gestaltete sich zu diesem Zeitpunkt besonders schwierig. Die Lösung fand er in der Definition einer eigenen Interface Definition Language (IDL) für REST-API’s und nannte diese Swagger, die nach wie vor als Open Source unter der Apache-Lizenz verfügbar ist. 2015 wurde die Swagger-Definition dann im Rahmen der neu gegründeten Open API Initiative (OAI) konsequenterweise in die Open API Specification (OAS) umbenannt.

Die API-Kernelemente in Swagger

Die Basis einer API-Beschreibungssprache besteht immer aus der Darstellung der einzelnen Kernelemente wie die Dokumentation, die Zugriffs- und Endpunkte sowie eine detaillierte Beschreibung der verwendeten und möglichen Fehlercodes. Je detaillierter diese Kernelemente beschrieben werden, umso einfacher ist die Anwendung und Wartung der API. Ein weiterer wichtiger Punkt ist die Sicherheit und damit auch die in der Schnittstelle verwendeten Security-Elemente. Ausschlaggebend für den Funktionsumfang sind die in der API vereinbarten Geschäftsobjekte in Form von Typendeklarationen nebst allen verwendeten Parametern und Rückgabewerten.

Je genauer diese Angaben in der Schnittstelle beschrieben werden, umso transparenter und einfacher wird deren Anwendung, Wartung und Integration ausfallen. Für die Beschreibung dieser Kernelemente verwenden wir exemplarisch einen fiktiven User-Store, bei dem sich ein Benutzer registrieren und den Shop für sein Haustier nutzen kann.

Docs

Ein wichtiges Kernelement für die API stellt der Bereich Docs dar. Hierin können alle für die Schnittstelle relevanten Textressourcen und Dokumente als Referenz hinterlegt werden. Dies können zum einen eine Funktionsbeschreibung der API, eine Entwicklerdokumentation oder auch ein Benutzerhandbuch für die Anwender sein. Für die Beschreibung einer Ressource stehen die beiden Parameter description und url zur Verfügung. Das folgende kurze Codeschnippsel zeigt eine mögliche Anwendung:

externalDocs:
  description: "Find out more about Swagger"
  url: "http://www.api-portal.io"

Details

Der Bereich Details beinhaltet alle wichtigen Informationen zur gesamten Schnittstelle. Diese Basisinformationen sind damit für die gesamte API gültig. Für die entsprechenden Angaben stehen einige Parameter wie auszugsweise description, version, title oder host zur Verfügung. Die wichtigsten Parameter sind hier basePath und schemes. Mit basePath kann sehr einfach und übersichtlich auf eine Versionierung eingegangen werden, indem man hier das aktuelle Verzeichnis angibt. In schemes werden die für diese API verwendeten Transfer-Protokolle aufgelistet. Im folgenden Quelltextausschnitt sehen Sie eine mögliche Parametrisierung:

info:
  description: "This is a sample server Userstore server."
  version: "1.0.0"
  title: "Swagger Users"
host: "users.api-portal.io"
basePath: "/v2"
schemes:
  - "http"

Beachten Sie hier die Möglichkeiten mit den Parametern basePath und version. Trotz einer Version 1.0.0 im Parameter version kann das Basisverzeichnis im Parameter basePath durchaus abweichend gestaltet sein.

Security

In einer API-Schnittstelle können ohne weiteres auch unterschiedliche Authentifizierungsmethoden verwendet werden. So können problemlos OAuth 2.0, Digest und Pass-Through gemeinsam verwendet werden. Für jeden Authentifizierungstyp gelten dabei ein Satz an notwendigen Parametern wie type, name, scopes oder auch flow. Im folgenden Beispiel verwenden wir OAuth 2.0 für die Authentifizierung und beschreiben dessen Anwendung in verschiedenen Parametern wie folgt:

securityDefinitions:
  userstore_auth:
    type: "oauth2"
    authorizationUrl: "http://users.api-portal.io/oauth/auth"
    flow: "implicit"
    scopes:
      write:users: "modify users in your account"
      read:users: "read your users"
  api_key:
    type: "apiKey"
    name: "api_key"
    in: "header"

Im Parameter type tragen wir OAuth2 als Authentifizierungsmethode ein und setzen den Scope auf die korrespondierenden Write- und Read-Methoden. Über api_key definieren wir den Zugriffsschlüssel nach Name und seinem Vorkommen im header.

Ressources

Für den Zugriff auf die unterschiedlichen Ressourcen des Hosts über das HTTP-Protokoll werden in der API zunächst die gängigsten Zugriffsmethoden deklariert. Hierzu gehören die 4 Standard-Methodenaufrufe GET, POST, PUT und DELETE. Hierüber wird dem jeweiligen Browser die gewünschte Verarbeitung signalisiert und wie die angefügten Parameter zu verwenden sind. Nachfolgend finden Sie die vier Methodenaufrufe mit einer kurzen Erläuterung beschrieben.

GET - Über die GET-Methode wird lesend auf eine definierte Ressource zugegriffen. In unserem fiktiven Beispiel des User-Shops kann hier ein bestimmtes Tier oder Produkt gesucht und danach angezeigt werden. Der Beispielaufruf hierzu lautet vereinfacht dargestellt: GET /user/{userId}

POST - Die POST-Methode wird immer dann verwendet, wenn eine neue aber noch nicht vorhandene Ressource erstellt werden soll. Wir können in unserem Beispiel ein neues Haustier mit folgendem Aufruf sehr einfach einfügen: POST /user 2

PUT – Mit dieser Methode kann eine Änderung an einer bereits im System bestehenden Ressource durchgeführt werden. Für unser Beispiel könnte eine Änderung vereinfacht wie folgt durchgeführt werden: PUT /user 2

DELETE - Sofern eine Ressource im Bestand nicht mehr benötigt wird, kann mit sehr einfach über den folgenden Methodenaufruf entfernt werden. Die Identifizierung erfolgt hier über die ID Nummer wie folgt: DELETE /user/{userId}

Das Zusammenspiel der Methoden und Parameter wird im folgendem Codefragment kurz dargestellt. Wir gehen davon aus, dass ein registrierter Benutzer im Bestand nach einem zuvor festgelegten Haustier suchen möchte. Hierfür wird die userId verwendet.

/user/{userId}:
  get:
    tags:
      - "user"
    summary: "Find user by ID"
    description: "Returns a single user"
    operationId: "getUserById"
    produces:
      - "application/xml"
      - "application/json"
    parameters:
      - name: "userId"
        in: "path"
        description: "ID of user to return"
        required: true
        type: "integer"
        format: "int64"
    responses:
      200:
        description: "successful operation"
        schema:
          $ref: "#/definitions/User"
      400:
        description: "Invalid ID supplied"
      404:
        description: "User not found"
        security:
          - api_key: []

Nach dem Methodenaufruf wird auch der ResponseCode entsprechend ausgewertet und zeigt über die hier implementierten 3 Auswertungscodes, ob die Abfrage erfolgreich war oder die ID-Nummer nicht ausgewertet werden konnte. Das Ergebnis kann dem Benutzer dann formatiert angezeigt werden.

Types

Im Bereich der types werden nun alle verwendeten Geschäftsobjekte der Schnittstelle eingetragen und verwaltet. Diese Objekte können dann innerhalb der API immer wieder und an verschiedenen Stellen verwendet werden. Die Beschreibung eines Objektes wird über seine Eigenschaften durchgeführt. Um bei unserem fiktiven Beispiel des User-Shops zu bleiben, finden Sie im folgenden Quelltext die Beschreibung des Benutzer-Geschäftsobjektes User mit allen Parameterdefinitionen:

definitions:
  User:
    type: "object"
    properties:
      id:
        type: "integer"
        format: "int64"
      username:
        type: "string"
      firstName:
        type: "string"
      lastName:
        type: "string"
      email:
        type: "string"
      password:
        type: "string"
      phone:
        type: "string"
      userStatus:
        type: "integer"
        format: "int32"
        description: "User Status"
        xml:
          name: "User"

Eine Objekt-Eigenschaft wie id wird in diesem Quelltext über die Eigenschaften type: und format: beschrieben. Die ID ist somit ein Integer-Wert im Format Int-64. Alle weiteren Eigenschaften werden ähnlich definiert, bis das Objekt vollständig beschrieben ist.

Errors

Eine der wichtigsten Definitionen einer Schnittstelle sind die möglichen Fehler- oder Response-Codes. Hierüber kann die API eine Auswertung eines Methodenaufrufs vornehmen und bei Fehlern anhand des Fehlercodes beispielsweise eine Klartextmeldung für den Anwender generieren. Die Deklaration erfolgt immer nach dem Schema {Fehlercode: Beschreibung/Meldung}. Im folgenden Beispiel finden Sie die exakte Notation:

responses:
  200:
    description: "successful operation"
  400:
    description: "Invalid ID supplied"
  404:
    description: "Order not found"

Vor- und Nachteile von Swagger

Die API-Entwicklung mit Swagger bietet einige wesentlichen Vorteile. Hier sind zum einen die weit verbreiteten Projekt-Ansätze wie Code-First oder Contract-/API-First zu nennen. Auf der anderen Seite bietet Swagger ein sprachenneutrales und vor allem maschinenlesbares Format an. Die Definitionen können dabei entweder in JSON oder auch YAML codiert werden. Zudem verfügt Swagger auch über einen relativ einfachen Erweiterungsansatz. Die Entwicklung wird dabei von etablierten Tools wie die Kernbibliothek (Swagger-Core), der Swagger-UI für Testing und Visualisierung, dem leistungsstarken Swagger-Editor und dem Codegenerator (Swagger-Codegen) unterstützt. Ein weiterer Pluspunkt ist die enorm große Community und die vielen Code-Snippets für nahezu alle Anwendungsfälle.

Gegenüber seinen Konkurrenten kann Swagger allerdings keine Codefragmente flexibel wiederverwenden. Somit sind Includes oder Extensions nicht mehrfach nutzbar, was einen erheblichen Mehraufwand und damit Nachteil in der Programmierung der API darstellt. In einigen Punkten wie beispielsweise der Serverspezifikationen oder der Security kann Swagger nicht mehr mit der neuen OpenAPI Spezifikation 3 mithalten. Content Negotiation und der Ausbau des JSON-Schemas sind weitere Pluspunkte für die modernere API-Beschreibungssprache.

Fazit

Gerade im Bereich der RESTful-APIs und der beiden weit verbreiteten Projektansätze Code-First und Contract-/API-First ist Swagger eine sehr gute Wahl. Die große Community und die zahlreichen verfügbaren Tools für nahezu alle Programmiersprachen bieten eine extrem breite und professionelle Ausgangsbasis für die Entwicklung von Schnittstellen. Die Wandlung von Swagger zur OpenAPI-Initiative in der aktuellen Version 3.0 bringt allerdings viele Neuerungen und Vorteile. Mehrere Hosts und eine wesentlich klarere Struktur sprechen für die überarbeitete Version. Mit der neuen OpenAPI-Spezifikation in der aktuellen Version 3.0 und der breiten Unterstützung der namhaften Hersteller in der neuen Initiative wird sich in naher Zukunft aber OpenAPI 3.0 durchsetzen und Swagger 2.0 damit schrittweise ablösen. Auch im Bereich der Tools und Codegeneratoren bedarf es für die OAS noch an einiger Aufholarbeit, was letztendlich aber nur eine Frage der Zeit ist.