Saturday, November 21, 2009

Bookmark and Share

In a recent post I presented a generic DTO implementation which allows a type-safe way of accessing its contents.

The presented approach reduces the efforts of writing a lot of dedicated DTOs but comes at the cost that it is not obvious, which attributes are allowed to be put into or can be retrieved from a given DTO instance. Therefore methods using such a generic DTO as parameter or return value define a rather loose contract.

In the following I'm going to show a way to solve this problem. The basic idea is to introduce the concept of "attribute groups". Each attribute belongs to such a group, and each GenericDTO instance is created for exactly one attribute group.

Let's first define a marker interface AttributeGroup from which all concrete attribute groups will derive:

1
2
3
public interface AttributeGroup {

}

The Attribute class doesn't differ much from the version introduced in the first post. It's just parametrized with the type of group to which a given attribute belongs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Attribute<G extends AttributeGroup, T> implements Serializable {

    private static final long serialVersionUID = 1L;

    private String name;

    public Attribute(String name) {

        if (name == null) {
            throw new IllegalArgumentException();
        }

        this.name = name;
    }

    public static <F extends AttributeGroup, S> Attribute<F, S> getInstance(
            String name) {
        return new Attribute<F, S>(name);
    }

    @Override
    public String toString() {
        return name;
    }

    // equals(), hashCode() ...
}

As in the previous post Attribute instances are declared as constants on interfaces, e.g. PersonAttributes. But now each of these interfaces extends AttributeGroup, while the Attribute instances are parametrized with the attribute group:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface PersonAttributes extends AttributeGroup {

    public static final Attribute<PersonAttributes, String> FIRST_NAME = 
        Attribute.getInstance("FIRST_NAME");

    public static final Attribute<PersonAttributes, Integer> AGE = 
        Attribute.getInstance("AGE");
}

...

public class OrderAttributes implements AttributeGroup {

    public static final Attribute<OrderAttributes, Long> 
        TOTAL_PRICE = Attribute.getInstance("TOTAL_PRICE");
}

Also the GenericDTO class now has a type parameter specifying for which attribute group a given instance is declared:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class GenericDTO<G extends AttributeGroup> implements Serializable {

    private static final long serialVersionUID = 1L;

    private Class<G> attributeGroup;

    private Map<Attribute<G, ?>, Object> attributes = new LinkedHashMap<Attribute<G, ?>, Object>();

    private GenericDTO(Class<G> attributeGroup) {
        this.attributeGroup = attributeGroup;
    }

    public static <B extends AttributeGroup> GenericDTO<B> getInstance(
            Class<B> clazz) {
        return new GenericDTO<B>(clazz);
    }

    public <T> GenericDTO<G> set(Attribute<G, T> identifier, T value) {

        attributes.put(identifier, value);

        return this;
    }

    public <T> T get(Attribute<G, T> identifier) {

        @SuppressWarnings("unchecked")
        T theValue = (T) attributes.get(identifier);

        return theValue;
    }

    public <T> T remove(Attribute<G, T> identifier) {

        @SuppressWarnings("unchecked")
        T theValue = (T) attributes.remove(identifier);

        return theValue;
    }

    public void clear() {
        attributes.clear();
    }

    public int size() {
        return attributes.size();
    }

    public Set<Attribute<G, ?>> getAttributes() {
        return attributes.keySet();
    }

    public boolean contains(Attribute<G, ?> identifier) {

        return attributes.containsKey(identifier);
    }

    @Override
    public String toString() {
        return attributeGroup.getSimpleName() + " [" + attributes + "]";
    }

    // equals(), hashCode() ...
}

That way it is now clear at compile time which attributes are supported by a given GenericDTO instance.

When for instance having a GenericDTO instance at hand, only attributes of the PersonAttributes group can be put into this DTO or can be retrieved from it. Trying to insert attributes of another group will yield in a compiler error:

1
2
3
4
5
6
7
8
9
...
GenericDTO dto = GenericDTO.getInstance(PersonAttributes.class);

//that's fine
dto.set(PersonAttributes.FIRST_NAME, "Bob").set(PersonAttributes.AGE, 28);

//compiler error
dto.set(OrderAttributes.TOTAL_PRICE, 9990);
...

By introducing the group concept Attribute declaration is a bit more verbose than in the first version, but real compile-time safety and more explicit contracts should be worth the effort, which is still way below than having to implement dedicated DTOs with getters and setters for each attribute as well as proper equals()-, hashCode()- and toString()-Methods.

What do you think – is a generic DTO implemented that way a good idea? As always I'm looking forward to receiving your feedback.

Saturday, November 7, 2009

Bookmark and Share

My article "Objekt: Zur Prüfung bitte!" covering the Bean Validation API (JSR 303) is available for download now. It originally appeared in Java Magazin 09.09.

Since the time of writing JSR 303 reached its final state, but besides some minor changes the article is still up to date (e.g. it's "Payload" instead of "ConstraintPayload" now). More in-depth information on using the Bean Validation API and its reference implementation Hibernate Validator 4 can be found in Hibernate Validator's reference guide, which I co-authored.