Die OpenAPI Specification alias Swagger

Swagger: Mehr als nur Schnittstellenbeschreibung
Keine Kommentare

APIs spielen bei der Verbindung von Anwendungen eine zentrale Rolle. Nahezu jede Applikation greift über APIs auf Datenquellen und andere Systeme zu. Durch die zunehmende Verbreitung von serviceorientierten und Microservices-Architekturen sowie der daraus folgenden Aufteilung hat die Anzahl der involvierten APIs pro Anwendungsfall zugenommen. Die OpenAPI Specification hilft als offenes Beschreibungsformat dabei, den Überblick und das Verständnis über die Fähigkeiten eines API zu erhalten.

Schnittstellen (Application Programming Interfaces, kurz APIs) zwischen Webanwendungen wurden in der Vergangenheit häufig in der Web Services Description Language (WSDL) beschrieben. Technisch ist es mit WSDL 2.0 möglich, auch REST-Schnittstellen zu beschreiben, aufgrund fehlender Ressourcenorientierung ist dies jedoch nur bedingt sinnvoll. Die Web Application Description Language (WADL) adressiert dieses Problem, gilt in der Praxis aufgrund der vorliegenden XML-Struktur aber als umständlich und wurde nie standardisiert.

Die Geburtsstunde von Swagger

Im Jahr 2010 stand auch Tony Tam von Wordnik vor dem Problem, dass zur Beschreibung von REST-APIs eine Sammlung von URLs alleine nicht ausreichte, aber auch kein einfaches, unkompliziertes Beschreibungsformat existierte. Insbesondere die Verwendung unterschiedlicher Technologien und Programmiersprachen in Kombination mit WSDL und WADL gestaltete sich als schwierig. Daraufhin entwickelte er mit Swagger eine Interface Definition Language (IDL) für REST-APIs und veröffentlichte diese Open Source unter der Apache-Lizenz. Swagger bietet ein

  • sprachneutrales und maschinenlesbares Format,
  • definiert in JSON oder YAML,
  • ermöglicht Contract-/API-First- sowie Code-First-Entwicklung und
  • verfügt über einen Erweiterungsmechanismus.

Unterstützt wird dies durch grundlegendes Tooling in Form einer Kernbibliothek (swagger-core), einer Oberfläche für Visualisierung und Testaufrufe (Swagger UI), eines Codegenerators (Swagger Codegen) sowie eines Editors (Swagger Editor).

Gründung der Open API Initiative

Über die Jahre fand Swagger eine Fangemeinde, die Anzahl der Downloads und Forks bei GitHub steigt seit der Veröffentlichung kontinuierlich, und es entsteht immer weitere Toolunterstützung. Anfang 2015 wurde Swagger von SmartBear erworben, bislang vor allem bekannt durch das Tool SoapUI. Um herstellerneutral zu bleiben und die unterschiedlichen Interessen und Verbesserungsvorschläge besser handhaben zu können, wurde Ende 2015 unter dem Dach der Linux Foundation eine Arbeitsgruppe mit dem Namen Open API Initiative (OAI) gegründet. In diesem Zuge wurde Swagger in OpenAPI Specification (OAS) umbenannt. Inzwischen zählt die Initiative 26 Mitglieder, darunter Google, IBM und Microsoft. Die Weiterentwicklung der Spezifikation erfolgt auf GitHub, sodass sich jeder Interessierte durch Pull Requests oder Issues einbringen kann. Nach anderthalb Jahren wurde Ende Juli 2017 die Version 3.0.0 veröffentlicht. In der Folge wird nun intensiv daran gearbeitet, die Toolunterstützung herzustellen. Im weiteren Verlauf dieses Artikels wird daher für die praktischen Beispiele die Version 2.0 genutzt, um im Anschluss die Änderungen der Version 3.0.0 zu erläutern.

Ausgangspunkt für die API-Entwicklung ist entweder die Schnittstellenbeschreibung („Contract/API First“) oder der erstellte Programmcode („Code First“). Im Folgenden wird der Prozess der API-Erstellung nacheinander für beide Varianten erläutert.

Contract/API First

Beim ersten Ansatz können APIs mithilfe eines Editors entworfen werden. Hierzu kann der Onlineeditor unter http://editor.swagger.io (Abb. 1) oder ein Plug-in für die Entwicklungsumgebung (verfügbar z. B. für IntelliJ IDEA) genutzt werden.

Abb. 1: Swagger Editor

Abb. 1: Swagger Editor

Vorteilhaft ist diese Vorgehensweise beispielsweise, wenn unterschiedliche Teams (häufig sogar in unterschiedlichen Unternehmen) für die Client- und Serverseite verantwortlich sind. Die Schnittstellenbeschreibung ist in dem Fall der vereinbarte Vertrag, an den sich beide Seiten zu halten haben. Client- und Servercode können mit Swagger Codegen direkt daraus generiert werden. Hierzu existieren Generatoren für alle gängigen Sprachen, die Vorlagen für die Generierung können auch angepasst und erweitert werden. So ist gegenüber manuell programmierten API-Aufrufen sichergestellt, dass der Code konsistent zum API ist. Bei Brüchen treten Kompilierungsfehler auf, die z. B. durch Einbindung in ein Continuous-Integration-System wie Jenkins automatisch sichtbar gemacht werden können.

Inzwischen gibt es aber auch kritische Stimmen zur spezifikationsbasierten Codegenerierung: Während WSDL2Java in SOAP-Zeiten gängige Praxis war, solle im Zeitalter von REST das Tolerant-Reader-Pattern angewendet werden und nur die Felder aus dem API extrahiert und verarbeitet werden, die auch wirklich nötig sind. Dadurch erhöhe sich die Test- und Wartbarkeit des Codes. Prinzipiell lässt sich hier keine pauschale Empfehlung geben. Es kommt auf viele Faktoren an (u. a. Umfang und Änderungshäufigkeit des API), sodass der Einsatz im jeweiligen Projekt bewertet werden sollte.

Listing 1 zeigt das Swagger-Petstore-Beispiel im YAML-Format. Die Datei beginnt mit Angabe der verwendeten Spezifikationsversion, gefolgt von generellen Informationen zum API im Abschnitt info. Host, Pfad und URL-Schema werden in weiteren Variablen getrennt angegeben, bevor dann für das gesamte API der Mediatyp (produces/consumes) einheitlich festgelegt wird (hier erkennt man schon eine Schwäche der Version 2.0). Danach werden die Inhalte dargestellt: Pfade, Operationen, Parameter und die jeweiligen Antworten nebst zugehöriger Beschreibung. Datentypen werden wiederverwendbar in einem Abschnitt definitions verwaltet.

swagger: "2.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
host: petstore.swagger.io
basePath: /v1
schemes:
  - http
consumes:
  - application/json
produces:
  - application/json
paths:
  /pets:
    get:
      summary: List all pets
      operationId: listPets
      tags:
        - pets
      parameters:
        - name: limit
          in: query
          description: How many items to return at one time (max 100)
          required: false
          type: integer
          format: int32
      responses:
        "200":
          description: An paged array of pets
          headers:
            x-next:
              type: string
              description: A link to the next page of responses
          schema:
            $ref: '#/definitions/Pets'
        default:
          description: unexpected error
          schema:
            $ref: '#/definitions/Error'
    post:
      summary: Create a pet
      operationId: createPets
      tags:
        - pets
      responses:
        "201":
          description: Null response
        default:
          description: unexpected error
          schema:
            $ref: '#/definitions/Error'
  /pets/{petId}:
    get:
      summary: Info for a specific pet
      operationId: showPetById
      tags:
        - pets
      parameters:
        - name: petId
          in: path
          required: true
          description: The id of the pet to retrieve
          type: string
      responses:
        "200":
          description: Expected response to a valid request
          schema:
            $ref: '#/definitions/Pets'
        default:
          description: unexpected error
          schema:
            $ref: '#/definitions/Error'
definitions:
  Pet:
    required:
      - id
      - name
    properties:
      id:
        type: integer
        format: int64
      name:
        type: string
      tag:
        type: string
  Pets:
    type: array
    items:
      $ref: '#/definitions/Pet'
  Error:
    required:
      - code
      - message
    properties:
      code:
        type: integer
        format: int32
      message:
        type: string

Neben der Textform lässt sich das API auch visualisieren. Am häufigsten wird dazu Swagger UI (Abb. 2) genutzt, das bereits im Swagger Editor auf der rechten Seite zu sehen war. Über die Oberfläche ist es auch möglich, direkt Testanfragen aus dem Browser abzusenden.

Abb. 2: Ansicht des Swagger Petstore in Swagger UI [9]Abb. 2: Ansicht des Swagger Petstore in Swagger UI [9]

Abb. 2: Ansicht des Swagger Petstore in Swagger UI

Interessante Alternativen sind Swagger2Markup und ReDoc. Die generierte Dokumentation ist ausführlicher, allerdings besteht keine Möglichkeit, direkt API-Anfragen auszulösen.

Code First

Bei Code-First-Entwicklung nimmt die Spezifikation ihren Ausgangspunkt im Programmcode. Für das nachfolgende Beispiel wird das Vorgehen mit Java und JAX-RS erläutert, für .NET, PHP und weitere Sprachen gibt es aber ähnliche Möglichkeiten.

Swagger-Annotationen ermöglichen es, in Java die entsprechenden Eigenschaften festzulegen. Die wesentlichen Annotationen sind in Tabelle 1 aufgeführt. Listing 2 zeigt die konkrete Anwendung exemplarisch anhand einer Operation aus dem Swagger-Petstore-Beispiel.

Annotation Beschreibung
@Api Markiert eine Ressource und ermöglicht auf dieser Ebene, Definitionen für alle darunterliegenden Operationen vorzunehmen, Daten von den JAX-RS-Annotationen @Path, @Consumes und @Produces werden übernommen.
@ApiOperation Deklariert eine einzelne Operation (Pfad und HTTP-Methode).
@ApiResponse Definiert einen Rückgabewert nebst Fehlercode.
@ApiParam Ermöglicht es, die Daten aus den JAX-RS-Parameterannotationen um weitere Details zu erweitern.
@ApiModel Kann für Metadaten auf Klassenebene genutzt werden, die sich dann im Schema wiederfinden.
@ApiModelProperty Erlaubt die konkreten Schemainhalte zu beschreiben, zusätzlich werden auch JAXB- und Bean-Validation-Annotationen verarbeitet.

Tabelle 1: Wesentliche Annotationen aus dem Paket Swagger Annotations

@Path("/pet")
@Api(value = "/pet", description = "Operations about pets", tags = "pet")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public class PetResource {
  static PetData petData = new PetData();

  @GET
  @Path("/{petId}")
  @ApiOperation(value = "Find pet by ID",
    notes = "Returns a pet when ID <= 10.  ID > 10 or nonintegers will simulate API error conditions",
    response = Pet.class
  )
  @ApiResponses(value = { @ApiResponse(code = 400, message = "Invalid ID supplied"),
      @ApiResponse(code = 404, message = "Pet not found") })
  public Response getPetById(
      @ApiParam(value = "ID of pet that needs to be fetched", allowableValues = "range[1,10]", required = true) @PathParam("petId") Long petId)
      throws NotFoundException {
    Pet pet = petData.getPetById(petId);
    if (pet != null) {
      return Response.ok().entity(pet).build();
    } else {
      throw new NotFoundException(404, "Pet not found");
    }
  }
}

@XmlRootElement(name = "Pet")
@ApiModel(value = "Pet", subTypes = {Cat.class}, discriminator = "type")
public class Pet {
  private long id;
  private String name;
  private String status;

  @XmlElement(name = "id")
  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  @XmlElement(name = "name")
  @ApiModelProperty(example = "doggie", required = true)
  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  @XmlElement(name = "status")
  @ApiModelProperty(value = "pet status in the store", allowableValues = "available,pending,sold")
  public String getStatus() {
    return status;
  }

  public void setStatus(String status) {
    this.status = status;
  }

}

Zur Laufzeit wird aus diesen Annotationen die Swagger-Beschreibung generiert, die dann über eine Ressource aus dem swagger-jaxrs-Modul unter /swagger.yaml bzw. /swagger.json (plus ggf. konfiguriertem Pfad) abrufbar ist. Für Jersey, RESTEasy und Mule existieren passende Ergänzungsmodule innerhalb des Swagger-Projekts. Darüber hinaus gibt es weitere Projekte mit Unterstützung, wie z. B. SpringFox oder Apache CXF. Bei letzteren kann die erzeugte Beschreibung auch direkt in einem integrierten Swagger UI angezeigt werden.

Häufig kommt in Projekten die Frage auf, ob es eine Möglichkeit gibt, Anfragen auf Konformität zur Swagger-Definition prüfen zu können (wie XML gegen XML Schema). Hierzu hat Atlassian einen Validator erstellt, der sich auch sehr gut in Unit-Tests einsetzen lässt.

Alternativen zur OpenAPI Specification

Die OpenAPI Specification ist mit Abstand am bekanntesten, nichtsdestotrotz sollen kurz zwei vergleichbare Alternativen erwähnt werden.

API Blueprint
API Blueprint fokussiert sich stärker auf die menschliche Lesbarkeit und Einfachheit. Der API-First-Ansatz steht im Vordergrund. Die APIs werden in einer Markdown-ähnlichen Syntax beschrieben.

RESTful API Modeling Language (RAML)
RAML ist YAML-basiert und erlaubt komplexere Definitionen als Swagger 2.0. API First ist auch hier der Ausgangspunkt. Mulesoft als Ersteller von RAML ist inzwischen der Open API Initiative beigetreten, sodass zukünftige Versionen ggf. auf der OpenAPI Specification aufbauen werden.

Praktische Anwendungsbeispiele

Um die praktische Anwendbarkeit zu unterstreichen, werden kurz zwei Beispiele deutscher Unternehmen vorgestellt.

Zalando, einer der umsatzstärksten deutschen Onlineshops, schreibt in seinen Richtlinien vor, dass APIs nach dem API-First-Prinzip zu erstellen und gemäß der OpenAPI Specification zu dokumentieren sind. Intern wird eine Vielzahl von APIs zwischen den Services autonomer Teams genutzt. Öffentlich stellt Zalando ein API zum Abruf von u. a. Artikeln und Bewertungen zur Verfügung.

Lufthansa, das größte Luftverkehrsunternehmen Deutschlands, stellt seit diesem Jahr mit dem Lufthansa Open API eine Schnittstelle zur Verfügung, mit der sich bspw. Flugpläne und Flugstatus auslesen lassen. Entwickelt wurde diese vom Lufthansa Innovation Hub und kürzlich mit dem Digital Leader Award prämiert.

Unterschiede zwischen Version 2.0 und 3.0.0

Die neue Version 3.0.0 der OpenAPI Specification unterscheidet sich vom Vorgänger durch einen klareren Aufbau. Auf der obersten Ebene ist die Struktur aufgeräumt worden, was in der Konsequenz dazu führt, dass die neue Version zur bestehenden inkompatibel ist (eine Migration ist aber automatisiert möglich). Wie in Abbildung 3 zu sehen, wurden definitions, parameters und responses zu allgemeinen wiederverwendbaren Elementen im Bereich components zusammengefasst.

Bislang wurde nur ein Host unterstützt, zukünftig können mehrere Zielserver nebst schema und basePath angegeben werden. Diese Werte können innerhalb des paths-Abschnitts auch individuell überschrieben werden, falls beispielsweise Teilfunktionen des API auf einem anderen Server bereitgestellt werden. Ebenfalls pro Pfad können nun eine zusammenfassende und eine ausführliche Beschreibung angegeben werden. Bislang war dies nur auf Operationsebene möglich, was bei unterschiedlichen Operationen für denselben Pfad (bspw. GET, POST, DELETE bei typischen CRUD-Szenarien) zu Duplizierung führte. CommonMark-Syntax ermöglicht eine Textformatierung.

Weiterhin wurden die Möglichkeiten erweitert, Beispiele im Feld examples festzulegen. Diese können nun flexibel als String oder als Referenz auf externe Dateien mit $ref angegeben werden. Diese Gelegenheit sollte unbedingt genutzt werden, da die Semantik der Schnittstelle verdeutlicht wird und Unklarheiten zwischen den Schnittstellenpartnern ausgeräumt werden können.

Bei den Beispielen für die Version 2.0 wurde bereits erwähnt, dass bislang nur ein Mediatype für das gesamte API möglich war. Die Angabe ist in der neuen Version jetzt pro Pfad im content-Objekt möglich, das den Inhalt der Anfragen und Antworten bündelt und für jeden Mediatype separate Schema- und Beispielverweise zulässt. Listing 3 zeigt das Beispiel aus Listing 1 in seiner migrierten Form.

openapi: "3.0.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
servers:
  - url: http://petstore.swagger.io/v1
paths:
  /pets:
    get:
      summary: List all pets
      operationId: listPets
      tags:
        - pets
      parameters:
        - name: limit
          in: query
          description: How many items to return at one time (max 100)
          required: false
          schema:
            type: integer
            format: int32
      responses:
        200:
          description: An paged array of pets
          headers:
            x-next:
              description: A link to the next page of responses
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pets"
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      summary: Create a pet
      operationId: createPets
      tags:
        - pets
      responses:
        201:
          description: Null response
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /pets/{petId}:
    get:
      summary: Info for a specific pet
      operationId: showPetById
      tags:
        - pets
      parameters:
        - name: petId
          in: path
          required: true
          description: The id of the pet to retrieve
          schema:
            type: string
      responses:
        200:
          description: Expected response to a valid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pets"
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
components:
  schemas:
    Pet:
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string
    Pets:
      type: array
      items:
        $ref: "#/components/schemas/Pet"
    Error:
      required:
        - code
        - message
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string

Während bislang einfache Request/Response-HTTP-APIs im Fokus standen, werden zukünftig auch andere Muster unterstützt. Im Bereich callbacks kann gemäß dem Publish/Subscribe-Pattern eine Operation spezifiziert werden, die vom Aufrufer als Rückantwort erwartet wird (bspw. zur Nutzung sogenannter Webhooks). Außerdem wurde ein Abschnitt links hinzugefügt, in dem auf andere relevante Ressourcen verwiesen werden kann, wie es bei Hypermedia-APIs üblich ist.

Zuletzt wurde die Unterstützung von JSON Schema erweitert. Alternative Schemas wie Google Protocol Buffers sind in der Diskussion, wurden bislang aber noch nicht in die Specification integriert. Für asynchrone Anwendungsfälle wie MQTT und AMQP wurde daher basierend auf der OpenAPI Specification die AsyncAPI Specification ins Leben gerufen.

Fazit

Swagger 2.0 ist aufgrund der hohen Verbreitung und der umfangreichen Toolunterstützung der Quasistandard für die Schnittstellenbeschreibung REST-basierter Anwendungen. Die OpenAPI Specification 3.0.0 sorgt für Ordnung in der gewachsenen Swagger-Struktur. Durch ein breites Gremium, in dem alle namhaften Hersteller vertreten sind, und die neuen Features, wird sich die OpenAPI Specification in der Branche durchsetzen und Swagger 2.0 ablösen, sobald die Toolunterstützung auf die neue Version angepasst ist.

Entwickler Magazin

Entwickler Magazin abonnierenDieser Artikel ist im Entwickler Magazin erschienen.

Natürlich können Sie das Entwickler Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -