Suche
API First mit RAML

Entwicklung und Dokumentation von REST-APIs

Kai Spichale
api-first-mit-raml

© Shutterstock / Kirill__M

Für die Beschreibung von REST-APIs gibt es (glücklicherweise) keinen Standard, der Innovationen bremst, sondern unterschiedliche Formate und Best Practices, die im Laufe der Zeit entstanden und von den Erfahrungen ihrer jeweiligen Vorgänger profitierten. Der jüngste Spross in dieser Reihe ist RAML, eine „RESTful API Modeling Language“, die in diesem Artikel als wichtiges Werkzeug für API First Development vorgestellt wird.

Gute APIs zeichnen sich durch unterschiedliche Qualitätsmerkmale aus. Hierzu gehören Konsistenz, einfache Benutzbarkeit und geeignete Abstraktionen. Auch die Bedeutung einer hilfreichen Dokumentation darf für ein API nicht unterschätzt bzw. vernachlässigt werden, soll es doch möglichst einfach und von möglichst vielen anderen Entwicklern verstanden und benutzt werden können. SOAP-basierte Web Services werden seit jeher einheitlich durch WSDL-Dokumente in einer für Menschen und Maschinen lesbaren Form beschrieben. Das WSDL-Dokument entspricht einem Vertrag, der zwischen API-Anbieter und API-Nutzer geschossen wird. Ein modernes WSDL-Pendant speziell für REST-APIs ist RAML. Obwohl diese Open-Source-Beschreibungssprache ursprünglich für REST-APIs entworfen wurde, eignet sie sich ebenfalls für APIs, die nicht alle REST-Anforderungen erfüllen. Auf diese Weise kommt sie für viele Web-APIs, die auf Ressourcen, Methoden und HTTP aufbauen, in Betracht.

Alternative Formate für REST-Metadaten

Die Notwendigkeit, auch REST-APIs einheitlich zu beschreiben und zu dokumentieren, ist nicht neu. API-Designer können heute zwischen unterschiedlichen Formaten für REST-Metadaten wählen. Eine solche Dokumentation hat in der Regel folgende Inhalte: Entry Points, Ressourcenpfade, eine Beschreibung der Methoden zum Zugriff auf die Ressourcen (GET, PUT etc.) samt Methodenparameter. Zur Dokumentation gehören auch die unterstützten Formate (JSON-Schema, XML-Schema) und die Status- und Fehlercodes. Bevor wir uns RAML im Detail ansehen, folgt in diesem Abschnitt ein Überblick über die wichtigsten Alternativen:

Swagger ist ein Format zur Beschreibung von REST-APIs, das von Reverb entwickelt wird. Es gehört neben WADL laut Google Trends zu den populärsten Technologien in dieser Liste. Zu Swagger gehört Swagger UI, eine Benutzeroberfläche auf Basis von HTML/JavaScript. Swagger UI ist Open Source und dient sowohl zur Dokumentation als auch zur Ausführung von Ad-hoc-Tests. Die eigentliche Stärke von Swagger ist sein umfassendes Ökosystem auf GitHub. Hierzu zählen Codegeneratoren für unterschiedliche Sprachen. Speziell für Java gibt es Annotationen. Ein gutes Beispiel ist der Swagger Petstore.

WADL wurde bereits 2009 von Sun Microsystems dem W3C vorgeschlagen, aber nie als Standard verabschiedet. Zur Codegenerierung, Testausführung und Dokumentation existieren Werkzeuge wie beispielsweise Jersey und Apache CXF. Ein anschauliches Beispiel bietet die Jersey-Dokumentation.

ioDocs stammt von Mashary. Die Open-Source-Technologie basiert auf einem JSON-Format zur Beschreibung von REST-APIs. Eine interaktive API-Dokumentation, die sich zum Lernen und Ausprobieren des API eignet, kann auf Basis der Beschreibung generiert werden. Obwohl Teile von ioDocs Open Source sind, profitiert man von vielen Funktionen nur durch die Benutzung der Mashery-Plattform. Einen guten Eindruck kann man sich mit diesem Beispiel verschaffen.

API Blueprint wurde von Apiary entwickelt. Das Markdown-Format ist gut dokumentiert, und der dazugehörige Parser ist Open Source. Wenn ein REST-API erst einmal mit API Blueprint beschrieben ist, können mit der apiary.io-Plattform Server-Mocks und eine Dokumentation generiert werden. Der Mehrwert dieses Formats ergibt sich daher vor allem aus der Kombination mit der apiary.io-Plattform. Ein gutes Beispiel bietet der Blog von Kreuzwerker.

RAML – eine benutzerzentrierte Sprache

Nach dem Überblick über die bekanntesten Formate zur Beschreibung von REST-APIs stellt sich die Frage, was an RAML anders oder besonders ist. RAML ist der jüngste Spross in dieser Reihe, und seine Entwickler von Mulesoft profitierten viel von den Vorgängern WADL und Swagger.

RAML verwendet zur Beschreibung der APIs das Dateiformat YAML, welches verschiedene Vorteile mit sich bringt: Es ist beispielsweise gut lesbar, weil es relativ wenige Klammern und andere strukturbedingte Zeichen (z. B. Doppelpunkte) enthält. Außerdem können mit YAML sauber Hierarchien abgebildet werden. Eine API-Beschreibung beginnt stets mit der verwendeten RAML-Version, gefolgt von weiteren Basisattributen wie Titel, Basis-URI und API-Version. Zusätzlich kann auch eine Dokumentation mit einzelnen Kapiteln angegeben werden:

#%RAML 0.8
title: Example HTTP JSON API
baseUri: http://localhost:8080/{version}
version: v1

Danach folgen in der API-Beschreibung die Pfade zu den Ressourcen, wie beispielsweise /articles. Die nächste Ebene bilden die HTTP-Methoden. Auf jeder Ebene können zu einer Ressource so viele Methoden wie notwendig hinzugefügt werden, aber jede Methode kann nur einmal pro Ebene verwendet werden:

/articles:
 get:
 put:
 post:
 /{articleId}:
  get:

In diesem Beispiel können Benutzer des API einen oder mehrere Artikel abrufen (get), einen Artikel aktualisieren (put) oder neu hinzufügen (post). Neben den genannten Methoden können auch URI-Parameter wie /articles/{articleId} verwendet werden. Mit articleId können einzelne Artikel mithilfe ihrer ID referenziert werden.

Für die einzelnen Methoden können Query-Parameter definiert werden. Hierzu gehören u. a. der Parametername, Typ, eine Beschreibung und ein Beispiel. Um derartige Definitionen ohne Wiederholung für mehrere Methoden nutzen zu können, bietet RAML so genannte Traits. Wiederkehrende Muster können in Form von Traits organisiert und an verschiedenen Stellen mit dem Schlüsselwort is referenziert werden. Auf diese Weise werden unnötige Duplizierungen vermieden. Eine andere Möglichkeit zur Strukturierung und Wiederverwendung bieten Includes (!include). Mit dieser Technik können Definitionen in andere Dateien ausgelagert werden. In Listing 1 wurden die Query-Parameter mit dem Trait searchable modelliert.

Listing 1

#%RAML 0.8
title: Example API
baseUri: http://localhost:8080/{version}
version: v1

traits:
 - searchable:
    queryParameters:
     author:
      type: string
      description: The author's name of the articles to search.
      example: Kai
      required: false

/articles:
 description: Collection of Java articles
 get:
  is: [
       searchable
      ]
  responses:
   200:
    body:
     application/json:
      schema: |
       {
         "type":"array",
         "items": [
         {
          "type":"object",
          "properties":
           {
            "id": { "type": "integer" },
            "content": { "type": "string" }
           }
         }
        ]
       }

JSON-Schema

RAML macht sich die Vorteile eines anderen Projekts zu eigen und ermöglicht die Definition der JSON-Rückgabewerte mit Schemata. Diese JSON-Schemata sind vergleichbar mit XSD für XML, welche sicherstellen, dass die Daten nicht nur wohlgeformt sind, sondern auch valide. Das bedeutet, dass die Datenstruktur nicht beliebig variieren darf, sondern eine bestimmte Form einhalten muss. Die in RAML verwendeten JSON-Schemata können direkt in der RAML-Datei oder in separaten YAML-Dateien abgelegt werden. Listing 2 zeigt, wie einfach externe Schemata inkludiert und referenziert werden können. Alternativ zu den JSON-Schemata können auch JSON-Beispiele zur Beschreibung der Rückgabewerte angegeben werden.

Listing 2

schemas:
 - !include article-schema.yaml

/articles:
 get:
  responses:
   200:
    body:
     application/json:
      schema: articles

Resource Types

Traits und Includes wurden bereits als Techniken zur Strukturierung von RAML-Dokumenten vorgestellt. In diesem Zusammenhang dürfen die so genannten Resouce Types nicht fehlen. Betrachten wir dazu als Beispiel den Resource Type mySearchableCollection. Dieser Typ erhält nur die Methode GET und dazugehörige Query-Parameter zur Suche. Eine konkrete Ressource wie /articles könnte von mySearchableCollection erben und so dessen Eigenschaften übernehmen. Bei der Definition von Resource Types und Traits können auch Parameter eingesetzt werden, um diese flexibel anwendbar zu machen. Beispielsweise könnte man die Suchparameter und die Beschreibungstexte variabel gestalten.

Nach dieser Einführung in die Beschreibungssprache RAML soll nun gezeigt werden, wie die Definitionen praktisch angewandt werden können, um testgetrieben ein REST-API zu entwickeln.

API First Development

Mithilfe des RAML-Testers können leicht die technischen Voraussetzungen erfüllt werden, um ein REST-API gegen eine RAML-Definition automatisch mithilfe von JUnit-Tests zu validieren. Wie dies konkret erfolgen kann, zeigt das Beispiel in Listing 3. Der gezeigte JUnit-Test startet einen Spring Context und initiiert eine Instanz der Klasse MockMvc. Mithilfe dieses Test-Doubles kann das REST-API getestet werden. Das Ergebnis des API-Aufrufs kann in Form von ResultActions überprüft werden. Die ResultActions sind in ihrer Rolle durchaus vergleichbar mit Hamcrest Matchern. Beliebig viele dieser Objekte können mit der Methode andExpect() hinzugefügt werden, um das Ergebnis zu überprüfen. Dieser Mechanismus wird auch vom RAML-Tester ausgenutzt. Die RAML-Datei wird von der Klasse guru.nidi.ramltester.RamlDefinition eingelesen. Anschließend wird im Test mit den Methoden matches() bzw. aggegating() eine passende ResultAction erzeugt.

Listing 3

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = SpringRestSampleApplication.class)
public class ApiIntegrationTest {

  private static RamlDefinition api = RamlLoaders
    .fromClasspath(SpringRestSampleApplication.class)
    .load("api.raml")
    .assumingBaseUri("http://localhost:8080/v1");

  private static SimpleReportAggregator aggregator = new SimpleReportAggregator();

  @Autowired
  private WebApplicationContext context;

  private MockMvc mvc;

  @Before
  public void setUp() {
    mvc = MockMvcBuilders.webAppContextSetup(context).build();
  }

  @Test
  public void retrieveArticlesOf2ndPage() throws Exception {
    mvc.perform(
      get("/articles?page=1&size=2").accept( MediaType.parseMediaType("application/json")))
        .andExpect(api.matches().aggregating(aggregator))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$", hasSize(2)))
        .andDo(MockMvcResultHandlers.print());
  }
}

Dieses technische Setup dient einerseits dazu, die Korrektheit des REST-API sicherzustellen, andererseits ermöglicht es Test-driven Development (TDD). In diesem Fall ist sogar die Bezeichnung API First Development passend. Um beispielsweise die GET-Methode der Ressource /articles um eine Paging-Funktion zu erweitern, würde man zunächst das Design des API in der RAML-Definition anpassen. In diesem Schritt entscheidet man beispielsweise, wie man die Paging-Informationen einbauen würde:

  • Im URI-Pfad: /articles/page/0
  • In der URI-Query: /articles?page=0

Da jedoch eine Page keine Ressource im Sinne von REST darstellt, würde man sich vermutlich für die zweite Variante entscheiden. Im nächsten Schritt schreibt man einen neuen JUnit-Test, der die GET-Methode mit der neuen Paging-Funktion testet (z. B. /articles?page=1). Der Test schlägt fehl und die Implementierung kann entsprechend des Tests erweitert werden. Dieses Vorgehen könnte man fortsetzen und zum Beispiel die RAML-Definition um einen Query-Parameter für die Page-Größe erweitern. Der Pfad für den Test könnte dann /articles?page=1&size=2 lauten. Im letzten Schritt des TDD-Kreislaufs wird die Implementierung angepasst.

Werkzeugunterstützung

Am Ende noch der Hinweis auf die Werkzeuge, die die Arbeit mit RAML vereinfachen. Beispielsweise können mit raml2html aus RAML-Definitionen interaktive HTML-Dokumente generiert werden. Ein passendes Beispiel zeigt Abbildung 1. Der verwendete Generator steht unter MIT-Lizenz und basiert auf Node.js. Installation und Ausführung erfolgt durch

npm i -g raml2html
raml2html api.raml > api.html
Abb. 1: Generierte HTML-Dokumente auf Basis der RAML-Definitionen

Abb. 1: Generierte HTML-Dokumente auf Basis der RAML-Definitionen

Alternativ kann auch die Webapplikation RAML 2 HTML for PHP zur Publikation von API-Dokumentationen verwendet werden. Das ist vor allem für offene APIs sinnvoll, deren Dokumentation von einer Vielzahl unterschiedlicher Benutzer benötigt wird.

Zusammenfassung

Mit RAML steht für APIs auf Basis von JSON und HTTP eine leichtgewichtige Beschreibungssprache bereit, mit der APIs einheitlich und strukturiert dokumentiert werden können. Wiederkehrende Muster können mit parametrisierbaren Traits und Resource Types modelliert und an verschiedenen Stellen des API wiederverwendet werden. Durch die Verwendung von YAML sind die Beschreibungen leicht lesbar und Baumstrukturen können sauber abgebildet werden. Wie im Artikel gezeigt wurde, können diese API-Definitionen mit dem RAML-Tester beispielsweise in JUnit-Tests in Kombination mit Spring MVC überprüft werden. Diese Technologiekombination ermöglicht ebenfalls API First Development. Ein anderer Grund, der für den Einsatz von RAML spricht, sind die verschiedenen Werkzeuge, die die Arbeit mit RAML vereinfachen.

Aufmacherbild: Robot with application via Shutterstock / Urheberrecht: Kirill__M

Geschrieben von
Kai Spichale
Kai Spichale
Kai Spichale (@kspichale) beschäftigt sich leidenschaftlich seit mehr als 10 Jahren mit Softwarearchitekturen und sauberen Code. Für innoQ Deutschland GmbH arbeitet er als IT-Berater mit modernen Architekturansätzen, API-Design und NoSQL. Er ist regelmäßiger Autor in verschiedenen Fachmagazinen und Sprecher auf Konferenzen.
Kommentare

Hinterlasse einen Kommentar

1 Kommentar auf "Entwicklung und Dokumentation von REST-APIs"

avatar
400
  Subscribe  
Benachrichtige mich zu:
Peter
Gast

Kennt jemand ein „simples“ Java Annotation basiertes Doku-Tool ? Dinge with @Path, @PathParameter, @QueryParamter, etc sind ja meist eh schon im source code vorhanden. Idealerweise dazu noch eine class für standard Container, die das während runtime auswertet und HTMLformatiert ausgiebt. Ich habe was eigenes, würde aber gerne auf Erfahrung von anderen aufbauen.