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.
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]](https://entwickler.de/wp-content/uploads/2018/01/kieselhorst_openapi_2.png)
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
Dieser 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.