Richardson 1200x400

Das Richardson Maturity Modell und Restful APIs mit Java

Āhnlich der maslowschen Pyramide definiert das Richardson Maturity Model den Reifegrad von Services in Bezug auf seine Rest-Haftigkeit. Als wirklich Restful kann nur ein Service der Stufe 3 bezeichnet werden.

Not restful Image by Geek and Poke

Das Modell kennt die folgenden vier Stufen:

Richardson-Maturity-Modell

Stufen des Models

Level 0: - Restless

Martin Fowler hat Level0 auch schon als ‚Die Quelle der Pest‘ bezeichnet. Ich persönlich empfinde den Radfahrer auf der Autobahn als den sympathischeren Vergleich.

Services, die sich auf Stufe 0 wiederfinden, nutzen das HTTP nur als Transportprotokoll für RPC-Aufrufe. Die Kommunikation findet somit über eine Middleware statt, welche nur auf dem Web aufsetzt. Der Service ist daher nur eine Blackbox mit nicht adressierten Ressourcen. Die Kommunikation findet jeweils nur über eine URI statt. Meist funktioniert dies auch nur über die POST Methode. Ein typisches Beispiel sind SOAP Services über http oder XML-RPC.

Level 1 - Ressourcen

Ressourcen sind das zentrale Konzept von REST. Ein Service auf Stufe 1 gibt jeder identifizierbaren Ressource eine eigene URI. Die Kommunikation findet nicht wie bei Stufe 0 Services über eine URI statt, sondern jede Ressource kann über ihre eigene URI angesprochen und somit auch verlinkt werden.

Level 2 - http Methoden

Das HTTP Protokoll sieht in seinem Grundaufbau die Nutzung von Methoden für die Kommunikation vor. Neben elementaren Aufrufen für CRUD Stehen auch Methoden zur Optimierung der Kommunikation bereit.

  • GET – Abholen von Informationen, die durch eine URI identifiziert werden, in Form einer Repräsentation.
  • POST – Neu anlegen einer Ressourcen und für alle Zwecke in denen keine der anderen Methoden passt.
  • PUT – Aktualisieren einer Ressource.
  • HEAD – Abfragen von Metadaten (bspw. um Ressourcenstatus zu überprüfen).
  • DELETE – Löschen einer Ressource.
  • OPTIONS – Abfragen von Ressourcen Metadaten, um bspw. herauszufinden welche Methoden einer Ressource unterstützt.

Zusätzlich werden ab Level 2 typischerweise die HTTP-Statuscodes richtig eingesetzt um den Client über den Ressourcenstatus zu informieren. Bspw. sollte eine Response-Fehlermeldung keinen Statuscode 2xx zurückliefern, sondern 4xx.

Level 3 - Hypermedia

Hypermedia macht einen Service definitiv zum REST Web Service. Die Idee besteht darin, alle Inhalte (Ressourcen, bzw. deren Repräsentationen) in einer Site zu verknüpfen. Der Client benötigt keine Kenntnisse über weiterführende URLs sondern kann bequem Links folgen.

Es wird dabei in zwei Arten von Links unterschieden: Links die direkt zu anderen Ressourcen führen und Links die den Status einer Ressource ändern, bspw. durch die HTTP- Methoden PUT (updaten) oder DELETE (löschen).

Die Vorteile von Hypermedia liegen auf der Hand:

  • Entkopplung von Client und Server, da sich der Client meistens nicht anpassen muss, wenn sich serverseitige Änderungen ergeben.
  • Transparenz von Änderungen in der Ressourcenverteilung: Client kann Links folgen ohne die genaue Serverinfrastruktur zu kennen.
  • Serverseitig steuerbarer Anwendungsfluss: Server teilt mit welcher Aktionen möglich sind, der Client kann sich dynamisch danach richten.

Rest APIs, welche Hypermedia korrekt einsetzen, werden oft auch mit dem Schlagwort HATEOAS (Hypermeda as the engine of application state) beschrieben.

Datenformate

Während auf Level 0 als Datenformat vorwiegend XML zum Zug kommt, wird - je höher die Reife der API steigt - JSON mehr und mehr relevant. Die Wahl des gewünschten Formats sollte jedoch - wenn immer möglich - dem Client überlassen werden.

Wie erfolgt die Umsetzung in der Java EE Welt?

Die richtige Kombination aus JAXB und JAX-RS ermöglicht eine einfache und saubere Implementatation von Restful- APIs in der Java-Welt:

Level 1

Für die Implementationen von URIs rücken zwei JAX-RS Annotationen in den Vordergrund.

  • @Path Ermöglicht es, den Pfad einer Ressource zu definieren. Die Annotation kann dabei sowohl auf Stufe der Klasse als auch auf einer Methode erfolgen. Bei der Definition auf einer Methode könnende Platzhalter für die Ressourcen-Id oder weitere Variable Werte definiert werden.
  • @PathParam bei der Deklaration eines Methodenparameters blindes einenPlatzhalter aus der @Path Annotation an den entsprechenden Methodenparameter.

Beispiel

    @Path("{id}")
    public Exam getById(@PathParam("id") final String anId) {
        ...
    }

Level 2

Das binden von Java-Methoden HTTP Methoden erledigen ebenfalls Annotations aus JAX-RS.

@GET, @POST, @DELETE, @PUT, @OPTIONS und @HEAD bei der Methodensignatur erledigen diesen Part.

Beispiel:

    @GET
    @Path("{id}")
    public Exam getById(@PathParam("id") final String anId) {
        final UUID id = UUID.fromString(anId);
        return examAccess.read(id);
    }

Level 3

Für die Umsetzung von HATEOAS bietet Java keine Unterstützung. Hier bietet es sich an, ein eigens POJO mit den Anforderungen der API zu erstellen und in die auszuliefernden Entities zu integrieren.

JSON

Via JAXB geschieht das marshallen von JSON Darenformaten recht transparent für einen Java Entwickler. Alle Annotations, welche für XML in den Einsatz kommen, werden von der Laufzeitumgebung auch für JAXB korrekt interpretiert.

Es gibt jedoch einige kleine aber feine Unterschiede je nach eingesetzter JAXB-Runtime:

  • Bei XML-Attributen mit der @XmlAttribute-Annotation wird von der Referenzimplementation ein @ vor den Node-Namen gestellt. Eclipselink stellt diese Knoten analog wie Elemente dar.

Richardson Maturity Modell in der JEE Welt

package ch.helmchen.examdemo.entities;

import java.io.Serializable;
import java.util.Date;
import java.util.UUID;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlType(name = "Exam")
public class Exam implements Serializable {

    private final UUID id;
    @XmlAttribute
    @XmlJavaTypeAdapter(DateAdapter.class)
    private Date closingDate;
    @XmlElement
    private String subject;
    @XmlElement
    private Testee testee;
    @XmlElement
    private Integer score;

    public Exam(final UUID theId) {
        id = theId;
    }

    public Exam() {
        id = UUID.randomUUID();
    }

    public Date getClosingDate() {
        return closingDate;
    }

    public void setClosingDate(final Date aClosingDate) {
        closingDate = aClosingDate;
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String theSubject) {
        subject = theSubject;
    }

    public Testee getTestee() {
        return testee;
    }

    public void setTestee(final Testee aTestee) {
        testee = aTestee;
    }

    public Integer getScore() {
        return score;
    }

    public void setScore(final Integer aScore) {
        score = aScore;
    }
}
{{ message }}

{{ 'Comments are closed.' | trans }}