Höchster Reifegrad für REST mit HATEOAS

Seite 4: Frameworks, Beispielcode und Fazit

Inhaltsverzeichnis

Nachdem das passende Format für den Austausch der Links gefunden ist, beginnt die Suche nach geeigneten Frameworks, die das Format unterstützen. Derzeit existieren nur wenige Implementierungen. Spätestens, wenn Entwickler dynamisch Schemata und Links anpassen wollen, müssen sie selbst Hand anlegen. In Jersey gibt es eine gewisse Dynamik, die aber leider nur eingeschränkt brauchbar ist, da sie über Annotationen funktioniert, die in der Expression Language zu verfassen sind. Dadurch entsteht schwer wartbarer Code.

Die derzeitigen Standard-Frameworks in Java sind Jersey (als JAX-RS-Referenzimplementierung) und Spring MVC mit Spring HATEOAS. Jersey lässt sich mittlerweile durch ein Plug-in dazu bringen, ein JSON-Hyperschema in die Responses zu schreiben. Die Erweiterung trägt der Dynamik der erzeugten Links Rechnung, da Entwickler bei Linkerzeugung nur Optionals in der Hand hat. Jersey wertet standardmäßig bereits die JAX-RS-Annotationen zur Berechtigung aus. Entwickler können dem Framework weitere Annotationen bekanntmachen.

Zur Umsetzung des eingangs beschriebenen Anwendungsfalls mit HATEOAS müssen Entwickler zunächst eine Einsprungsressource erstellen und die jeweils richtigen Links ausliefern. Eine Implementierung dafür findet sich auf GitHub.

Schematische Darstellung der HATEOAS-Schnittstelle (Abb. 6)

Der Client überprüft von Anfang an dauerhaft, ob die Links vorhanden sind, sodass er generisch sein darf. Ein Beispiel für einen Java-Client findet sich auf GitHub.

Änderungen an der Struktur der Berechtigungen erfordern kein erneutes Release des Clients. Auch andere Faktoren wie dynamisches An- und Abschalten von Features und Änderungen in der Businesslogik lassen sich komplett am Server umsetzen, ohne die Clients anpassen zu müssen. Das klingt zunächst nach einem deutlichen Mehraufwand für den Serverentwickler, der sich allerdings meist in Grenzen hält. Die Ergänzung sorgt sogar dafür, dass der Einsatz von HATEOAS die Schnittstelle deutlich aufwertet.

Das folgende Beispiel zeigt anhand der Methode getStation(), wie leicht die Integration von HATEOAS mit dem Framework durchzuführen ist. Ausgangspunkt ist die Methode vor der Änderung:

@GET
@Produces(MediaType.APPLICATION_JSON)
public WithId<Station> getStation(@PathParam(STATION_ID)
UUID stationId) {
log.debug("getStation('{})'", stationId);
  return stationRepository.getStationById(stationId)
.getOrElseThrow(()
->new NotFoundException("Station with id "
+ stationId
+ " was not found"));
}

Zum Hinzufügen der Schemainformation existiert ein Wrapper-Typ namens ObjectWithSchema. Entwickler müssen die Antwort lediglich darin verpacken und mit den passenden Links anreichern:

@GET
@Produces(MediaType.APPLICATION_JSON)
public ObjectWithSchema<WithId<Station>> getStation(
@PathParam(STATION_ID) UUID stationId) {
log.debug("getStation('{})'", stationId);
  return stationRepository.getStationById(stationId)
.map(station -> hyperSchemaCreator.create(
station,
stationLinkCreator.createFor(baseUri,
station.id),
weatherLinkCreator.createForStation(baseUri,
station.id)
)
).getOrElseThrow(() ->
new NotFoundException("Station with id " +
stationId +
" was not found"));
}

Das Erzeugen der Links ist in eine separate *Creator-Klasse ausgelagert und lässt sich darin umfangreich testen. Der Erzeuger für einen Link auf eine Messstation sieht folgendermaßen aus:

public List<Link> createFor(URI baseUri, UUID stationId) {
return collect(stationLinkFactory.forCall(
baseUri,
Rel.SELF,
r -> r.getStation(stationId)),
stationLinkFactory.forCall(
baseUri,
Rel.DELETE,
r -> r.deleteStation(stationId)));
}

Mithilfe einer integrierten LinkFactory für die passende Ressource erwartet die Funktion lediglich die Basis-URI des Services, die Relation und ein Lambda mit dem gewünschten Ressourcenaufruf. Die Angabe von Argumenten beim Aufruf der Methode legt den erlaubten Wert für den jeweiligen Parameter auf den übergebenen Wert fest. Diese Werte werden dann in Schema und URI des erzeugten Links übernommen.

Darüber hinaus gibt es die Option, die erlaubten Werte für einen Parameter flexibel zu setzen. Bei Verwendung einer Enumeration übernimmt das System die erlaubte Werte. Der normale Aufruf der Methoden an den Ressourcenklassen gewährleistet die Unterstützung von IDEs. Entwickler können bei Änderungen an den Ressourcen erkennen, an welchen Stellen sie benötigt werden. Damit offenbart sich der Vorteil der gegenüber dem Client abgeschlossenen Serverdomäne.

Die Annotation @RolesAllowed(Roles.ADMIN) erlaubt ausschließlich der Admin-Rolle den Zugriff auf die Methode deleteStation(<id>). Um eine redundante Konfiguration von Zugriffsregeln zu vermeiden, gilt die Rolleneinschränkung auch für die Link-Generierung. Im konkreten Fall bedeutet das, dass ohne weitere Anpassungen nur Nutzer mit Admin-Rechten den Link mit der Relation delete erhalten. Ein komplexes Berechtigungsszenario lässt sich ebenfalls umsetzen, das ohne Redundanz sowohl für die Zugriffskontrolle als auch für die Beschreibung der möglichen Zugriffe über das Hyperschema wirksam ist.

Da die Generierung der Schemainformationen dynamisch erfolgt, lassen sich auch weitere Annotationen umsetzen. Mit deren Hilfe können Entwickler beispielsweise einzelne Ressourcenmethoden oder spezielle Datenfelder für einen bestimmten Nutzerkreis ein- oder ausblenden.

Mithilfe des beschriebenen Ansatzes lassen sich einfache und performante Unit-Tests erstellen. Die Verwendung des Jersey-Testframeworks ist dafür nicht erforderlich.

Die Vorteile für den Einsatz von HATEOAS sind somit auch beim konkreten Programmieren zu spüren und nicht nur abstrakt greifbar, was teilweise von Entwicklern am Server bemängelt wird. Vor allem die Abgeschlossenheit der Serverdomäne erlaubt eine Skalierung in mehreren Bereichen. Software lässt sich schneller fertigstellen und im Nachhinein ändern.

Nicht zu unterschätzen ist auch der verringerte Kommunikationsaufwand zwischen den Entwicklern von Client und Server. Die Teams tauschen sich nur noch über Statusübergänge aus, die uniform möglich sind. Fachliche Clientlibraries oder seitenlange Wikidokumentationen zu Statusübergängen können entfallen.

Schwierigkeiten liegen jedoch in der fehlenden Standardisierung und recht dünnen Frameworkunterstützung. Dieses Problem ist jedoch erkannt und wird zur Zeit aktiv behoben. Es steht also nichts mehr im Wege, heute schon von den genannten Vorteilen zu profitieren.

Jörg Adler
ist zur Zeit bei der Mercateo AG als Softwareentwickler tätig. Aktuelle Schwerpunkte sind REST-Schnittstellen und interne Systeme.

Andreas Würl
ist zur Zeit Senior Consultant bei der TNG Technology Consulting GmbH. Sein Schwerpunkte sind agile Softwareentwicklung und modernes Softwaredesign.

Literatur

  1. Eric Evans; Domain-Driven Design. Tackling Complexity in the Heart of Software; Addison-Wesley; 2003.

(rme)