Nachladen, bitte!

Lazy Loading im Kontext von REST und HATEOAS

Stefan Ullrich

© Shutterstock.com / MOLPIX

Im Artikel „Weckruf der Moderne“ habe ich eine RESTful-Servicearchitektur für die Hamburger Sparkasse vorgestellt. Im folgenden Artikel geht es um ein wichtiges Detail dieser Architektur: Wie können im Kontext von REST und HATEOAS Referenzen auf Geschäftsobjekte dargestellt, übertragen und nachgeladen werden?

Die Hamburger Sparkasse betreibt eine Vielzahl von IT-Systemen auf verschiedenen Plattformen in unterschiedlichen Technologien. Die Vielfalt und silohafte Gestalt dieser Systeme erforderte die Einführung einer serviceorientierten Architektur zur plattformübergreifenden Kommunikation und Darstellung von Geschäftsobjekten. Dies wurde mit dem Haspa-Service-Framework umgesetzt. Hierbei handelt es sich um eine Java-Enterprise-Schicht aus Adaptern zu den Backend-Systemen und Services mit REST-Schnittstellen für die Kommunikation untereinander und zum Intranetportal. Die Services mit ihren REST-Schnittstellen bilden eine ganzheitliche, ressourcenorientierte Darstellung der Geschäftsobjekte der Haspa. Durch die Abstraktion der Backend-Technologien „sprechen alle Services dieselbe Sprache“ und können beliebig kombiniert und bearbeitet werden. Da die Objektcluster schnell sehr groß werden können, ist eine intelligente und generische Möglichkeit zum Referenzieren und Nachladen von Objekten nötig. Dieser Artikel zeigt, wie das Haspa-Service-Framework dies im Kontext von REST und HATEOAS umgesetzt hat.

Von der vertikalen zur horizontalen Architektur

Für eine ganzheitliche (d. h. potenziell systemübergreifende) Abbildung von Geschäftsobjekten und -prozessen ist es unerlässlich, von den konkreten Backend-Systemen und deren Technologien zu abstrahieren: Erst wenn die plattformspezifischen Technologien durch eine Adapterschicht neutralisiert sind, können Services (und Prozesse) wahlfrei, systemübergreifend und technologieagnostisch darauf zugreifen. Adapter realisieren die Verfügbarkeit von Daten und Funktionen der Backend-Systeme, während Services die Fachlichkeit realisieren. Beide bedingen und benötigen einander, um zu einer flexiblen Gesamtarchitektur zu gelangen. Diese wiederum ist Voraussetzung für die Erstellung einer medienbruchfreien Benutzeroberfläche (Portal), die die vielen heterogenen Frontends der einzelnen Anwendungen ersetzen bzw. integrieren kann (Abb. 1).

Abb. 1: Adapter, Services und Portal

Abb. 1: Adapter, Services und Portal

Das Haspa-Service-Framework

Das Haspa-Service-Framework (HSF) ist ein Java-Enterprise-Toolkit, das die schnelle und einheitliche Produktion leichtgewichtiger Services ermöglicht (siehe: Java Magazin 5.14, Weckruf der Moderne). Neben der Vorstrukturierung der Komponenten durch Generierung (Maven Archetypes) werden die Entwickler von der Entwicklungsinfrastruktur (Maven-Projektstruktur, Basiskomponenten, Abhängigkeiten) und dem „Plumbing“ (Komponentenstruktur, Injection, JSON-Serialisierung, Integrationstests) entlastet. So können sie sich auf die fachliche Implementierung konzentrieren. Die Generierung unterstützt zusätzlich die Einheitlichkeit der Services, die von Haspa-Mitarbeitern gewartet und gepflegt werden müssen. Technologisch gesehen sind HSF-Services JEE-Komponenten, die dem Entity-Control-Boundary-Pattern folgen:

  • Entity: Das Package enthält das Datenmodell des Service. Datenmodellklassen sind einfache Beans, die maximal wiederverwendet werden. Sie sind sowohl Entities als auch Domänenklassen und Transferobjekte. Anpassungen der JSON-Repräsentation gegenüber der Entity können bei der Serialisierung dynamisch erfolgen, sodass spezielle Transferobjekte nur in begründeten Ausnahmefällen notwendig sind. Dies ist eine radikale Anwendung des DRY-Prinzips.
  • Control: Das Package enthält die Implementierung der Servicefunktionalität und alle weiteren dazu nötigen Klassen. Die Serviceimplementierung besteht aus (mindestens) einer Enterprise Java Bean, die in der Regel als „stateless no-interface Session Bean“ implementiert wird (das EJB-Protokoll wird nur zur internen Kommunikation benutzt). Außerdem enthält das Package unter Umständen Delegate-Klassen, die z. B. Adapterzugriffe kapseln oder bündeln.
  • Boundary: Das Package enthält REST-Ressourcen und damit die Remote-Schnittstelle des Service. Ausschließliche Aufgabe der Boundary ist die Bereitstellung des REST-API. Hier finden neben der JSON-Serialisierung/-Deserialisierung nur Aufrufe der Serviceimplementierung statt, jedoch keinerlei Geschäftslogik oder Validierung von Geschäftsobjekten.

HATEOAS

Hypermedia As The Engine Of Application State ist eine essenzielle Bedingung für jede REST-Architektur (siehe: Java Magazin 7.14, Fieldings Vermächtnis). Hypermedia sind verlinkte Objekte: Links sind URLs und URLs sind „die Primary Keys des Webs“ – sie definieren eine Netzwerklocation (oder REST-Ressource) eindeutig. Um Geschäftsobjekte durch URLs eindeutig referenzieren zu können, müssen diese als atomar – nicht veränderbar – betrachtet werden. Um dem Anspruch an atomare URLs zu genügen, müssen IDs von Geschäftsobjekten in ihrer Repräsentation zu URLs werden: Ein Service-Consumer kann mit einem Primary Key „245987245“ nichts anfangen, wohl aber mit dem Location-URL http://server/service/kunde/kunden/245987245. Ein solcher URL hat wiederum in der Entität Kunde nichts zu suchen. Im Sinne der Separation of Concerns gehört die Dekoration mit dem Locationattribut zur Boundary (REST-Schnittstelle) des Service und wird während der JSON-Serialisierung automatisch der Repräsentation hinzugefügt. Diese spezielle JSON-Serialisierung ermöglicht die generische Implementierung von Remote Lazy Loading über REST.

Objektreferenzen in der JSON-Repräsentation

Um aus einem Java-Objekt eine REST-Repräsentation zu machen, wird es zu JSON serialisiert. Im Haspa-Service-Framework bedient sich die Klasse JsonMapper der Serialisierungsfähigkeiten von Jackson und kümmert sich um die Erzeugung der Location-URLs. Jedes JSON-Objekt bekommt ein Locationattribut, dessen Wert dieser URL ist. Dazu muss der JsonMapper allerdings wissen, welches Attribut eines Objekts seine ID ist. Hier wird ein CoC-Pattern (Convention over Configuration) verwendet: Das ID-Attribut heißt entweder „id“ (Convention) oder ist mit der Annotation @HSFReferenceId (Configuration) annotiert.

Location-URLs werden aber nicht nur benötigt, um ein serialisiertes Objekt damit zu identifizieren, sondern auch, um ein Objekt zu referenzieren, das nicht serialisiert werden soll. Dieses Objekt wird mit der Annotation @HSFReference gekennzeichnet. Dessen Attribut path bezeichnet das Pfadelement des URI, das diese Unterressource identifiziert. Listing 1 zeigt ein Beispiel einer von der Konvention abweichenden ID und einer Referenz auf eine Unterressource.

Listing 1
public class Mitarbeiter { 
  @HSFReferenceId 
  private Long personalnummer;
  
  @HSFReference(path="foto") 
  private Mitarbeiterfoto foto; 
  ...
Listing 2
{
  "location" : "http://server/service/mitarbeiter/mitarbeiter/936099",
  "personalnummer" : 936099,
  "nachname" : "Ullrich",
  "vorname" : "Stefan",
  "email" : "stefan.ullrich@haspa.de",
  "foto" : {
      "location" : "http://server/service/mitarbeiter/mitarbeiter/936099/foto"
  }, 
  ...

Mit diesen Annotationen ergibt sich eine Serialisierung, wie sie in Listing 2 dargestellt ist.  Sowohl Mitarbeiter als auch Mitarbeiterfoto werden als JSON-Objekte mit dem Attribut location serialisiert. Dieses enthält den URL, unter dem das Objekt zu erreichen ist. Ein Service Consumer kann sich immer darauf verlassen, dass es zumindest dieses Attribut gibt, mit dessen Hilfe das Objekt nachgeladen werden kann. Bei Objekten, die mit @HSFReference annotiert sind, ist diese Referenz unter Umständen das einzige Attribut. Es besteht aber die Möglichkeit, einzelne Attribute des referenzierten Objekts mitserialisieren zu lassen, indem deren Namen in der Annotation genannt werden. Das bietet sich an, wenn eine Information des Objekts (z. B. der Name) im Frontend erscheinen soll, das Objekt selbst aber nicht benötigt wird. Es kann auch vorkommen, dass ein Objekt referenziert werden soll, das über seine ID identifiziert wird und einen absoluten Pfad hat (keine Unterressource). Möglicherweise hat dieses Objekt ein ID-Attribut, das nicht id heißt, und auch keine Annotation @HSFReferenceId. In diesem Fall kann man den Namen der abweichenden ID und ein absolute-Flag der Annotation @HSFReference mitgeben. Listing 3 zeigt ein Beispiel für eine solche Annotation mit dem resultierenden JSON.

Listing 3
public class Mitarbeiter { 
  ...  
  @HSFReference(path="teams", absolute=true, id="kos", additionalFields="name") 
  private Team team; 

{
  "location" : "http://server/service/mitarbeiter/mitarbeiter/936099",
  ...
  "team" : {
      "location" : "http://server/service/mitarbeiter/teams/672",
      "name" : "IT-Architektur"
  },
  ...

Die bisherige Beschreibung bezog sich auf Objektreferenzen. Es gibt aber noch zwei weitere Szenarien: Listenreferenzen und Listenelementereferenzen.

Listenreferenzen und Listenelementereferenzen

Listenreferenzen sind sinnvoll, wenn schon die Ermittlung der Listenelemente eine teure Operation ist und keine ihrer Eigenschaften benötigt werden. Da hier eine Liste (und nicht deren Elemente) referenziert wird, ist die abweichende ID von mitarbeiter nicht nötig (Listing 4).

Listing 4
  ...  
  @HSFReference(path="mitarbeiter") 
  private Set mitglieder; 

{
  "location" : "http://server/service/mitarbeiter/teams/672",
  ...
  "mitglieder" : {
      "location" : "http://server/service/mitarbeiter/teams/672/mitarbeiter"
  },
  ...

Wenn die Listenelemente zwar verfügbar, aber sehr groß sind, ist es unter Umständen wünschenswert, sie zu referenzieren, statt sie zu serialisieren. Hier wird eine Referenz auf jedes einzelne Listenelement erzeugt, wobei auch zusätzliche Attribute hinzugefügt werden können (Listing 5).

Listing 5
public class Team { 
  ...  
  @HSFReference(path="mitarbeiter", absolute=true, 
              additionalFields={"nameMitAnrede","funktion")) 
  private Set mitglieder; 

{
  "location" : "http://server/service/mitarbeiter/teams/672",
  ...
  "mitglieder" : {
      "location" : "http://server/service/mitarbeiter/teams/672/mitarbeiter",
      "list" : [ 
          {
    "location" : http://server/service/mitarbeiter/mitarbeiter/936099,
    "nameMitAnrede" : "Herr Stefan Ullrich",
    "funktion" : "Software-Architekt"
          }, { 
    "location" : http://server/service/mitarbeiter/mitarbeiter/825988, 
    ... 
          }
      ]  
  },
  ...

Die JSON-Repräsentation einer Liste ist das Array. HSF serialisiert eine Liste aber zu einem JSON-Objekt mit den Attributen location und list. Dies ist eine feste Konvention, sodass sich Service Consumer darauf verlassen können, das Array unter dem Attribut list zu finden.

Die Liste lässt sich mit Namen und Funktion der Teammitglieder darstellen und auf den Location-URL verlinken. Der Aufwand für die Serialisierung und der Umfang der HTTP-Response sind erheblich kleiner, als wenn jedes Teammitglied vollständig serialisiert worden wäre. Interessanterweise wird die abweichende ID des Objekts Mitarbeiter (personalnummer) der Annotation @HSFReference nicht als Attribut mitgegeben – das ist nicht nötig, da sie im Mitarbeiterobjekt durch die Annotation @HSFReferenceId identifiziert wird.

Aufmacherbild: Industrial crane loading Containers via Shutterstock.com / Urheberrecht: MOLPIX

Geschrieben von
Stefan Ullrich
Stefan Ullrich
Stefan Ullrich hat Informatik studiert und ist Sun Certified Enterprise Architect mit 17 Jahren Java-Erfahrung. Er hat eine RESTful SOA („ROA“) implementiert (siehe „Weckruf der Moderne“ im Java Magazin 5.2014) und diverse weitere Artikel im Java Magazin veröffentlicht.
Kommentare

Hinterlasse einen Kommentar

1 Kommentar auf "Lazy Loading im Kontext von REST und HATEOAS"

avatar
4000
  Subscribe  
Benachrichtige mich zu:
Oliver Gierke
Gast
Ein schöner Artikel. Es freu mich zu sehen, dass Hypermedia Elemente weiter Verbreitung finden. Ein paar Nachfragen hätte ich jedoch :). Vorab: ich bin Autor des REST Moduls des Spring Data Projektes was sich ähnlichen Themen wie im Artikel angesprochen widmet. 1. Warum kommt kein bereits verbreitetes Hypermedia Format zum Einsatz? Implizit einen neuen MediaType zu kreieren bindet die Clients sehr stark an die verwendete Servertechnologie. Eigentlich etwas, was man vermeiden möchte, oder? Ein Blick auf HAL oder Collection/JSON könnten hier lohnen. 2. Woher wissen die Clients, welche Properties links sind und welche nicht? Üblicherweise wird doch mit Linkrelationen gearbeitet.… Read more »