Mapeando Objeto para Objeto com ModelMapper
Introdução
É muito comum que aplicações mapeiem o modelo de negócio (domínio) para outras estrutura de dados. Por exemplo, se quisermos expor uma parte do domínio como serviço web é preciso mapear os dados para JSON ou XML. Há bibliotecas para esta tarefa como, por exemplo, XStream ou JAX-B. Com elas podemos usar anotações para configurar o mapeamento de JSON/XML. O problema é que essas configurações começam a poluir as classes de domínio e nem sempre são flexíveis o suficiente.
Resolvendo o problema
Uma forma de resolver isso é criar classes dedicadas que só existem para definir o modelo do serviço web. São classes que não possuem nenhuma regra de negócio. Podemos enxerga-las seguindo o antigo padrão DTO (Data Transfer Object, muitas vezes chamado de Value Object). São objetos que só existem para serem transferidos entre camadas físicas. As anotações de mapeamento ficariam então nessas classes e o domínio estaria livre dessas configurações.
O problema agora é que precisamos popular esses objetos, já que criamos uma hierarquia paralela ao nosso domínio, algo que pode ser bastante trabalhoso. O ideal é delegar o trabalho para um framework. Com tempo sugiram algumas opções que se preocupam com o mapeamento de objeto para objeto (Object-to-Object Mapper). Exemplos disso, são Apache Dozer, Orika, Automapper e entre vários outros.
Um framework que se destaca no mercado é o ModelMapper. Com ele é possível mapear modelos complexos, com nenhuma ou poucas configurações - sempre seguindo convenções.
Vamos dar um exemplo mais concreto. Parte do nosso domain model poderia representar um Pedido
que se relaciona com outras classes como Endereco
, Produto
e Cliente
:
public class Pedido { private Endereco destino; private List<Produto> produtos = new ArrayList<>();
private Cliente cliente;
//construtores e métodos omitidos }
A tarefa do ModelMapper é mapear isso para o DTO que normalmente está mais achatado (flat). Veja abaixo que o PedidoDto
possui, na sua maioria, apenas atributos simples:
public class PedidoDto { private String ruaDestino; private String numeroDestino; private String cidadeDestino; private String cepDestino; private String cliente;
private List<ProdutoDto> produtos;
//getters e setters omitidos }
O uso do ModelMapper é simples bastando instanciá-lo para em seguida chamar seu método map(..)
que popula o DTO e recebe o objeto fonte e o tipo do DTO: ```java
Pedido pedido = pegaPedido(); ModelMapper mapper = new ModelMapper(); PedidoDto dto = mapper.map(pedido, PedidoDto.class); //já foi populado
Repare que o _ModelMapper_ precisa chamar vários _getters_ no domínio para pegar, por exemplo, o nome da rua. Nesse exemplo ele executará algo assim:
```java
String rua = pedido.getDestino().getRua(); pedidoDto.setRuaDestino(rua);
O ModelMapper é inteligente o suficiente para encontrar o caminho no domínio e chama os getter desde que existam essas informações no DTO. Quando não há como deduzir o caminho correto, é preciso configurá-lo programaticamente. Por exemplo, para pegar o nome do cliente é necessário chamar:
String nome = pedido.getCliente().getNome().getSobreNome(); peditoDto.setCliente(nome);
Nesse caso o ModelMapper precisa de uma dica pois não tem como deduzir esta chamada. Para tal, existe o PropertyMap
que configura programaticamente o mapeamento entre a fonte (source()
) e o destino (map()
) :
mapper.addMappings(new PropertyMap<Pedido, PedidoDto>() {
@Override protected void configure() { String nome = source().getCliente().getNome().getSobreNome(); map().setCliente(nome); }} );
É importante mencionar que um Object-to-Object Mapper não só serve para mapear o domínio para um DTO. Há várias outras motivações para uma hiearquia paralela. De qualquer forma um object-to-object mapper assume esse parte trabalhosa de copiar os valores entre objetos.
O código completo deste post encontra-se no github.