HATEOAS – Können REST-APIs Geschichten erzählen?

15.06.2022

Geschichten faszinieren, begeistern und bleiben im Kopf. Dabei decken sie die Bandbreite von der reinen Unterhaltung bis hin zum Wissenstransfer ab. Storytelling ist in aller Munde.

Das bringt mich als Entwickler zu der Frage: Können wir mit unseren REST APIs auch Storytelling betreiben und werden sie dadurch verständlicher? Vielleicht lassen sich ja mit Storytelling die Funktionsweisen der Schnittstellen besser vermitteln?

Mein erster direkter Kontakt mit dem Thema Storytelling liegt mittlerweile einige Jahre zurück. Es war während einer internen Konferenz. Ein Kollege hat das Thema vorgestellt. Seine Herzensangelegenheit. Alle Kollegen, die diesen Vortrag besucht hatten, waren begeistert und sprachen ununterbrochen davon. Leider hatte ich mich für einen anderen Vortag entschieden. Ein technischeren, denn wie sollte mir Storytelling als Entwickler weiterhelfen? Zum Glück wurde sein Vortrag aufgezeichnet. So verzauberte auch mich das Storytelling. Bis heute erinnere ich mich an diesen Vortrag. An wie viele Vorträge der letzten Jahre erinnern Sie sich noch?

Ist Storytelling mit REST APIs überhaupt möglich?

Die Anbindung einer REST API kann beliebig komplex sein. Es muss die Schnittstelle selbst sowie ihre Domäne verstanden werden. Die Einhaltung von Best Practices hilft uns, die Schnittstelle technisch schneller zu verstehen. Aber oftmals steckt der größere Aufwand im Verständnis der Domäne. Kann uns Storytelling dabei weiterhelfen?

Darstellung einer dynamischen Geschichte
Abbildung 1: Darstellung einer dynamischen Geschichte; Bildquelle: Schmidt Spiele „Das schwarze Auge“

Geschichten müssen nicht statisch sein, sondern können auch dynamisch sein, so wie unsere Schnittstellen. Beispiele dafür sind Computerspiele, die eine dynamische Storyline haben, aber auch Bücher. Als Kind hatte ich ein Detektiv-Buch, bei dem man am Ende des Absatzes selbst entscheiden konnte wie die Geschichte weiter geht – ähnlich zu der Abbildung 1. Somit konnte man den Geschichtsverlauf beeinflussen. Wichtig ist der rote Faden.

HATEOAS – Hypermedia as the Engine of Application State – kann bei REST APIs zum Storytelling verwendet werden. Es wird von Roy Fielding in seiner Promotion als wichtigste Eigenschaft einer REST API beschrieben. Bei HATEOAS sendet der Server zusätzlich zu den eigentlichen Daten Metainformationen für den Klient mit. Dadurch wird eine lose Kopplung zwischen dem Klienten und dem Server erreicht. Diese Metainformationen verwendet der Klient zur Navigation. Ist das nicht ähnlich zu meinem Detektivbuch? Die Schnittstelle ist das Buch, eine Ressource ist der Absatz der Geschichte und die Metainformationen, die Verweise wo die Geschichte weitergeht.
Die zusätzlichen Metainformationen helfen einerseits, die Domäne besser zu verstehen und andererseits muss der Klient die Domäne nicht so im Detail verstehen, wie der Server. Somit wird der Aufwand für die Anbindung verringert.

{
 "_links" : {
  "self" : {
   "href" : "https://my-service/v1/invoices/123456"
  },
  "createCreditNote" : {
   "href" : "https://my-service/v1/invoices/123456/credit-notes"
  }
},
  "invoiceId" : "2022-007",
  "orderId" : "3465756245",
  ...
}

Code Snippet 1: Antwort der Ressource “invoices“ mit verschiedenen Antwortoptionen

Geschichten aus REST APIs gehören zum Entwickler-Alltag

Vermutlich sind die Geschichten, die eine REST API erzählt, nicht so interessant, wie die eines Detektivs. Aber sie sind doch ein wichtiger Bestandteil unseres Entwickler-Alltags. In einem Projekt haben wir einen Microservice entwickelt, dessen (Kurz-)Geschichten sich um das Thema Rechnungen dreht. Die Informationen einer Rechnung sind sozusagen ein Absatz einer eigenständigen Geschichte. Je nach Inhalt der Geschichte gibt es verschiedene Pfade, diese weiterzuführen. Ist eine Rechnung also beispielsweise schon storniert, gibt es lediglich einen Verweis auf die Gutschrift. Andernfalls gäbe es einen Verweis zur Erstellung der Gutschrift. In Zukunft soll es aber noch weitere Handlungsstränge für die Rechnung geben. Dazu zählt beispielsweise die Rechnungskorrektur.

Das Codebeispiel zeigt eine beispielhafte Antwort. Unter dem Attribut „_links“ sind alle weiteren Handlungsstränge für die Rechnung dargestellt. In diesem Fall kann eine Gutschrift für diese Rechnung erstellt werden. Bei HATEOAS ist es auch üblich, dass jede Ressource eine Referenz auf sich selbst hat.

Die vorherrschende Meinung ist: Die Umsetzung von HATEOAS ist mit einem hohen Aufwand verbunden

Auf den ersten Blick erhöht sich der Aufwand durch die Umsetzung von HATEOAS. Der zusätzliche Aufwand ist aber geringer als man im ersten Augenblick vermutet. Besonders im Spring-Umfeld und unter der Nutzung von Spring HATEOAS.

import org.springframework.hateoas.RepresentationModel;

public class InvoiceDO extends RepresentationModel {
  private String invoiceId;
  private String orderId;

  // Getter and Setter

}

Code Snippet 2: Die einzige Änderung im Model ist die Vererbung von RepresentationModel.

@RestController@RequestMapping(value = "/v1/invoices", produces = MediaType.APPLICATION_JSON_VALUE)
public class InvoiceApiResource {

  // other methods and fields

  @GetMapping("/{invoiceNo}")
  public ResponseEntity getInvoice(
    @PathVariable("invoiceNo") @NotNull final Integer invoiceNo) {
   final var invoiceResult = invoicesBF.getInvoiceInformation(invoiceNo);
   if (invoiceResult.isSuccess()) {
    final var invoiceBE = invoiceResult.getValue();
    final var invoiceNo = invoiceBE.getId().intValue();
    final var invoiceDO = invoiceMapperBA.map(invoiceBE);

    final var doesCreditNoteExist =
      InvoiceTypeDO.INVOICE.equals(invoiceDO.getType())
&& invoicesBF.doesCreditNoteExistForInvoice(invoiceNo);

   invoiceDO
     .add(linkTo(methodOn(InvoiceApiResource.class).getInvoice(invoiceNo)).withSelfRel())
     .addIf(InvoiceTypeDO.INVOICE.equals(invoiceDO.getType()) && !doesCreditNoteExist,
     () -> linkTo(methodOn(InvoiceApiResource.class).createCreditNote(invoiceNo)).withRel(
     "createCreditNote"))
     .addIf(
     doesCreditNoteExist,
     () -> linkTo(methodOn(InvoiceApiResource.class).getCreditNotes(invoiceNo)).withRel(
     "creditNotes"))
    .addIf(
    InvoiceTypeDO.CREDIT_NOTE.equals(invoiceDO.getType()),
   () -> linkTo(methodOn(InvoiceApiResource.class).getInvoice(invoiceBE
   .getOriginId()
   .intValue())).withRel("originInvoice"));

return ResponseEntity.ok(invoiceDO);
  } else {
return ResponseEntity.notFound().build();
  }

}

@GetMapping("/{invoiceNo}/credit-notes")
public ResponseEntity getCreditNotes(@NotNull final Integer invoiceNo) { }

@PostMapping("/{invoiceNo}/credit-notes")
public ResponseEntity createCreditNote(@NotNull final Integer invoiceNo) { }

}

Code Snippet 3: Beispiel für einen RestController mit Fokus auf die Bestandteile, die für die Verwendung von HATEOAS benötigt sind

Der Code in Zeile 15 – 33 entsteht durch den Einsatz von HATEOAS. Zusätzlicher Code, der auch gewartet und verstanden werden muss. Allerdings ist hier der Vorteil, dass der Code von den Domänenexperten entwickelt wurde. Kein Klient muss diese Logik verstehen und selbst implementieren. Dadurch fällt diese Fehlerquelle weg. Sollte sich doch einmal ein Fehler eingeschlichen haben, so kann er an einer zentralen Stelle gefixt werden.

Fazit: HATEOAS ermöglicht Storytelling

REST APIs können Geschichten erzählen. Diese Geschichten helfen dem Anwender die Domäne besser zu verstehen. Nicht nur wir, sondern auch unser Kunde war begeistert davon. Er hat den Mehrwert erkannt und sich darüber gefreut, wie einfach wir seine Domäne zugänglich gemacht haben. Der befürchtete erhöhte Aufwand bei der Umsetzung war vernachlässigbar gering. Der zusätzliche Code funktioniert problemlos und musste nicht nochmals angepasst werden. Das ist vor allem auf die wirklich gute Umsetzung von Spring HATEOAS zurückzuführen. Spring HATEOAS hat uns überzeugt und wir können jedem nur empfehlen es selbst auszuprobieren .

Könnt ihr euch vorstellen, bei der nächsten Schnittstelle auch auf HATEOAS zu setzen? Oder warum nicht?

Zurück zur Übersicht

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*Pflichtfelder

*