TransferObjects generieren +

Für persistente Objekte (Hibernate-POJOs oder Entity-Beans) gilt: über Remote-Schnittstellen werden an Clients nur sogenannte TransferObjects weitergegeben. (J2EE-Pattern hierzu corej2eepatterns/TransferObject)

Komisch nur, dass man bei Anwendung dieses Pattern, welches zweifellos wichtig und richtig ist, massiv gegen ein anderes, grundlegendes Softwareentwicklungs-Gebot verstößt: DRY – don’t repeat yourself. Oft wird das persistente Modell quasi verdoppelt als TransferObject-Modell. Die TransferObjekte besitzen allenfalls einige Attribute weniger, bestehen nur aus Getter-Setter-Methoden, implementieren keine Businesslogik und weniger Relationen untereinander.

Wir wollen TransferObjects nutzen ohne DRY zu verletzen, daher bietet es sich an, die TransferObjects durch Codegenerierung zu erzeugen. Wie geht das auf einfache Weise?

- mit dem kleinen agimatec-Framework “annomark”. (siehe auch Annotieren und Generieren)

In der Praxis finden sich natürlich Alternativen, z.B. durch die Verwendung von Codegeneratoren (MDSD).

Nehmen wir jedoch an, es gibt kein UML-Modell der Klassen.

Nehmen wir auch an, wir wollen die TransferObjekte flexibel abwandeln, z.B.

  • andere Attributtypen als beim persistenten Objekt
  • Umbenennung von Attributen
  • Konvertierung zwischen Transfermodell und persistenter Klasse (in beide Richtungen), flexibel konfigurierbar
  • Wahlweises Weglassen einzelner Relationen und Attributen
  • “Flachklopfen”, d.h. Attribute von Childobjekten in ein flaches Transferobjekt hochziehen
  • Splitten von Attributen (d.h. das Gegenteil von “Flachklopfen”)
  • unterschiedliche Transferobjekte aus einer Klasse generieren (je nach Use-Case, z.B. Anzeigeobjekt, Bearbeitungsobjekt, …)
  • Aus Getter-Methods des persistenten Objekts Attribute im Transferobjekt erzeugen

Dazu einige Beispiele, wie annomark dies ermöglicht. Wer hier eigenen Bedarf sieht, kann sich gerne melden, denn wir würden das Framework, das sich leicht in ant und maven Projekte integrieren läßt, dann als OpenSource Projekt veröffentlichen. Jetzt aber mal ein paar kurze Beispiele:

/** persistent class PostOffice **/
@DTOs({@DTO(usage = "edit"), @DTO(usage = "view")})
public class PostOffice {
    @DTOAttribute(type="Long", property="postOfficeId")
    private long id;
    private int version;
    /** Bezeichnung der Filiale **/
    @DTOAttribute
    private String name;
    @DTOAttribute(usage="edit")
    private String description;
    @DTOAttribute
    private Address address;

    // Getters and Setters ...
}

Wie wird sich das generierte TransferObjekt von class PostOffice unterscheiden?

  • der Typ des “id” Attributes wird von primitive “long” auf die Wrapper-Class “Long” geändert (und kann damit null sein)
  • der Name des “id” Attributes wird “postOfficeId” sein
  • das Attribut “version” wird im TransferObjekt nicht enthalten sein, da es nicht annotiert ist
  • das Attribut “description” wird nur im TransferObjekt für Bearbeitung enthalten sein. Zur reinen Anzeige kann eine andere Transferklasse erzeugt werden, in der “description” fehlt.
  • die Beziehung zum “Address” Objekt wird in eine Beziehung zum TransferObjekt für Address gewandelt (sofern für Address ein eigenes Transferobjekt erzeugt wird)
  • Java-Doc Kommentare werden übernommen und zusätzliche generiert.

Was kommt heraus? (Beispiel: Bearbeitungs-Transferobjekt)

public class TransferPostOffice {
  /**
  * source: id
  **/
  private Long postOfficeId;
  /**
  * source: name
  * Bezeichnung der Filiale
  **/
  private String name;
  /**
  * source: description
  **/
  private String description;
  /**
  * source: address
  **/
  private TransferAddress address;

  // Getters and Setters aus Platzgründen weggelassen...
}

Weitere Möglichkeiten lassen sich am Source der @DTOAttribute-Annotation erahnen:

@Retention(RetentionPolicy.SOURCE)
@Target(value = {ElementType.FIELD, ElementType.METHOD})
public @interface DTOAttribute {
    // reference to DTO.usage (when multiple DTO annotations exist per class)
    String usage() default "";
    // Reflector.path to take the attribute of the pojo FROM
    String path() default "";
    // name of the pojo.property to write the value TO
    String property() default "";
    // for dozer-mapping: name of a Converter class
    String converter() default "";
    // for dozer-mapping: true when call-by-reference
    boolean copyByReference() default false;
    // for dozer-mapping: true when one-way
    boolean oneWay() default false;
    // the type of the property, default is the same type as origin's
    String type() default "";
    // iterate-type setter method
    String addMethod() default "";
}

Alles, was nicht über die Annotation eingestellt wird, kann direkt im freemarker-Template (30 Zeilen groß) geändert werden, welches die eigentliche Codegenerierung durchführt.

Aber, wie sieht der Aufruf aus (z.B. aus Maven2 heraus)?


  
  
  

Anstelle vieler Parameter ruft das Groovyscript “Launcher.groovy” die Generierung auf und gibt dabei weitere Parameter an:

import java.util.*;
import com.agimatec.annotations.jam.*;
import com.agimatec.annotations.*;
def dtoClasses = new HashSet();
dtoClasses.addAll(classes.findAll { it.getAnnotation(DTO.class) != null });
dtoClasses.addAll(classes.findAll { it.getAnnotation(DTOs.class) != null });
generator = new JAMDtoGenerator();
generator.setTemplateDir("annomark/templates");
generator.addInstruction("java-pojo", "target/generated-src", null)
    .setUsageQualifier("edit")
    .setPrefix("Transfer")
    .setSuffix(".java")
    .setDefaultPackage("com.agimatec.model.transfer");

Die Konvertierung zwischen Transferobjekt und Sourceobjekt erledigt Dozer. Die Konfiguration für Dozer generiert ein weiteres Template.

generator.addInstruction("dozer-mapping", "target/generated-resources", "dozerMapping-generated.xml")
    .setUsageQualifier("edit")
    .setPrefix("Transfer")
    .setSuffix(".xml")
    .setDefaultPackage("com.agimatec.model.transfer");

Unsere Metadaten zur Validierung sowie die View-Transferklassen generieren wir mit weiteren Template.

Über Meinungen hierzu freuen wir uns!

5 Responses to “TransferObjects generieren”

  1. Roman: ist das nicht eh auch ein Open Source-Kandidat?

  2. Ja und gerne, aber umso eher, wenn es explizit gewünscht wird.

  3. HI, ich halte rein gar nichts von den Transfer Objekten. Mich würden mal die Argumente interessieren warum ich nicht meine persistenten pojos über die remote schnittstelle verschicken darf, ehrlich ! Ich höre es mir gerne an. Für meinen Teil habe ich ein konsistentes domain modell, das sowhl server als auch clientseitig verwendet wird, nix transfer Object, IMHO völliger Blödsinn.

  4. Ich finde Transfer Objekte auch doof und wenn ich keine brauche, lasse ich sie gerne weg. Wenn ich sie brauche, generiere ich sie lieber, als dass ich sie manuell schreibe. Soweit sind wir uns sicher einig.
    Es gibt aber einige (teilweise zwingende) Situationen, in denen sie sich aufdrängen:
    - Weil Hibernate-Objekte bei Relationen Proxies enthalten, die sich jenseits der Remote-Schnittstelle nicht mehr nachladen/ansprechen lassen. (Und ich in der API klar dokumentieren will, welche Objekte zugänglich sind und welche nicht)
    - Weil ich eine GUI-Schicht nicht auf Objekte zugreifen lassen will, die direkt mit der Datenbank gekoppelt sind, um Änderungen nicht durchschlagen zu lassen oder eine für die GUI-Schicht einfachere Modellschicht zu haben.
    - Weil Performancegründe einer gemeinsamen Verwendung der Entities in allen Schichten entgegenstehen können und
    - ggf. auch Abhängigkeiten der persistenten Modellklassen nicht an die Client-API weitergereicht werden sollen.

    Trifft dies alles nicht zu oder ist das Modell so simpel / das Team so klein, dass die Arguemente nicht ins Gewicht fallen, dann verzichte auf ein separates Modell in der remote-API.

    Viele Grüße
    Roman

  5. [...] TransferObjects generieren [...]

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>