Friday 24 February 2012

Thoughts on Data Transfer Objects

To be honest, the Data Transfer Object pattern is not my favourite one. I like to keep my code DRY and the idea of replicating parts of the domain model does not appeal to me. However, exposing entities directly to clients can be tricky, mostly due to their relational nature. Lazily loaded relationships and lengthy object graphs can soon became an issue. Also, more often than not, different clients have different views on the same data and making your core business logic client-agnostic is probably the last thing you want to do. DTO to the rescue, as it provides a good control over the data volume and shape.


For the sake of performance and maintainability, the mapping between DTOs and entities should be as straightforward as possible. To achieve this, DTOs should be mere data containers and stick closely to the underlying entites. Ideally, the mapping can be automated and becomes transparent.

What follows is a sample application of the DTO pattern. The key feature is the seamless conversion between domain model and DTOs. The Orika mapper proved brilliant for this job.

The application is a simple address book, Person and Address being the only entities:
@Entity
public class Person {
    
    @Id
    private Long id;
    
    @Column(name = "first_name", nullable=false)
    private String firstName;
    
    @Column(name = "last_name", nullable=false)
    private String lastName;
    
    @Column(nullable=false, unique=true)
    private String email;
    
    @OneToMany(mappedBy = "person")
    private Collection<Address> addresses;
    ...
}

@Entity
public class Address {

    @Id
    private Long id;
    
    // The usual address fields (street, city, etc.)
    
    @ManyToOne
    private Person person;
    ...
}
Although, there is not the actual GUI in my example I considered the usual approach where people (contacts) would be listed in a grid on the main page and clicking on a row would reveal personal details.

There is not much to show on the grid:
public class PersonDTO {

    private Long id;
    
    private String firstName;
    
    private String lastName;
    
    private String email;
    ...
}
Personal details come in two flavours, a compact one showing a flattened contact address only and a full detail presenting every single bit.
public class PersonCompactDetailDTO extends PersonDTO {
    
    private String contactAddress;
    ...
}

public class PersonDetailDTO extends PersonDTO {

    private List<AddressDTO> addresses;
    ...
}

public class AddressDTO {

    // Matches exactly the Address entity
}
Now, the client data is crafted by a dedicated facade layer which pulls out entities and turns them into DTOs behind the scenes, and vice versa. Here is how the contract looks like:
public interface PresentationManager {
    
    List<PersonDTO> getPeople();
    
    PersonDetailDTO getPersonDetail(Long id);
    
    PersonCompactDetailDTO getPersonDetailAsCompact(Long id);
    
    Long savePerson(PersonDetailDTO person);
}
The final piece is the mapping based on the Orika mapper. The important bits look as follows:
@Service
public class OrikaPresentationManager implements PresentationManager {
  ...    
  private MapperFactory factory = new DefaultMapperFactory.Builder()
                                                            .build();
  ...
  public OrikaPresentationManager() {
    // Register any custom settings     
    ObjectFactory<PersonCompactDetailDTO> customFactory = 
                     new ObjectFactory<PersonCompactDetailDTO>() {
                        
      @Override
      public PersonCompactDetailDTO create(Object o,
                                           MappingContext mc) {
        PersonCompactDetailDTO detail = new PersonCompactDetailDTO();
        Address address = ((Person) o).getContactAddress();
                
        if (address != null) {
          detail.setContactAddress(address.toString());
        }
        return detail;
      }            
    };                
    factory.registerObjectFactory(customFactory,
                                  PersonCompactDetailDTO.class);        
  }

  private <T extends Object> T map(Object source, Class<t> clazz) {
    return factory.getMapperFacade().map(source, clazz);
  }

  @Override
  public List<PersonDTO> getPeople() {
    List<PersonDTO> people = new ArrayList<PersonDTO>();

    for (Person person : addressBook.findAll()) {
      people.add(map(person, PersonDTO.class));
    }

    return people;
  }

  @Override
  public PersonDetailDTO getPersonDetail(Long id) {
    return map(addressBook.find(id), PersonDetailDTO.class);
  }

  @Override
  public PersonCompactDetailDTO getPersonDetailAsCompact(Long id) {
    return map(addressBook.find(id), PersonCompactDetailDTO.class);
  }

  @Transactional
  @Override
  public Long savePerson(PersonDetailDTO person) {
    return addressBook.save(map(person, Person.class));
  }    
}
As you can see the mapping is pretty straightforward. No configuration is needed for most of the properties. Only a little help had to be provided in the constructor to let the mapper know how to get to a calculated field. Apart from that the mapping works seamlessly in both directions.

For the sake of ease-of-use I had to sacrifice some constraints in my domain model though. Typically, I wouldn't expose a public setter on a primary key and I would definitely want to control collection items via custom 'addXYZ' methods etc. Simply put, any fancy stuff in the model adds to configuration effort in the mapper.

Choice is yours nevertheless, finding balance between strict control and high maintainability would be worth an article of its own.

Download Source Code

4 comments:

  1. what orika mapper offer ???

    ReplyDelete
  2. Hi,
    to me it was easy enough to use and catered for a seamless automatic mapping between entities and data transfer objects.

    I am sure you would be able to find another benefits, such as good performance, nested properties mapping etc.

    ReplyDelete
  3. i think dozer also easy to use and performs very well i've spent a significant amount of time profiling the code.
    i'll be thankful if you give me more details for example:
    what types of data objects are supported?
    will orika mapper automatically perform data type conversions?
    is there an eclipse plugin or visual editor for orika?

    ReplyDelete
  4. I think you would be better off checking the project website. Don't get me wrong but my intention was not to promote a particular tool. You certainly could use Dozer or any other mapper of your choice.

    ReplyDelete