JPA + Rest, JSON-B in Action

„JSR 367“ alias JSON-B soll es endlich richten: ein standardisiertes JSON-Binding, ähnlich der Verarbeitung von XML mit JAXB. Gerade bei der Arbeit mit Rest-Schnittstellen und JPA stolpert man immer wieder über die gleichen Probleme. Wir machen einen ganz kurzen Rundflug über JSON-B und schauen uns eine Möglichkeit die JSON-B Adapters zu verwenden um JPA-Relationen zu mappen.

Einfache Möglichkeiten in die JSON-Generierung ein zu greifen fehlten bisher im Standard. Anbieter wie z.B. Jackson waren hier bereit viel weiter. JSON-B, Standard JSON-Binding in Java EE 8, bringt nun einige Möglichkeiten in diesem Bereich mit.

Annotationen

  • @JsonbProperty, Überschreiben des JSON-Property
  • @JsonbTransient, Attribute ignorieren
  • @JsonbPropertyOrder, Sortierung der Attribute anpassen
  • @JsonbDateFormat, Format für Datum
  • @JsonbNumberFormat, Format für Zahlen

Adapter

Neben der Implementierung einer Low-Level Serialisierung (implements JsonbSerializer) gibt es die Möglichkeit einen Art Converter zu implementieren der für die Verarbeitung einzelner Attribute verwendet werden kann. Ein interessantes Szenario für diesen Fall ist z.B. Entitäten die per JPA-Relationen referenziert werden nur durch ihre IDs zu repräsentieren (anstatt durch das gesamte Objekt). Das minimiert nicht nur die zu übertragenden Daten sondern verhindert auch das Problem von „circular dependencies“ bei bidirektionalen Relationen. Ein solcher (generischer) Adapter könnte so aussehen:

public abstract class EntityAdapter<T extends JPAEntity> implements JsonbAdapter<List<T>, JsonArray>{

    @Inject
    private EntityManager em;

    private Class getTargetClass() {
        return (Class) ((ParameterizedType) getClass()
                .getGenericSuperclass()).getActualTypeArguments()[0];
    }

    public JsonArray adaptToJson(List orgnl) throws Exception {
        JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
        orgnl.stream().map(JPAEntity::getId).forEach(arrayBuilder::add);
        return arrayBuilder.build();
    }

    public List adaptFromJson(JsonArray adptd) throws Exception {
        List list = new ArrayList();

        adptd.stream().forEach(val -> {
            Integer id = ((JsonNumber) val).intValue();
            list.add(em.getReference(getTargetClass(), id));
        });

        return list;
    }

}

Das Vorgehen ist schnell erklärt. Die Liste der referenzierten Entitäten wird nur mit den IDs befüllt. Diese werden bei der Deserialisierung dazu verwendet JPA-Referenzen zu laden um die korrekten Relationen wieder her zu stellen.

jsonb_demo.png

Dieser Adpater kann nun in unserer Resource verwendet werden um die JSON-B Konfiguration zu erweitern

    @GET
    public Response getTalks() {
        JsonbConfig config = new JsonbConfig().withAdapters(new EntityAdapter() {});
        Jsonb jsonb = JsonbBuilder.create(config);

        List allTalks = this.talkRepository.findAll();
        return Response.ok(jsonb.toJson(allTalks)).build();
    }

(zu beachten ist die Verwendung der anonymen Klasse bei der Instanziierung des Adapters. Dies wird benötigt damit JSON-B den generischen Typ zur Laufzeit ermitteln zu kann)

Alternativ ließe sich ein Adapter auch fest per Annotation an der Relation definieren:

@JsonbTypeAdapter(SpeakerAdapter.class)

(stand heute gibt es hier jedoch keine Möglichkeit zusätzliche Parameter, wie den Ziel-Entity-Typ mit zu geben (im Beispiel oben durch Generics und Reflection gelöst). Demzufolge müsste hier ein konkreter Adapter pro Entity Klasse implementiert werden)

Keine Frage viele Bibliotheken zur JSON-Verarbeitung sind ein ganzes Stück weiter. Mit JSON-B geht Java EE 8 aber einen großen Schritt in die richtige Richtung.

Github? Klar!

 

Advertisements

JPA + REST, Jackson Resolver


JAX-RS macht das Erstellen von Webservices leicht. Innerhalb von Minuten ist die Businesslogik und JPA Entitäten über REST im JSON Format verfügbar. Aber Moment! LazyLoadException? Circular Dependencies? Schnell stellen wir fest das unsere komplexen JPA Entitäten eben nicht out of the box in ein JSON Format übertragen werden können wenn Relationen und Bidirektionale Verbindungen im Spiel sind. Die Verarbeitung solche Relationen ist dann oftmals mühsam und mit viel manuellen Aufwand verbunden wenn der Umweg über DTOs gegangen wird. Jackson (JSON Parser, Standardimplementierung z.B. im Wildfly) biete hier eine interessante Möglichkeit: JsonIdentityInfo mit eigenem Resolver.

Folgendes einfaches Datenmodel führt ohne Eingriff bereits zu besagten Problemen:

 

@Entity
public class Talk {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @NotNull
    @Size(min = 5)
    private String title;

    @ManyToMany(fetch = FetchType.EAGER)
    private List speakers;
}

@Entity
public class Speaker {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    private String firstname;

    private String lastname;

    @ManyToMany(mappedBy = "speakers", fetch = FetchType.EAGER)
    private List talks;

JSON kennt keine Referenzen und würde beim Verarbeiten dieser Entitäten in eine Endlosschleife münden (Talk > Speaker > Talk > Speaker…). Um dies zu verhindern gibt es eine ganze Reihe von Möglichleiten, z.B. die Verwendung von @JSONIgnore (Referenzen beim JSON erzeugen ignorieren) oder die Entwicklung von einem DTO. Auch Denkbar ist die Verwendung von Jacksons @JsonIdentityInfo Annotation:

    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
    @JsonIdentityReference(alwaysAsId = true)
    @ManyToMany(fetch = FetchType.EAGER)
    private List speakers = new ArrayList();

Dies führt dazu das Jackson diese Relation immer lediglich mit dem Feld „ID“ im JSON versieht:

{
„id“: 1,
„title“: „Power Workshop Java EE“,
„speakers“: [
1,3
],
„talkType“: „WORKSHOP“,
„duration“: 480
},

Diese Varianten haben bisher einen entscheidenden Nachteil: ein einfaches Ändern der Relationen ist ohne manuelle Schritte nicht möglich:

  • @JSONIgnore: würde sogar dazu führen das die Referenzen auf Speaker entfernt werden, da sie nicht im JSON geliefert werden und bei einem „merge“ = „null“ sind
  • DTO: manueller Aufbau der Entität und „merge“

Auch @JsonIdentityInfo hilft in diesem Fall noch nicht. Optional kann dieser Annotation aber eine „resolver“ übergeben werden, der für die Umwandlung in/von JSON sorgt und der in unserem Fall die entsprechende Entitäts-Referenz aus der Datenbank holt und diese korrekt zuweist:

    @JsonIdentityInfo(
         resolver = JPAResolver.class, 
         generator = ObjectIdGenerators.PropertyGenerator.class, 
         scope = Speaker.class, 
         property = "id"
     )

Der Resolver:

public class JPAResolver extends SimpleObjectIdResolver {

    private EntityManager em;

    public JPAResolver() {
        this.em = CDI.current().select(EntityManager.class).get();
    }

    @Override
    public void bindItem(IdKey id, Object pojo) {
        super.bindItem(id, pojo);
    }

    @Override
    public Object resolveId(IdKey id) {
        Object resolved = super.resolveId(id);
        if (resolved == null) {
            resolved = loadFromDatabase(id);
            bindItem(id, resolved);
        }

        return resolved;
    }

    private Object loadFromDatabase(IdKey idKey) {
        return this.em.getReference(idKey.scope, idKey.key);
    }

    @Override
    public ObjectIdResolver newForDeserialization(Object context) {
        return new JPAResolver();
    }

    @Override
    public boolean canUseFor(ObjectIdResolver resolverType) {
        return resolverType.getClass() == JPAResolver.class;
    }
}

Dieser muss noch (zugegeben etwas umständlich) dem Context hinzugefügt werden. Dies kann z.B. mittels JaxRS Provider geschehen mittels HandlerInstantiator . Die Registrierung ist im GitHub hier: de/gedoplan/jackson/system/JacksonJPAResolverProvider.javazu finden

Das ist charmant! Rest-Clients können nun nicht nur die Entität selber (Talk) verändern, sondern in einem Abwasch auch die Referenz (>Speaker) auf Basis der ID manipulieren. Da der Resolver über das scope-Attribut mit der Referenz-Entity-Klasse versehen ist kann er in dieser Form generisch für alle Referenzen verwendet werden. Cool.

Im nächsten Artikel werfen wir einen dann einen Blick auf JSON-B, dem neuen Standard für JSON in Java EE 8

 

Live. In Farbe. Zum ausprobieren. Auf GitHub.

 

Angular 5 Konverter

Ein sehr häufig vorkommendes Szenario bei der Arbeit mit Fomularen ist die Umwandlung von Werten, also die Konvertierung zwischen Formular- und Daten-Repräsentation. In Angular ist dies mit einer Direktive möglich die das Interface ControlValueAccessor implementiert.

Als Beispiel dient ein sehr simples Beispiel. In unserem Datenmodel werden Namen als separates Objekt mit den Attributen „firstname“ und „lastname“ abgelegt. Innerhalb unsere Formulares sollen diese Werte jedoch gemeinsam (mit Leerzeichen getrennt) eingegeben werden:

2018-06-12 13_49_09-AngularParserFormatter

Der Schlüsselpunkt ist das Interface ControlValueAccessor mit dessen Hilfe wir unsere eigene Zugriffsmethode auf Eingabefelder realisieren können (in diesem Beispiel verwenden wir es als Konverter, dieses Interface würde aber z.B. auch bei der Erstellung einer völlig eigenen Eingabekomponente verwendet werden).

@Directive({
  selector: '[nameConverter]',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => NameConverterDirective),
      multi: true
    }
  ]
})
export class NameConverterDirective implements ControlValueAccessor {...}

Hier deklarieren wir eine entsprechende Directive, die wir an die Liste der Angular zur Verfügung stehenden Value-Accessoren anhängen.

Zur Implememtierung müssen wir nun noch die entsprechenden Methoden implementieren, die im Kern diese beiden sind:

  • writeValue(value: any), vom Model in die View
  • HostListener, um auf Änderungen in der View zu reagieren
  writeValue(value: any) {
    let val = '';
    if (value && value.firstname && value.lastname) {
      const name: Name = value;
      val = name.firstname + ' ' + name.lastname;
    }
    this._renderer.setProperty(this._elementRef.nativeElement, 'value', val);
  }

  @HostListener('input', ['$event'])
  onInput(event: any) {
    const namesp = event.target.value.split(' ', 2);
    const targetValue = new Name(namesp[0], namesp.length == 1 ? '' : namesp[1]);
    this.onChangeMethod(targetValue);
  }

Live. In Farbe. Demo auf Github

Angular Animations – Frohe Weihnachten

ng-xmas-tree

Live und in Farbe unter: github/GEDOPLAN

Apache CompareToBuilder

Gleichheit von Objekten wird bekanntlich mittels „equals“ Methode implementiert in der fachliche Informationen heran gezogen werden um zu prüfen ob es sich bei zwei Objektinstanzen um ein und dasselbe fachliche Objekt handelt. Es mag aber nun Situationen geben in denen uns das nicht reicht. Denken wir an einen JUnit-Test oder ein Migrationsprogramm wo möglicherweise weitreichendere Prüfungen auf Gleichheit durchgeführt werden sollen. Die Apache Commons Lang Bibliothek hat da was in petto : der CompareToBuilder

Ich bin du?

Die einfachste Variante den CompareBuilder zu verwenden ist es über die Fluent-API alle Vergleichswerte über die „append“ Methode ein zu binden und am Ende mittels „toComparison“ Methode das entsprechende Ergebnis an zu fordern. Der CompareToBuilder führt dann null-Prüfungen durch und ruft für jedes übergeben Objekt die „equals“ Methode auf und liefert am Ende einen int-Wert zurück der die Gleichheit der beiden Objekte ausweist.

int cResultFirma = new CompareToBuilder()
.append(firma1.getFax(), firma2.getFax())
.append(firma1.getInternet(), firma2.getInternet())
.append(firma1.getMail(), firma2.getMail())
.append(firma1.getTelefon(), firma2.getTelefon())
.toComparison();

Weniger schreiben, mehr Reflection

Das gezeigte Beispiel ist für komplexe Objekte natürlich ein aufwendiges Unterfangen. Der CompareToBuilder bietet darüber hinaus aber noch eine statische Methode die es erlaubt solche Prüfungen per Reflection durchführen zu lassen. Dabei bietet die Methode auch die Möglichkeit bestimmte Felder nicht aus zu werten, transiente Felder zu ignorieren oder die Prüfung nicht auf alle Felder der Superklassen an zu wenden.


int cResultFirma = CompareToBuilder.reflectionCompare(
    firma1, firma2,   // zu vergleichende Objekte
    "id", "createTimestamp" // Felder die nicht geprüft werden sollen
);

Was auf den ersten Blick sehr einfach in der Verwendung aussieht ist allerdings leider auch in seiner Funktionalität sehr eingeschränkt. So stößt dieser Mechanismus schon auf Probleme wenn es sich bei den zu prüfenden Feldern um eine Collection von Objekten handelt. Auch ist es leider nicht möglich auf bestimmte Attribute mit einer separaten Prüfungen zu reagieren. So wäre es z.B. wünschenswert beim Vergleich unserer Firma mittels Reflection eine referenzierte Instanz „Adresse“ nicht mittels „equals“ prüfen zu lassen sondern auch hier per Reflection zu prüfen oder individuell zu prüfen. Das lässt sich somit leider nur über den Umweg realisieren solche Referenzen beim „reflectionCompare“ in die Ignore-Liste mit auf zu nehmen und diese separat zu prüfen.

Der CompareToBuilder aus der Bibliothek Apache Commons Lang ist ein sehr rudimentäres Hilfsmittel das uns hilft Vergleiche von Objekten an zu stellen. Wenn diese allerdings sehr komplexe Strukturen aufweisen, die vollständig geprüft werden sollen, stößt der CompareToBuilder sehr schnell an seine Grenzen

 

Spring Boot + DATA JPA + HATEOAS

Spring Boot bietet die Möglichkeit auf sehr einfachem Wege standalone Anwendungen zu schreiben, die keinerlei Applicationserver als Laufzeitumgebung benötigen. Als Erweiterungen existieren eine ganze Reihe von Möglichkeiten, darunter die Einbindung von JPA und Rest (unter Anwendung von HATEOAS). Dieser Beitrag fast das Projekt-Setup kurz zusammen und stellt die technischen Möglichkeiten vor Rest-Schnittstellen mittels Spring Data auf einfachem Wege bereitzustellen.

Aller Anfang ist leicht. Die Entwickler von Spring unterstützen neue Projekte in der Initialisierung neuer Projekte über ein Webangebot über das Basis-Projekte mit den gewünschten Features generiert werden können: https://start.spring.io/ .

Das pom.xml eines so erstellten Projektes könnte wie folgt aussehen:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.M3</version>
        <relativePath/>
    </parent>

    <dependencies>
        <!--JPA Support-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--Support für die Veröffentlichung unserer Repositories als REST-Schnittstelle-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <!--Laufzeitumgebung-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jersey</artifactId>
        </dependency>
        <!--Testing-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--Basis-Support für Webanwendungen-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--integrierte Datenbank-->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>@RepositoryRestResource
            <scope>runtime</scope>
        </dependency>
    </dependencies>

Gestartet wird das Projekt über eine klassische Main-Methode und beinhaltet neben unserer Anwendung auch einen entsprechenden WebServer und Datenbank. Die Konfiguration für diese Komponenten erfolgt in einer einzelnen Properties Datei (src/main/resources/application.properties) in der wir zum Beispiel wie im folgenden zu sehen die (standardmäßige) In-Memory H2 Datenbank auf eine File-basierte umstellen und JPA anweisen die benötigten Tabellen zu generieren.

application.properties

spring.jpa.hibernate.ddl-auto = create
spring.jpa.generate-ddl = true
spring.datasource.url=jdbc:h2:file:~/spring;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=test
spring.datasource.password=test
spring.datasource.driverClassName=org.h2.Driver

Start, per main(…)

Der Start übernimmt wie angesprochen eine simple main-Methode, die lediglich Spring dazu veranlasst die benötigten Container zu initialisieren. Darüber hinaus scannt dieser Prozess alle unterliegenden Packages nach Spring Komponenten und registriert z.B. unsere Rest-Resources und aktualisiert auf Basis unserer JPA-Entitäten die Datenbank.

SpringDemoApplication.java

@SpringBootApplication
public class SpringbootDemoApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringbootDemoApplication.class, args);
  }
}

Rest, ganz einfach

Spring bietet uns die Möglichkeit in vielen Situationen auf das implementieren einer separaten Rest-Resource-Klasse zu verzichten. Dank der Annotation „@RepositoryRestResource“ ist Spring in der Lage auf Basis seines CRUD-Basis Repositories alle Operationen direkt als Rest-Resource zur Verfügung zu stellen. Um dies zu realisieren reicht es ein entsprechendes Interface bereit zu stellen und das generische CrudRepository zu erweitern:

MessageRepository

@RepositoryRestResource(path = "msg", collectionResourceRel = "msg")
public interface MessageRepository extends CrudRepository<Message, Integer> {

}

Request: http://localhost:8080/msg

{
    "_embedded": {
        "msg": [
            {
                "date": "1970-01-01T00:00:00.000+0000",
                "message": "Hello World",
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/msg/1"
                    },
                    "message": {
                        "href": "http://localhost:8080/msg/1"
                    },
                    "user": {
                        "href": "http://localhost:8080/msg/1/user"
                    }
                }
            }]
    }
}

Dank unseres Projekt-Setups verwendet Spring per default ein HAL-Format um unsere Entität aus zu liefern (s. Blog Beitrag HATEOAS + HAL). Damit entfällt das lästige manuelle Handling Umwandeln von Referenzen auf andere Entitäten. Stattdessen fügt Spring entsprechende Links in die Response eins, über dessen URL der Client die referenzierten Daten bei Bedarf abholen kann.

Vom Suchen und Finden

Neben den einfachen Get-Anfragen die automatisch für uns generriert werden bietet Spring auch eine halbautomische Erzeugung von Query-Methoden an. So erübrigt sich das manuelle Schreiben von Methoden die lediglich simple Queries an die Datenbank übermitteln wenn wir uns auf die reine Definition solcher zusätzlichen Methoden im Resource-Interface stützen. Dies geschieht wahlweise durch eine Namens-Konvention oder durch die explizite Angabe einer JPQL-Query über Annotationen

  @RestResource(path = "searchMessage", rel = "searchMessage")
  List<Message> findByMessageIgnoreCaseContaining(@Param("msg") String message);
}

ermöglicht GET-Anfragen ala:

http://localhost:8080/msg/search/searchMessage?msg=Blank

Post it

Die automatische Erzeugung von Rest-Schnittstellen beschränkt sich dabei aber nicht nur auf GET-Anfragen. Auch das Speicher und Löschen von Objekten wird unterstützt. Sehr charmant dabei ist die erneute Verwendung von HAL um Referenzen zwischen unseren Entitäten vom Client aus fest zu legen.

Post Request

{
    "user": "http://localhost:8080/usr/2",
    "message": "Hello Blank",
    "date": 0
}

Dies führt nicht nur zu einer simplen Speicherung unseres Objektes sondern Spring fügt ganz automatisch, auf Basis der User-URL, die benötigte JPA-Referenz hinzu.

JAX-RS bietet uns eine sehr einfache, standatisierte Möglichkeit Rest-Schnittstellen bereit zu stellen. Dabei existieren jedoch sehr viel sich wiederholende Implemetierungen, welche die Entwicklungszeit erhöhen. Hier liefert Spring ( /Spring Data) eine extrem mächtige Möglichkeit unnötigen Ballast bei CRUD-Operationen aus der Anwendung fern zu halten.

Live? In Farbe? Auf Github !

Rest – HATEOAS + HAL

REST-Webservices sind wohl die am weitesten verbreitete Schnittstellentechnologie die im WEB zu finden ist. Dank JSON als Datenformat bietet REST eine zustandslose und leichtgewichtige Möglichkeit Daten zwischen Anwendungen aus zu tauschen. HATEOAS (Hypermedia as the Engine of Application State) ist nun im Grunde keine weitere Technologie, sondern ein Konzept wie eine REST-Schnittstelle aufgebaut werden sollte.

Der Grundgedanke hinter HATEOAS ist das Clients in der Lage sind die Funktionen der Anwendungs-API zu verwenden ohne das diese fest verdrahtet im Client-Code sein müssen. Vergleichbar ist dies mit der menschlichen Nutzung einer Webseite. Wir besuchen einen Internetauftritt, erhalten Informationen, aber auch weiterführende Links über die wir weitere Funktionen/Seiten abrufen können. Das Prinzip lässt sich nahezu eins zu eins auf HATEOAS anwenden. Anstatt lediglich die angeforderten Informationen in der Datenstruktur von JSON zu erhalten informiert uns die Anwendung ebenfalls über weiterführende Aktionen und Links.

Ein einfaches Beispiel ist z.B. die Abbildung von JPA Entitäten im JSON Format. Nehmen wir ein sehr einfaches Datenmodell aus zwei Entitäten: User und Message. Die Entitäten besitzen eine Bi-Direktionale Verbindung:

@Entity
public class Message {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;

  private Date date;

  private String message;

  @ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST })
  private User user;

------------------------------------------------------------------------

@Entity
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;

  private String username;

  @OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
  private List<Message> messages;

Bereits hier müssen wir uns Gedanken über die Umwandlung in ein JSON Format machen: ohne zusätzlichen Eingriff von unserer Seite könnten diese Entitäten bereits nicht in ein JSON Format umgewandelt werden, da die Bi-Direktionale Relation zu einer Endlosschleife bei der Umwandlung führen würde. Neben die Bildung von DTOs richten sich die Möglichkeiten um dieses Problem zu beheben nach dem verwendeten JSON Parser (Jackson bietet z.B. Annotationen an um Felder zu ignorieren, Sub-Views aus zu liefern,  solche Relationen zu kennzeichnen oder diese lediglich mit der entsprechenden ID aus zu liefern). Eine klassischer JSON Output für unser „Message“-Objekt könnte nach diesem Prinzip so aussehen:

{
     "date": "1970-01-01T00:00:00.000+0000",
     "message": "Hello World",
     "user": 2
}

Anstatt das Userobjekt mit aus zu liefern bietet das JSON hier nur die entsprechende ID an. Wenn der Client auch die Benutzer-Information benötigt muss dieser wissen das es sich bei dieser ID um eine User-ID handelt, zudem muss die entsprechende URL zum Abruf der User-Daten (z.B. “ http://…/user/2 „) im Client-Code festgelegt sein.

HAL

Zur Unterscheidung: HATEOAS ist der Architektur-Ansatz, HAL ist eine Spezifikation zur JSON-Syntax der Link-Relationen. Neben HAL gibt es noch einige Alternativen (Collection+JSON, Hydra…). HAL findet z.B. Verwendung in „Spring HATEOAS“. Mittels „HAL“ können die Informationen nun verlinkt werden und der Client damit in die Lage versetzt werden die benötigen zusätzlichen Informationen ab zu rufen, ohne die konkreten URLS selber zusammen zu setzen.

Unser Message-Objekt könnte mittels „HAL“ dann wie folgt aussehen:

{
    "date": "1970-01-01T00:00:00.000+0000",
    "message": "Hello World",
    "_links": {
        "self": {
            "href": "http://localhost:8080/msg/2"
        },
        "user": {
            "href": "http://localhost:8080/msg/2/user"
        }
    }
}

Header Links

Eine andere Möglichkeit besteht darin Links im HTTP Header zu verankert. Dies lässt sich auch mit JAX-RS direkt erledigen. Ein einfaches Beispiel um innerhalb der Rest-Methode einen Link der Response hinzuzufügen könnte so aussehen:

  @Context
  UriInfo uriInfo;

  @GET
  @Path("hal")
  public Response getHelloHAL() {
    DemoEntity demo = new DemoEntity(1, "Hello World");
    return Response.ok(demo).link(uriInfo.getBaseUri().resolve("login"), "login").build();
  }

Der Header wird dann um einen entsprechenden Eintrag erweitert und enthält den angegebenen Link:

...
link <http://localhost:8080/angular-jwt-1.0-SNAPSHOT/rest/login>; rel="login"
...

Im nächsten Artikel werfen wir einen Blick auf Spring-Boot und dessen Möglichkeiten HATEOAS und HAL mit sehr wenig Aufwand in der eigenen Anwendung zu verwenden.