adam bien's blog

No Duplication, No Decoupling - The Generic Data Transfer Object 📎

Lazy loading and binary dependencies are the main problem of direct exposure of persistent domain objects in stateless architectures. The user has know in advance, what path (subgraph) of lazy relations has to be loaded. The invocation of not yet loaded relations in a unmanaged entity will result in exceptions and errors.

Typesafe DTOs are often introduced - only to enforce the eager loading of the entities. Such DTOs are very similar - if not identical to the persistent entities - what causes repetition and actually decreases the maintainability. 

Furthermore a client is not always interested in a type safe structure, rather than in rich metadata and reflective APIs. A UI can be even partially generated from the DTO's meta data in that case. 

The solution is simple - a usual java.util.Map is a perfect tool for data transportation between layers. It was only wrapped in a GenericDTO for convenience:

public class GenericDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    private Map<String, Attribute> attributes = null;

    private Map<String, Set<GenericDTO>> relations = null;

    private String name = null;

    public GenericDTO(String name) {

        notNull(name, "The name of the DTO cannot be null...");

        this.attributes = new HashMap<String, Attribute>();

        this.relations = new HashMap<String, Set<GenericDTO>>();

        this.name = name;

    }

    public GenericDTO add(String name, Attribute attribute) {

        notNull(name, "Attribute name cannot be null");

        notNull(attribute, "Attribute with name: " + name + " is null!");

        this.attributes.put(name, attribute);

        return this;

    }


    public GenericDTO addString(String name, String value) {

        notNull(name, "Attribute name cannot be null");

        notNull(value, "Attribute with name: " + name + " is null!");

        this.attributes.put(name, new StringType(null,value));

        return this;

    }

    //some book keeping methods omitted.

The GenericDTO will never change it structure - what makes your APIs binary compatible. This is only possible because, the GenericDTO's content and not the structure transports the type information. Your code will always compile - but not always run. With GenericDTO you, and not the compiler, are responsible for type checks. Furthermore you will have to give up auto-completion support in IDEs. 

Working with GenericDTOs requires lot of plumbing, but on the other hand, they can be easily constructed and read using the Java's reflection mechanism:

    @Test

    public void construction(){

         GenericDTO dto = null;

         String description = "description";

         String name = "name";

         String numberOfPages = "numberOfPages";

        try {

            dto = new GenericDTO("Book").

            addString(description, "Duke's Adventures").

            addString(name, "Java").

            addInt(numberOfPages, 10).

            validate();

        } catch (CompositeValidationException ex) {

            fail("Not excected " + ex);

        }

The GenericDTOs payload are not primitive types / wrappers, rather than implementations of the Attribute interface:

public interface Attribute<T> extends Serializable{

    public void validate() throws ValidationException;

    public void instantiateFromString(String content);

    public void setRegexp(String regExp);

    public void setId();

    public T getValue();

    public boolean isId();

}

All the concrete Attribute implementations are able to validate, as well as construct itself from a String. This can be leveraged to automatically create attributes from the user input in the UI. This flexibility comes with price: a GenericDTO needs an order of magnitude more instances at runtime, than an usual, type safe DTO.

By the way a GenericDTO: is a DTO, not a VO

The whole example with attribute implementations and unit tests was pushed into: http://kenai.com/projects/javaee-patterns/ 

[See Generic DTO, page 162 in "Real World Java EE Patterns Rethinking Best Practices" book for more in-depth performance / maintainability discussion]