Java EE 8, Security (JSR 375)


Für die Deklaration von Security brauchen wird xml-Dateien. Sei es die web.xml oder serverspezifische Konfigurationsdateien. So ist es lange Zeit gewesen. Bis jetzt. Java EE 8 weicht diese Feststellung nun auf und führt Interfaces ein, wie den HttpAuthenticationMechanism um die Deklaration dieser Regeln in den Code zu verlagern.

ee8security

Java EE 8 liefert neben dem angesprochenen Interface (und weiterer) auch gleich drei passende Implementierungen mit:

  • BasicAuthenticationMechanismDefinition
  • FormAuthenticationMechanismDefinition
  • CustomFormAuthenrticationMechanismDefinition

In diesem Beispiel widmen wir uns einem davon: FormAuthenticationMechanismDefinition. Die Idee ist einfach. Anstatt die Konfiguration in xml-Dateien vor zu nehmen, verwenden wir Annotationen um die Security-Regeln (an einer ApplicationScoped-Bean) zu deklarieren. Eine solche Deklaration könnte so aussehen:

@FormAuthenticationMechanismDefinition(
        loginToContinue = @LoginToContinue(
                loginPage = "/login.html",
                errorPage = "/error.html"))

Damit legen wir erst einmal nur fest wie der User seine Login-Informationen übergeben soll (nämlich über die login.html Datei). Ebenso spannend ist die Frage „Wie autorisiert unsere Anwendung einen User?“. Wer hätte es erwartet, auch hier liefert der JSR375 ein passendes Interface (IdentityStore) und entsprechende Implementierungen (DataBaseIdentityStoreDefinition, LdapIdentityStoreDefinition).

@DatabaseIdentityStoreDefinition(
        dataSourceLookup = "java:jboss/datasources/DemoDS",
        callerQuery = "select PASSWORD from USER where USERNAME=?",
        groupsQuery = "select GROUPNAME from USERGROUPS where USERNAME=?",
        hashAlgorithm = de.gedoplan.PlainTextPasswordHash.class
)
@ApplicationScoped
public class ApplicationSecurityConfig {}

Hier geben wir an welche Datasource und Queries verwendet werden sollen um einen Benutzer zu validieren und dessen Benutzergruppen aus zu lesen. Zusätzlich muss hier eine PasswordHash-Implementierung angegeben werden (javax.security.enterprise.identitystore.PasswordHash). Mit Pbkdf2PasswordHash wird eine Implementierung mitgeliefert, das Beispiel oben verwendet eine eigene Implementierung welche die Passwörter in Klartext in der Datenbank ablegt (don’t do this at home).

Das war es schon ( fast ) jetzt können wir unsere Resourcen mit entsprechenden Berechtigungen versehen. Hier in Kürze 3 Varianten:

Servlets:

@WebServlet(urlPatterns = "/servlet")
@ServletSecurity(@HttpConstraint(rolesAllowed = "ADMIN"))
public class DemoServlet extends HttpServlet{...}

JAX-RS, Rest Webservices

@Path("demo")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Stateless
public class DemoResource {

    @GET
    @PermitAll
    public DemoModel getDemo() {...}
    
    @GET
    @Path("admin")
    @RolesAllowed("ADMIN")
    public DemoModel getAdminDemo() {...}
}

Webseiten über web.xml (dann doch noch mal xml)

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Protected user resource/url</web-resource-name>
            <url-pattern>/secured/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>ADMIN</role-name>
            <role-name>USER</role-name>
        </auth-constraint>
    </security-constraint>

Das wars. Alles. Live. In Farbe:

https://github.com/GEDOPLAN/jee8-security

Anmerkung zur Verwendung von Wildfly:
Das gezeigte Feature muss durch die Aktivierung der Security-Domain „jaspitest“ erst aktiviert werden (ab Wildfly 10)
(in der jboss-web.xml<jboss-web> <security-domain>jaspitest </jboss-web>)
Hier auch eine Anleitung für andere Versionen: click

Advertisements

Generics, Type Erasure ausgetrickst

Generics sind eine feine Sache die in nahezu jeder Application anwendung finden. Die meisten Entwickler werden jedoch festgestellt haben das die generische Typinformation zur Laufzeit nicht mehr zur Verfügung stehen. Aber wie dann zur Laufzeit an den Typ gelangen?

„Type Erasure“ heißt das „Phänomen“ das aus abwärtskompatiblen Gründen fester Bestandteil des Java Compilers ist. Generische Informationen sind somit nur zur Compile-Zeit vorhanden und werden bei der Übersetzung in den ByteCode entfernt. Damit fällt eine Factory wir die folgende erst einmal weg:

public class Factory<T> {

public T create() {
       return new T(); // Type-Erasure in Action
}

Wir können natürlich nun die konkrete Klasse als zusätzlichen Parameter in eine solche Factory übergeben um auf Basis dieser neue Instanzen zu erzeugen. Das funktioniert, fühlt sich aber nach „doppeltem Aufwand“ an, da die konkrete Klasse zwei mal angegeben wird.

Ein kleine Schlupfloch gibt es aber: anonyme Klassen. Anders als „normale“ Klassen sind bei anonymen Klassen die generischen Informationen auch zur Laufzeit vorhanden.

In unserer Factory können wir dann per Reflection den generischen Typ ermitteln und damit z.B. eine entsprechende Instanz erzeugen.

public class Factory<T> {

    public T create() throws InstantiationException, IllegalAccessException {
        Type superClassType = this.getClass().getGenericSuperclass();
        Type tType = ((ParameterizedType) superClassType).getActualTypeArguments()[0];
        Class clazz = (Class) tType;
        
        return clazz.newInstance();
    }
}
//Type Erasure > Exception
Customer c = new Factory<Customer>().create();

//anonyme Klasse
Customer c = new Factory<Customer>(){}.create();

Angular, asyncrone Initialisierung

Es gibt Dinge die sollte man einfach nicht aufschieben, dazu zählen auch immer wieder diverse Schritte die vor dem Start der eigentlichen Anwendung nötig sind. Sei es eine externe Konfiguration zu laden oder den alten Login Status eines Benutzers zu prüfen. Angular bietet hier die Möglichkeit diese (oftmals asynchronen) Aufgaben per APP_INITIALIZER Multi-Provider zu realisieren

angular_init_demo

Für diesen Zweck bietet Angular einen Multi Provider (analog zu z.B. NG_VALIDATORS) an: APP_INITIALIZER der in unserem Modul mittels Provider-Deklaration registriert wird.

@NgModule({
  providers: [
    {
      provide: APP_INITIALIZER,
      multi: true,
      deps: [HttpClient],
      useFactory: initApplication
    },

Neben der provide-Token (APP_INITIALIZER) und der Angabe das es sich um einen Multi-Provider3 handelt (multi: true), müssen wir eventuell benötige Depedencies deklarieren die in unserem Beispiel unserer Factory-Funktion übergeben werden.

Bei der Factory handelt es sich um eine im Modul deklarierte Methode die eine neue Methode zurück liefern muss die wärend der Initialisierung aufgerufen wird.
Diese erzeugte Methode kann dann entweder synchron ablaufen oder aber ein Promise liefern auf dessen Abarbeitung gewartet wird (Achtung, auf die Rückkehr von Observables wird hier nicht gewartet).

export function initApplication(http: HttpClient) {
  return () => {
    return new Promise((resolve, reject) => {
      //make some init steps, load data for example
      http
        .get('https://jsonplaceholder.typicode.com/users/1')
        .delay(5000) //wait a little bit, just to enjoy the loading gif
        .subscribe(
          r => {
            //handle response
            console.log('Init 1');
            resolve(r);
          },
          e => {
            // dont forget to handle errors or user will only see a blank screen
            document.body.innerHTML = 'Schwerer Ausnahmefehler';
          }
        );
    });
  };
}

Die Anwendung lädt, führt dann erst die Initialisierung durch und startet dann wie gewohnt. Dadurch sind wir in der Lage im Vorfeld sicher zu stellen das benötigte Services bereits initialisiert sind oder die Anwendung korrekt konfiguriert wurde

Demo? Auf GitHub.

 

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!

 

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

Software-Architektur: Domain-Driven Design – ein Einstieg

Das Domain-Driven Design (DDD) wurde von E. Evans in seinem Buch Domain-Driven Design: Tackling Complexity in the Heart of Software eingeführt. Für mich hat die Beschäftigung mit dem DDD-Ansatz vor allen Dingen eine (Rück-) Besinnung auf die Konzepte der objektorientierten Programmierung zur Folge gehabt. Diese Aussage mag überraschend klingen, wenn man sich doch über viele Jahre mit der Programmiersprache Java und der Java EE Plattform beschäftigt hat. Wo liegt also das Problem, zumal Java doch eine objektorientierte Programmiersprache ist?

Zunächst rückt DDD mit drei Begriffen die eigentliche Fachlichkeit von Software in das Zentrum der Betrachtung.

Da ist zu aller erst die Domäne selbst. Dies ist der Ausschnitt der realen Welt, der Geschäftsbereich, das Aufgabenfeld usw. wofür ein Software-System realisiert wird. Als Beispiel mag ein Flight Information System (FIS) dienen. Hierin gibt es Aircrafts die viele Flights auf bestimmten Routes ausführen. Auf der anderen Seite gibt es Customer, die eine Booking für einen bestimmten Flight durchführen. Eine von einem Customer getätigte Booking kann aus mehreren BookingPositions je Passenger bestehen und wird durch eine Invoice abgerechnet. Offensichtlich liegt also einer Domäne in der Regel ein Datenmodell zugrunde. Darüber hinaus umfasst eine Domäne aber auch Verhalten, Prozessabläufe, Geschäftsregeln oder Ereignisse.

Um eine solche Domäne für die Software-Entwicklung greifbar zu machen, wird sie durch ein Domänen-Modell in einer abstrahierten Form beschrieben. Zur Beschreibung von Domänen-Modellen sind grafische Software-Spezifikationssprachen wie insbesondere die Unified Modeling Language (UML) oder spezielle textuelle Software-Spezifikationssprachen in Form einer Domain Specific Language (DSL) geeignet. Das Domänen-Modell spezifiziert die Gesamtheit von Daten, Verhalten, Prozessen, Regeln, Ereignissen usw., welche die Domäne ausmachen. Somit beinhaltet ein UML-Domänen-Modell nicht nur Klassen-Diagramme für das Datenmodell, sondern beispielsweise auch ein Zustands-Diagramm für den BookingState oder ein Aktivitäts-Diagramm für den Geschäftsprozess cancel flight.

Die implementierte Anwendung ist eine exakte Repräsentation des Domänen-Modells in Form von Source-Code. Damit dies gelingt, müssen für die Erstellung des Domänen-Modells und dessen Implementierung auf der einen Seite die Software-Entwickler und auf der anderen Seite die fachlichen Experten sehr eng und permanent zusammen arbeiten. Im Zuge dieser Zusammenarbeit entsteht nach und nach die Ubiquitous Language für die Domäne. Dies ist die allgegenwärtige fachliche Sprache, welche alle für die betrachtete Domäne relevanten Begriffe eindeutig definiert. Die Ubiquitous Language für das Flight Information System beinhaltet somit Begriffe wie order, departure time, reserve seat, offered price usw. Für ein Projekt-Team von Software-Entwicklern und fachlichen Experten gilt, dass die Ubiquitous Language „von jedem gesprochen“ und „von jedem eindeutig verstanden“ wird.

Das Resultat einer erfolgreichen Umsetzung des DDD-Ansatzes ist ein Software-System, welches das spezifizierte Domänen-Modell mit den Begriffen der Ubiquitous Language implementiert. Sowohl Modell als auch Implementierung der Domäne nutzen ausschließlich das Vokabular der Ubiquitous Language, deren Begriffe somit in UML-Diagrammen, DSL-Spezifikatinen, Test-Beschreibungen, Source-Code, Wiki-Dokumentation usw. einheitlich verwendet werden.

Wie sieht nun aber die konkrete Vorgehensweise des DDD für die Implementierung aus und warum spielt hierbei die Objektorientierung eine so wichtige Rolle?

Zur Klärung dieser Frage begebe ich mich auf eine Zeitreise zurück auf die Anfänge meines Programmierer-Daseins. Nach einem kurzen Intermezzo mit BASIC war Pascal die erste Programmiersprache, mit der ich mich ernsthaft auseinender gesetzt habe. Ein kleines Pascal-Beispiel deuten die folgenden Code-Ausschnitte an, nämlich die typische Vorgehensweise bei der prozeduralen Programmierung mit der Definition von Datenstrukturen und der Implementierung von Algorithmen darauf:


PROGRAM myapp;

TYPE
person = RECORD
name: STRING;
home: address;
END;
address = RECORD
...
END;

PROCEDURE foo;
VAR p: person;
a: address;
BEGIN
...
END {foo};

BEGIN
...
END {myapp}.

Nach vielen Jahren modularer Programmierung mit Modula-2 folgte dann der Übergang zur Objektorientierung mit Smalltalk. Ich lernte Sätze wie „alles ist ein Objekt“ und „ein Objekt kapselt Daten und das darauf definierte Verhalten ein“.

Mit dem Einsatz von Java und der Java EE Plattform im aktuellen Jahrtausend gelangen wir nun wieder in die Gegenwart. Wie dort ein leichtgewichtiges Java EE Programm, welches mit Maven gebaut wird, oftmals aussieht deuten die folgenden Code-Ausschnitte an:


<project ...>

<artifactId>myapp</artifactId>
<packaging>war</packaging>

@Entity
public class Person {

private String name;
private Address home;
----
@Embeddable
public class Address {
...

@RequestScoped
public class PersonService {

public void foo(Person p) {
Address a = p.getHome();

Vergleicht man nun die Beispiele in Pascal und Java EE, dann kann man sich fragen, was aus dem objektorientierten Konzept der Einkapselung von Daten und Verhalten geworden ist: Die anämische (blutleere) JPA-Entity-Klasse Person kapselt nur die Datenstruktur für Personen ein, d. h. außer Dingen wie einem Konstruktor, den standardmäßigen Getter-/Setter-Methoden oder trivialen Methoden wie z. B. toString() findet man dort kein fachliches Verhalten vor. Dieses wird stattdessen in einer separaten CDI-Service-Klasse PersonService implementiert, deren Methoden (= Algorithmen) auf den Personen-Objekten (= Datenstruktur) operieren.

Diese Art der Programmierung fühlt sich für mich inzwischen problematisch an.

Das DDD definiert nun eine Reihe von Building Blocks als die konstituierenden Elemente eines Software-Systems. Im Sinne der Objektorientierung kann man sich jeden Building Block als ein Entwurfsmuster für eine bestimmte Art von Klassen mit einer festgelegten Aufgabenstellung vorstellen. Innerhalb der Menge der Building Blocks spielt dabei das Entity eine zentrale Rolle.

Ein Entity repräsentiert ein Geschäftsobjekt im klassischen Sinn der Objektorientierung: Das Entity kapselt die benötigten Geschäftsdaten zusammen mit den darauf operierenden Methoden ein. Jedes Entity besitzt eine eindeutige und unveränderliche Objekt-Identifikation. Schließlich sind Entities im allgemeinen Verständnis persistente Objekte.

Die letztgenannte Eigenschaft legt die Implementierung von Entities im Sinne des DDD als JPA-Entity-Klassen nahe. Um jedoch auch reichhaltige Funktionalität in Form von Methoden in einer JPA-Entity-Klasse realisieren zu können, fehlt solchen Klassen die Möglichkeit, sich per CDI beliebige andere „Dienste“ in Form von entsprechenden Objekten injizieren zu lassen. Diese Injektions-Fähigkeit lässt sich jedoch durch die folgende Utility-Klasse leicht nachrüsten, wobei nur die Möglichkeiten von Standard Java EE (CDI 1.1) ausgenutzt werden (vereinfachte Darstellung):


public abstract class Injector {

public static void injectFields(Object entity) {

BeanManager beanManager = CDI.current().getBeanManager();
AnnotatedType annotatedType = beanManager.createAnnotatedType(entity.getClass());
InjectionTarget injectionTarget = beanManager.createInjectionTarget(annotatedType);
CreationalContext creationalContext = beanManager.createCreationalContext(null);
injectionTarget.inject(entity, creationalContext);

Das folgende Beispiel deutet eine Anwendung an, indem in der Entity Booking des Flight Information System ein „Dienst“ vom Typ BookingPositionRepository injiziert wird. Im Konstruktor der JPA-Entity-Klasse wird diese Injektion aufgelöst, so dass etwa innerhalb einer Methode der Klasse Booking eine zugeordnete BookingPosition persistiert werden kann:

@Entity
public class Booking {

@Inject
@Transient
BookingPositionRepository repositoryBookingPosition;

protected Booking() {
Injector.injectFields(this);
}

public void addPassenger(...) {
...
BookingPosition bookingPosition = this.factoryBookingPosition.create(...);
...
this.repositoryBookingPosition.persist(bookingPosition);
...

Durch diese Erweiterung erhält man die so genannten Rich Entities mit den folgenden Eigenschaften:

  • Einkapselung der persistenten Daten eines Geschäftsobjektes
  • Implementierung aller Methoden, die ausschließlich auf den eingekapselten Daten operieren
  • Implementierung von reichhaltigen Methoden vermöge von injizierten, beliebigen „Diensten“

Da nun vielfältige Geschäftslogik direkt in den Entities implementiert werden kann, erübrigt sich die Notwendigkeit, dass solche Funktionalität in mehr oder weniger willkürlich gewählten Service-Klassen implementiert werden muss, weil nur dort Injektionen mit @Inject möglich sind. Somit wird die Geschäftslogik ganz überwiegend direkt an den Geschäftsobjekten implementiert, wodurch die Geschäftslogik im gesamten Software-System einfach und schnell lokalisierbar bleibt.

Soweit ein erster Überblick über wesentliche Konzepte des Domain-Driven Design und die Realisierung von Rich Entities im Sinne der Objektorientierung. Die weiteren Building Blocks von DDD und den wichtigen Begriff des Bounded Context schauen wir uns in der Fortsetzung dieses Blogs zur Software-Architektur an.