Von Atlanta bis Zürich

Von Atlanta bis Zürich: Projektionen in modernen Enterprise-Anwendungen

Michael Schäfer, Achim Müller

© iStockphoto.com/pictafolio

Durch Projektionen lassen sich passgenau die Informationen bereitstellen, die in einer Enterprise-Anwendung benötigt werden. Das Flugplanungssystem einer Fluggesellschaft etwa ist eine typische Enterprise-Anwendung. So benötigen Mitarbeiter einer Fluggesellschaft, abhängig vom Anwendungsfall, ganz unterschiedliche Informationen, seien es detaillierte Angaben über eine Flugverbindung oder die zu einer Flugverbindung gehörenden Flüge. Wie können wir diese unterschiedlichen Projektionen bereitstellen? Im Folgenden zeigen wir, welche methodischen und technischen Probleme das aufwirft und wie sie sich lösen lassen.

Enterprise-Anwendungen sind Unternehmensanwendungen, die die Mitarbeiter einer Organisation bei der Ausführung ihrer täglichen Aufgaben unterstützen. Mit ihnen werden die Geschäftsprozesse in den Unternehmen optimiert. Die Anforderungen an Unternehmensanwendungen, insbesondere an die Usability, werden immer höher. Daher verwenden moderne Unternehmensanwendungen JavaScript-MVC-Frameworks wie AngularJS, Backbone.js, oder CompontentJS in Kombination mit HTML und CSS. Wir sprechen in diesem Fall von einer Rich-Client-Architektur (siehe: Engelschall, R. S.: „7-layer application“, Architecture Dissertation, 2014). Der Rich-Client besitzt im Gegensatz zum Thin-Client Teile der Präsentationslogik und Ressourcen. So kann er autonom Oberflächenelemente erzeugen und Dialogabläufe steuern. Die Usability der Anwendung erhöht sich entscheidend. Und der Server wird dann zu einem schlanken, einem Thin-Server, der im Wesentlichen für die Bereitstellung von Ressourcen verantwortlich ist. Er stellt die Ressourcen über einen CRUD-Service bereit, der über ein REST-API angesprochen wird (siehe: Webber, Jim: „REST in Practice“, O’Reilly, 2010 ). Bei der Ausführung des REST-API beschränken wir uns auf die REST-Prinzipien nach dem Maturity Model – Level 2 von Richardson (siehe: Richardson, Leonard: „The Maturity Heuristic“, QCon, 2008). Das heißt Hypermedia kommt nicht zum Einsatz sondern nur URIs, die über HTTP aufgerufen werden. In dem beschriebenen Anwendungskontext bringt Hypermedia für Unternehmensanwendungen keinen entscheidenden Mehrwert. Der Aufwand wäre nicht gerechtfertigt. Das haben Arbeiten wie die von Constantin Gerstberger („Towards a Data-Driven Enterprise Ressource“, Architecture Dissertation, 2014) und die Praxis gezeigt.

Starten wir das Gedankenexperiment. Eine Fluggesellschaft will wissen, welche Ziele sie von München aus anfliegt. Mit einem URI-Aufruf auf einem CRUD-Service ließen sich alle Flugverbindungen vom Server auf den Client übertragen. Schränken wir die Sicht dabei nicht ein, erhalten wir alle Informationen der Flugverbindungen einschließlich der bereits ausgeführten und geplanten Flüge. Im schlimmsten Fall kommen noch weitere mit der Flugverbindung verlinkte Ressourcen wie Personal, Besatzung oder Flugzeuge hinzu. Das wäre ein sehr einfaches REST-API, allerdings nicht sehr effizient. Der Anwender würde lange auf den Bildschirm starren, bis alle Ressourcen geladen sind. Keine gute Idee! Wie lassen sich also nur die Daten einer Ressource vom Server laden und übertragen, die auch wirklich benötigt werden? Wir brauchen eine definierte Sicht auf die Ressourcen auf dem Server. Die Lösung heißt: Projektionen.

Projektion in REST

Eine Projektion stellt eine andere Sicht auf eine bestehende Ressource dar. Sie enthält nur einen Teil der bestehenden Informationen.

Sucht der Anwender nach den Routen, die montags in München starten, bekommt er mit dem Aufruf einer URI und einer dafür definierten Projektion genau das, was er sucht. Die Bereitstellung von Projektionen wirft zwei Fragen auf: Wie lassen sich Projektionen sinnvoll auf Ressourcen abbilden? Und wie setzt man das technisch um?

Projektionen im DDD

Klar ist: Projektionen auf Ressourcen ergeben sich in erster Linie aus dem Anwendungsfall und der Fachlichkeit. Für die Modellierung der Projektionen liefert das Domain-driven Design (DDD) von Eric Evans ( „Domain-Driven Design“, Addison-Wesley, 2003) einen methodischen Rahmen. Wer mehr zu DDD wissen will, dem empfehlen wir „Domain-Driven Design Quickly“ (Avram, Abel: „Domain Driven Design Quickly“, C4Media Inc., 2006), eine kurze Zusammenfassung des Standardwerks von Eric Evans.

Warum haben wir uns für DDD entschieden? Zunächst ist DDD eine fundierte Softwaredesignmethode, die gerade in den letzten Jahren wieder an Bedeutung gewonnen hat. Außerdem lassen sich Projektionen mit den Domain-Pattern Entities, Aggregate, Aggregate Root und Repositories sehr gut definieren.

Abb. 1: Entities, Aggregates und Repositories

Abb. 1: Entities, Aggregates und Repositories

Abbildung 1 zeigt den Zusammenhang zwischen Entities, Aggregates und Repositories. Entities sind alle Subjekte, die unsere Beispielanwendung erfasst, z. B. die Route, den Flug oder den Mitarbeiter. Sie bilden die kleinste Einheit. Ein Aggregate bündelt inhaltlich zusammenhängende Entities, etwa Route und Flug. Ein konkreter Flug kann ohne die Route mit Flugnummer und Start- bzw. Zielflughafen nicht existieren. Die Entity Route nennt man Aggregate Root Entity, da von ihr alle anderen Entities abhängig sind. Über Repositories können Aggregate Root Entities erstellt, gelesen, verändert oder gelöscht werden.

Kommen wir nun zu der Methodik und definieren Regeln für die Abbildung von Projektionen in einem existierenden Domain-Model, wie in Abbildung 2 dargestellt.

Ressourcen in DDD

Gedanklich entspricht eine Ressource in REST einer Aggregate Root Entity in DDD. Eine Projektion entspricht somit einem Teil der verfügbaren Informationen einer Aggregate Root Entity.

Abb. 2: Projektionen in einem Aggregat

Abb. 2: Projektionen in einem Aggregat

Wir sehen zwei Aggregate A1 und A2. Die gestrichelten Linien definieren die Grenzen der Projektionen p1, p2, p3 und p4. Schauen wir uns zunächst die Projektion p1 aus dem Aggregate A1 an. Es handelt sich dabei um eine Projektion auf die Entity e1 der Aggregate Root Entity. Wir erhalten mit der Projektion p1 eine Sicht auf ausgewählte Attribute aus e1. Die Projektion p2 erweitert die Projektion p1 um eine Sicht auf die Entity e2. Wir haben mit p2 somit eine Projektion auf e1 und e2. Bei beiden Projektionen handelt es sich um eine Root Projection, da sie eine Sicht auf die Aggregate Root Entity bereitstellen. Die dritte Projektion p3 in Aggregate A1 liefert uns eine Sicht auf die Entity e3. Diese Projektion steht nur innerhalb des Aggregates zur Verfügung; wir sprechen von einer Anonym Projection. In Aggregate 2 haben wir nur eine Root Projection p4 definiert. Es ist erlaubt, dass eine Root Projection auf eine andere Root Projection in einem anderen Aggregate verweist. Es ist auch erlaubt, dass eine Anonym Projection auf eine Root Projection verweist.

Regeln für die Definition von Projektionen in DDD

  1. Eine Projektion ist eine definierte Menge von Entities innerhalb eines Aggregates.
  2. Es können beliebig viele Projektionen in einem Aggregate definiert werden.
  3. Enthält die Projektion die Aggregate Root Entity, sprechen wir von einer Root Projection.
  4. Ausschließlich Root Projections können über das Repository nach außen bereitgestellt werden.
  5. Eine Root Projection darf eine Root Projection aus einem anderen Aggregate referenzieren.

Die erste Frage, wie sich Projektionen sinnvoll auf Ressourcen abbilden lassen, ist damit geklärt. Unter Einhaltung der fünf Projektionsregeln haben wir ein strukturiertes Vorgehen auf Grundlage von DDD, mit dem wir Projektionen auf Aggregate Root Entities bzw. Ressourcen definieren können.

DTOs vs. Spring Data REST Projections

Projektionen werden in Enterprise-Anwendungen oft über das Data Transfer Object (DTO) Pattern abgebildet, bekannt aus dem Buch „Enterprise Pattern“ von Martin Fowler [8]. Ein DTO kapselt die Projektion in einer DTO-Klasse. Für die Konstruktion der DTO-Klasse benötigt er einen DTO-Assembler, der die Instanziierung und Befüllung der DTO-Klasse übernimmt. Das hat einen entscheidenden Nachteil. Für jede Projektion sind eine DTO-Klasse und ein DTO-Assembler zu implementieren. Das führt bei steigender Anzahl von Projektionen schnell zu einem enormen Wartungsaufwand und einer großen Menge an Boilerplate-Code. Spring Data REST (SDR) Projections hingegen werden, wie in Listing 1 zu sehen, über ein Interface definiert.

@Projection(name = "flightplan", types = {Route.class})
public interface FlightplanProjection {
  String getFlightNumber();
}

Das Interface wird mit der Annotation @Projection deklariert und damit zur Projektion. Die Daten werden über die Definition von Getter-Methoden innerhalb des Interface bereitgestellt. Die SDR-Projection ersetzt die DTO-Klasse. Die Implementierung einer DTO-Klasse entfällt. Und die Instanziierung und Befüllung der SDR-Projection erfolgt automatisch durch einen Proxy. Somit entfällt die Implementierung eines DTO-Assemblers. Wie können wir nun die SDR-Projections in unserer Enterprise-Anwendung verwenden?

SDR-Projections in Enterprise-Anwendungen

Projections sind ein Feature von Spring Data REST (SDR), das Mitte 2014 mit dem Release 2.1 eingeführt wurde. SDR stellt ein REST-API für einen einfachen Zugriff auf JPA, MongoDB, Gemfire, und Neo4j Repositories unter strenger Einhaltung aller REST-Prinzipien wie HTTP, URIs, Hypermedia über HAL und Caching. Mit SDR sind somit Hypermedia-Services nach dem Maturity Model – Level 3 von Richardson (Richardson, Leonard: „The Maturity Heuristic“, QCon, 2008) – möglich.

Da wir bei Enterprise-Anwendungen aber kein Hypermedia verwenden, stellt sich die Frage: Wie können wir Hypermedia in SDR abschalten? Das Abschalten von Hypermedia ist von SDR explizit nicht gewollt, da SDR ausschließlich für REST-Architekturen im Sinne von Roy Fielding ausgelegt ist. Wie also können wir das Feature dennoch verwenden? SDR stellt ein Common-Paket bereit, das von anderen Spring-Projekten verwendet werden kann. Listing 2 zeigt, wie einfach über einen Spring-MVC-Controller eine Projektion bereitgestellt werden kann.

@RestController
@RequestMapping("/routes")
public class RouteController {
  @Autowired private RouteRepository rr;
  @Autowired private ProjectionFactory pf;
  
  @RequestMapping("{id}/connection")
  public ResponseEntity getConnection(@PathVariable(value="id") Long id) {
    Route routes = routeRepository.findOne(id);
    CP cp =  pf.createProjection(CP.class, routes);
    return new ResponseEntity( cp,HttpStatus.OK);
  }

Die SDR-Projection CP muss dafür über eine Factory-Klasse manuell erstellt werden. Dazu ist eine ProjectionFactory notwendig, die über eine Bean Definition mit SpelAwareProxyProjectionFactory instanziiert und über @Autowired eingebunden wird. Die Projektion wird über die Methode createProjection() erzeugt.

Ran an den Code: Routenplanung für unsere Fluggesellschaft

Kommen wir zur konkreten Umsetzung. Für unsere Fluggesellschaft entwickeln wir ein Flugplanungssystem. Die Fluggesellschaft pflegt darin ihre Flugzeuge (Aircraft), Mitarbeiter (Employee) und Flugverbindungen (Route) ein. Sie kann dann auf den Routen konkrete Flüge (Flight) durchführen, denen sie bestimmte Mitarbeiter zuweist. Eine Route wird dabei durch die Flugnummer identifiziert. Sie besteht aus dem maximal dreistelligen Airline-Code, z. B. „LH = Lufthansa“ und einer bis zu vierstelligen Nummer, z. B. „LH7902“. Die Flugnummer ist außerdem für eine bestimmte Strecke zu einer bestimmten Uhrzeit unabhängig vom Wochentag immer gleich. Sie wird im Flugplan mit den Verkehrstagen aufgeführt, an denen die Route bedient wird. Wird eine Verbindung an einem Tag zweimal geflogen, dann wird eine zweite Flugnummer vergeben, und es handelt sich im Sinne unserer Definition um eine zweite Route. Die Verbindung aus Flugnummer und Datum stellt einen konkreten Flug auf der Route dar. So finden sich etwa im Flugplan der Lufthansa mit Abflughafen München die in Abbildung 3 zu sehenden Einträge.

Abb. 3: Ausschnitt aus dem Flugplan der Lufthansa

Abb. 3: Ausschnitt aus dem Flugplan der Lufthansa

Die Route „München – Houston“ wird täglich um 9:30 Uhr unter der Flugnummer „LH7902“ mit einem Flugzeug vom Typ B764 bedient, während die Route „München – Ibiza“ samstags (Tag 6) unter der Flugnummer „LH1602“ mit einem A320 geflogen wird. Da wir auch noch die Besatzung des Flugs planen wollen, enthält unser Flugplanungssystem in Abbildung 4 als konkrete Ausprägungen der Mitarbeiterrolle (Role) zusätzlich Piloten (Pilot) und Kabinenpersonal (CabinAttendant).

Abb. 4: Das Datenmodell als Graph

Abb. 4: Das Datenmodell als Graph

Auf Basis des Domain Model wünscht sich die Fluggesellschaft nun drei User Stories:

  1. Übersicht aller Routen
  2. Erzeugung eines Flugplans wie in Abbildung 3
  3. Mitarbeitereinsatzplanung mit der Detailansicht eines Flugs und der Crew

Der Sourcecode unseres Beispiels ist auf GitHub zu finden. Der Anwendungsrahmen wird denkbar einfach durch Spring Boot vorgegeben. Die Entities aus unserem Domain Model werden mit JPA-Annotationen versehen und nach Aggregates getrennt in verschiedenen Java-Packages abgelegt. So befinden sich im Employee-Package die Entities Employee, CabinAttendant, Pilot und Role, im Aircraft-Package nur die Entität Aircraft und im Route-Package die Entitäten Flight und Route. Für jedes Aggregate Root aus jedem Aggregate gibt es nun ein Repository AircraftRepository, EmployeeRepository und RouteRepository, welches sich um das Persistieren aller Entities im Aggregate kümmert.

User Story 1 : Übersicht aller Routen

Die Definition eines Repositorys ist mit Spring Data (SD) trivial und besteht nur aus einer Zeile. Wir leiten ein neues Interface RouteRepository vom SD-Interface CrudRepository ab, das auf die Aggregate Root Entity Route mit dem entsprechenden Schlüssel typisiert wird. Alle unsere Entities haben eine gemeinsame Basisklasse AbstractEntity, die einen technischen Schlüssel id vom Typ Long definiert:

public interface RouteRepository extends CrudRepository<Route, Long> {
}

Der Zugriff auf das Repository erfolgt über einen CRUD-Service, der über ein REST-API exponiert wird. Das REST-API wird, wie in Listing 3 gezeigt, über Spring MVC Controller realisiert.

@RestController
@RequestMapping("/routes")
public class RouteController {
  @Autowired
  private RouteRepository routeRepository;
  
  @RequestMapping("{id}")
  public ResponseEntity get(@PathVariable(value="id") Long id) {
    Route route = routeRepository.findOne( id);
    return new ResponseEntity(route,HttpStatus.OK);
  }

So erhalten wir unter http://localhost:8080/routes/{id} noch ohne Projection bereits eine Route. Inbegriffen sind alle Attribute einer Route. Die zugehörigen Flüge auf der Route usw. sind darin eingebettet. Neben der Tatsache, dass wir die Flüge in unserer ersten User Story gar nicht benötigen, stört auch die Darstellung der Uhrzeit als eigenständiges Objekt. Für die geforderte Übersicht aller Routen kommt nun unsere erste SDR-Projection zum Einsatz. Wir definieren ein neues Interface ConnectionProjection und annotieren dieses mit @Projection. Mit dem Attributwert name= connection legen wir den Namen der Projection fest und mit dem Attributwert types=Route.class die Entity, auf welche die Projektion angewandt werden soll.

Doch wie definieren wir nun, welche Felder in unserer Projektion angezeigt werden sollen? Wir listen einfach die gewünschten Getter-Methoden aus der Entity-Klasse auf: Flugnummer, Abflughafen, Zielflughafen und Uhrzeit. Die Schreibweise muss dabei genauestens eingehalten werden. Was uns in der Ausgabe noch stört, ist die unhandliche Darstellung der Uhrzeit als Objekt. Hier werden sich die Clientprogrammierer zu Recht beklagen, zumal Implementierungsdetails des Servers unnötigerweise veröffentlicht werden. Hier lässt sich Abhilfe schaffen, indem wir den entsprechenden Getter getDepartureTime() mit einer @Value-Annotation versehen. Der übergebene Wert benutzt die Spring Expression Language (SpEL). Mit dem Aufruf #{target.departureTime.toString()} formatieren wir die Uhrzeit in das Standardformat hh:mm. Die @Value-Annotation ist damit ein wertvolles Hilfsmittel, um Werte für die Projektion zur Laufzeit zu berechnen. Die fertige Projektion zeigt Listing 4.

@Projection(name = "connection", types = Route.class)
public interface ConnectionProjection {
  String getFlightNumber();
  String getDeparture();
  String getDestination();
  @Value("#{target.departureTime.toString()}")
  String getDepartureTime();
}

Der RouteController in Listing 5 ist entsprechend Listing 4 zu erweitern. Die Projection Connection Projection ist im Code für eine bessere Lesbarkeit mit CP abgekürzt.

 
@RequestMapping("{id}/connection")
public ResponseEntity getConnection(@PathVariable(value="id") Long id) {
  Route routes = routeRepository.findOne( id);
  CP connectionProjection = pf.createProjection(CP.class, routes);
  return new ResponseEntity< CP >(connectionProjection,HttpStatus.OK);
}

Die Projektion können wir nun aufrufen, indem wir den URI um den Path-Parameter connection ergänzen: http://localhost:8080/routes/{id}/connection (Listing 6).

Listing 6: Die Ausgabe der Connection-Projection (Auszug)
{
  "flightNumber" : "LH7902",
  "departure" : "MUC",
  "destination" : "IAH",
  "departureTime" : "09:30",
}

User Story 2: Erzeugung eines Flugplans

Kommen wir zu unserem zweiten Anwendungsfall: dem Flugplan. Wir benötigen als zusätzliche Felder die Flugtage der Verbindung und den Flugzeugtyp (Abb. 3). Die Flugtage werden am Beginn der Zeile durch eine Auflistung von Zahlen (1 = Montag etc.) oder ein X für tägliche Verbindungen angezeigt. Da die Darstellung Sache des Clients ist, beschränken wir uns im Server auf die Übergabe der Rohdaten: die einzelnen Wochentage, so wie wir die Informationen auch gespeichert haben. Erhält der Client ein Set mit allen Wochentagen, dann entscheidet er selbstständig über die Ausgabe des X. Um den Flugzeugtyp zu ermitteln, müssen wir die Aggregate Root Entity Aircraft mit in die Projektion aufnehmen.

Dazu definieren wir zunächst im Aircraft Aggregate eine Root Projection AircraftProjection nach dem gleichen Muster wie in Listing 4 und geben in der Projektion über die Getter-Methode getType() den Typ zurück. Diese Projektion verwenden wir nun innerhalb der Root Projection FlightPlanProjection (Listing 7).

 
@Projection(name = "flightplan", types = Route.class)
public interface FlightplanProjection extends ConnectionProjection {
  Set getScheduledWeekdays();
  @Value("#{@routeService.formatTime(target.arrivalTime)}")
  String getArrivalTime();
  AircraftProjection getAircraft() ;
}

Die Projektion FlightplanProjection erweitert über extends die Projektion ConnectionProjection. Es sind somit nicht mehr alle Getter-Methoden aus ConnectionProjection neu zu implementieren. Der Aufruf http://localhost:8080/routes/flightplan führt zur Ausgabe (Listing 8).

routes" : [ {
  "flightNumber" : "LH7902",
  "departure" : "MUC",
  "destination" : "IAH",
  "departureTime" : "09:30",
  "scheduledWeekdays" : 
  ["MONDAY", "TUESDAY", "WEDNESDAY", 
    "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY" ],
  "arrivalTime" : "14:00",
  "aircraft" : {
    "type" : "B764"
  },

Die Flightplan Projection zeigt noch ein weiteres interessantes Feature: Die Spring Expression Language erlaubt auch das Aufrufen einer Servicemethode, um den Wert für ein Feld in der Projection zu berechnen. Wenn wir mit der Standardformatierung für LocalTime nicht einverstanden sind, können wir die Formatierung mit dem Ausdruck („#{@routeService.formatTime(target.departureTime)} auch in eine Servicemethode auslagern. Dort könnten wir auch Berechnungen durchführen und so völlig neue Felder in die Projection einfügen, z. B. die Anzahl der Flüge auf einer Route etc.

Aber was machen wir, wenn wir den Flugplan nur für einen bestimmten Abflughafen ausgeben wollen? Hier kommt uns Spring Data zu Hilfe, und wir können unser Repository einfach durch Hinzufügen einer findBy-Methode erweitern, in diesem Falle findByDeparture (Listing 9).

public interface RouteRepository extends CrudRepository<Route, Long> {
  @EntityGraph("byDepature")
  @Query("select r from Route r where r.departure = :departure")
  public Iterable findByDeparture(@Param("departure") String eparture);
}

Damit das funktioniert, müssen wir unseren RouteController so erweitern, dass er den Suchparameter departure als Query-Parameter entgegennimmt und an das Repository übergibt. Der URI könnte dann wie folgt lauten: http://localhost:8080/routes/flightplan?departure=MUC.

Als Ergebnis erhalten wir alle Verbindungen für den Flughafen München. Listing 9 zeigt noch einen weiteren interessanten Aspekt. Mit der Annotation @EntityGraph aus JPA 2.1 steuern wir, dass nur die Daten aus der Datenbank gelesen werden, die wir für die Projektion wirklich benötigen.

User Story 3: Mitarbeitereinsatzplanung

Kommen wir zu unserem letzten Beispiel, der Mitarbeitereinsatzplanung. Hier schauen wir uns das Thema Vererbung näher an. Unsere Projection crew soll die Flüge auf einer Route inklusive der Mitarbeiter mit Namen und Rolle anzeigen. Listing 10 zeigt die Root Projection EmployeeProjection, die in die CrewProjection eingebunden wird.

@Projection(name = "employeeRole", types = Route.class)
public interface EmployeeProjection {
  @Value("#{target.firstName} #{target.lastName}")
  String getName();
  Role getRole();
  // Funktioniert nicht 
  // @Projection(types = { Role.class, CabinAttendant.class, Pilot.class})
  // public interface RoleProjection {
    // String getCertificateNumber();
  //}
}

Beim Aufruf der Projektion crew fällt auf, dass die Rolle getRole() mit den Attributen der Basisklasse und der konkreten Ausprägung der Kindklasse gerendert wird. Leider ist es nicht möglich, mithilfe einer weiteren Projection nur einzelne Attribute einer konkreten Kindklasse in die Ausgabe der Perspektive zu packen, z. B. die CertificateNumber des Piloten wie in Listing 10 auskommentiert. Das ist zwar grundsätzlich möglich, funktioniert aber nur bei homogenen Collections, d. h., alle Elemente sind Exemplare derselben Klasse. Listing 11 zeigt die Ausgabe der crew Projection mit einer inhomogenen Collection.

{
  "flightNumber" : "LH7902",
  "departure" : "MUC",
  "departureTime" : "09:30",
  "flights" : [ {
    "date" : "2015-09-23",
    "employees" : [ {
      "name" : "Fred Flieger",
      "role" : {
        "roleName" : "Pilot",
        "certificateNumber" : "RF775566734",
        "allowedAircrafts" : [ "A320", "B764" ]
      }
    }, {
      "name" : "Tom Purser",
      "role" : {
        "roleName" : "CabinAttendant",
        "rank" : 1
      }
    } ]
  },

Fazit

Mit der neuen Spring-IO-Plattform ist in Spring eine Fokussierung in Richtung Cloud, Microservices und Big Data zu beobachten. Diese Technologien haben sich im beschriebenen Enterprise-Umfeld bisher nicht durchgesetzt. Daher können aktuell viele Features nicht genutzt werden. Das zeigt sich auch bei Spring Data REST (SDR) Projections. In Enterprise-Anwendungen bringt Hypermedia und das HAL-Rendering keinen Mehrwert. Beide können in SDR aber nicht abgeschaltet werden. Die alternative Nutzung der SDR-Projections über Spring MVC Controller führt dazu, dass wichtige Features wie die Integration der SDR-Projections in URI-Query usw. nicht genutzt werden können.

Unser Beispiel zeigt allerdings das Potenzial, das SDR-Projections auch für Enterprise-Anwendung haben. Sie ermöglichen uns eine einfache Implementierung der Projektionen mithilfe des SDR-Common-Pakets und Spring MVC Controllern. Der Boilerplate-Code, den wir mit DTOs hätten, entfällt. So können wir Projections auch in Enterprise-Anwendungen ohne Hypermedia und HAL-Rendering verwenden.

Im Rahmen von Domain-driven Design (DDD) haben wir Regeln gefunden, SDR-Projections in einem Aggregate zu definieren. Damit haben wir ein strukturiertes Vorgehen, um SDR-Projections in Enterprise-Projekten zu realisieren.

Geschrieben von
Michael Schäfer
Michael Schäfer
Michael Schäfer arbeitet als Lead IT Consultant im Bereich Applied Technology Research bei der msg systems ag in München. Sein Schwerpunkt liegt auf dem Spring-Ökosystem. In diesem Bereich ist er in Projekten, als Trainer und Autor unterwegs. Aktuell schreibt er an einem shortcut zum Thema JPA-2.1-Persistence-Features.
Achim Müller
Achim Müller
Achim Müller ist als Lead IT Consultant im Bereich Applied Technology Research bei der msg-systems AG in München tätig. Er besitzt langjährige Erfahrung in der Entwicklung komplexer und hochwertiger Softwaresysteme, insbesondere mit Java EE und Spring-Architekturen. Aktuell interessiert ihn besonders das Design von REST-Schnittstellen für HTML5-Rich-Clients.
Kommentare

2
Hinterlasse einen Kommentar

avatar
4000
2 Kommentar Themen
0 Themen Antworten
0 Follower
 
Kommentar, auf das am meisten reagiert wurde
Beliebtestes Kommentar Thema
2 Kommentatoren
Achim MüllerJohannes Steinhäuser Letzte Kommentartoren
  Subscribe  
Benachrichtige mich zu:
Johannes Steinhäuser
Gast
Johannes Steinhäuser

Hallo, in Abb.2 über Projektionen, Aggregate und Entities sehe ich nur die Projektionen p1 bis p3. Im Text wird über die Projektionen p1 bis p4 gesprochen. Fazit: sehr interessanter Artikel, wenngleich nicht besonders gut redaktionell begleitet. Der Praxisbezug geht vollends verloren, da das Beispiel vom GitHub leider gar nicht funktioniert. Beim Paketieren mit Maven bricht der Vorgang mit einer ziemlich grundlegenden Fehlermeldung ab: Caused by: org.sonatype.aether.transfer.ArtifactNotFoundException: Failure to find org.springframework.integration:spring-integration-bom:pom:4.2.0.M2 in http://repo.springsource.org/snapshot was cached in the local repository, resolution will not be reattempted until the update interval of repository.springsource.snapshot has elapsed or updates are forced at org.sonatype.aether.impl.internal.DefaultUpdateCheckManager.newException(DefaultUpdateCheckManager.java:230) at org.sonatype.aether.impl.internal.DefaultUpdateCheckManager.checkArtifact(DefaultUpdateCheckManager.java:204) at… Read more »

Achim Müller
Gast
Achim Müller

Sehr geehrter Herr Steinhäuser,

das Problem haben wir Anfang September behoben, inzwischen sollte es funktionieren.

Mit freundlichen Grüßen
Achim Müller