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]


NEW online workshop: WebStandards Igniter (online)

Airport MUC workshops: Java EE 7: Bootstrap, Effective, Architectures, Web, React and Angular, Testing and Microservices

Podcast: airhacks.fm and newsletter: airhacks.news

A book about rethinking Java EE Patterns

Comments:

- "Your code will always compile - but not always run."

- "Working with GenericDTOs requires lot of plumbing..."

No need to say more! This reminds me of Struts' DynaActionForms and the long road we've taken since then. Do we really need to go back?

Posted by John on September 25, 2009 at 12:37 PM CEST #

@John,

I used this approach in the past for CRUD-use cases. It worked very well. However, it is not a general best practice (as all other patterns).

If you decided to use GDTOs / SDOs (Service Data Objects), you shouldn't touch them in the IDE, rather than populate them from the meta data / reflection and leverage fully the "Convention Over Configuration" principle,

thanks!,

adam

Posted by Adam Bien on September 25, 2009 at 12:52 PM CEST #

I was wondering what your experience with generic DTOs is. Currently I'm in a project where a new project lead has stepped in and started to make this pattern a general pattern to be used everywhere where a client (flex, java, etc.) would communicate using generic DTOs.
Almost 100% of client-server communication is done through remoting using this generic DTO, the number of users on this system is going to be close to a 4 digits number.
That pattern introduces quite some bad consequences
->no compiler checks ->
->refactorings are a nightmare
-> no longer code complete support by the IDE
->quite a lot of serialization going on (probably even more than with classic DTO classes)
-> there are many many more cons ...

I think that pattern should only be applied with strong arguments for flexible data structure but not as a general means of data transport!

Posted by Martin Ahrer on September 25, 2009 at 02:00 PM CEST #

I've always considered DTO an anti-pattern (rather than a pattern). It it a sign that something is broken in the communication channel between the bits that "need" the DTO. Quite often the broken bit isn't broken it's more an enforced pattern that simply should never have been used. DTO was an unfortunate by-product (protection against revolving door performance anti-pattern) of EJB 1.x, 2.x.... We're over EJB 2.x... and thus we should relegate it to the dustbin where it belongs. My question is, isn't GDTO along the same lines?

Regards,
Kirk

Posted by kirk on September 27, 2009 at 08:12 PM CEST #

This pattern was discussed in detail in Floyd's 'EJB Design Patterns' in 2002 and even at that time it rather counted as an antipattern.. I agree with the guys here, we shouldn't really advocate this.

Posted by Kristof on September 28, 2009 at 10:38 AM CEST #

Type safety is a fundamental feature of Java. Well designed applications should avoid generic objects VOs, DTOs etc.. and use pure domain POJOs as much as possible. I agree this is an anti-pattern.

Posted by Ashish on October 01, 2009 at 09:05 AM CEST #

Maps are absolutely wonderful. They are of course the mainstay of every "associated array" based scripting language (perl, js, php). They are also, fundamentally, evil. (Hey, if they were not, we would all be using those languages full time and we would not bother with the hassle of typed languages like C++ or Java, right?)
Developers writing raw code with maps just don't have any meta-data to know what is allowable in the map: there isn't any. This is a problem. This problem of 'the intent' is why Java has classes and strong types. It gives developers some meta-data to explain the allowable/intended content of any given object.
If you have a team with a dozen guys and you use maps then within a month someone will start stuffing some random state into the map to pass around as 'convenience'. By 'convenience' read: "could not be bothered to refactor the code so since we are passing maps I can use them as a trojan horse to pass some information around the layers of the system without refactoring the formal API fix the problem I am solving". To catch such crap with peer code review you are going to have to read all the code anyone else writes on the system - that scales about as well as writing it all yourself.
Client code which doesn't know Java (JSON JavaScript or Flash) is a tiny excuse to downgrade serverside J2EE by the use of Maps; and only as boundary 'outbound/inbound' data transfer objects. If you just downgrade to speak to a type unsafe client then by all means do so, just don't call it anything other than a tactical impedance mis-match solution.
Anyone looking to use maps to solve an OO or ORM problem is basically saying "I have found a corner case which is hard to do with OO - so lets not use OO". That's fine for Hibernate dynamic map mode. And fine for ZK framework code that creates CRUD table editing screens on the fly by looking at meta-data of your database tables. Such framework code knows the meta-data of what keys are allowable to be set within any map for a given screen/table. They have a truly hard on-the-fly-problem where using types would require on the fly class generation.
If your not solving a complex on-the-fly problem and don't have a proper solution to manage the meta-data of your system then using maps is usually a bad idea.

Posted by simbo1905 on October 20, 2009 at 04:17 AM CEST #

Post a Comment:
  • HTML Syntax: NOT allowed
realworldpatterns.com
Online Workshops
...the last 150 posts
...the last 10 comments
License