Thoughts on Data Transfer Objects
@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.
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.