Mapeando Objeto para Objeto com ModelMapper

É 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.
mapeamento1

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.
mapeamento2

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.

modelmapper

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:

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:

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.

14 Comentários

  1. Rafael Ponte 09/06/2014 at 11:54 #

    Muito bom!

    Já tinha visto algumas ferramentas desse tipo mas não gostei devido a grande necessidade de configuração via XML. Ter uma boa convenção e uma API de mapeamento simples é essencial para adoção de um framework com esta finalidade.

    Parabéns pelo post e obrigado pelo dica!

  2. Bruno Daniel Marinho 09/06/2014 at 14:47 #

    Esse assunto é bem interessante nessa thread que abri recentemente falamos sobre algumas abordagens para esse problema, interessante os frameworks, porém as vezes da para resolver de forma simples depende da sua aplicação;

    http://www.guj.com.br/java/308252-mapeamento-dos-dados-de-request–para-o-domain-model

  3. Nykolas Lima 09/06/2014 at 15:25 #

    Legal o post, bem bacana essa parte dele conseguir encontrar os getters certos mesmo sem eles terem o mesmo nome nos dois objetos.

    Mas a parte de configuração programática dele achei um pouco verbosa. Eu costumo utilizar um outro framework chamado BFMapper(https://github.com/six2six/bfmapper), acho que pode ser uma alternativa legal para quem não curtir o ModelMapper. PS: o framework é brazuca hahaha

  4. Fabricio Vallim 10/06/2014 at 09:23 #

    Assunto extremamente importante e recorrente no dia-a-dia do desenvolvimento.

    Parabéns pela clareza e simplicidade do post!

  5. Renato Silva dos Santos 10/06/2014 at 13:46 #

    Parabéns pelo post muito interessante e útil.

  6. Leonardo Wolter 10/06/2014 at 13:53 #

    Muito bom, Nico! Conheço vários projetos que fariam bom uso disso 🙂

  7. Gleidson Passos 10/06/2014 at 14:25 #

    Interessante a proposta, mas eu vejo alguns gaps. Caso os objetos que sevem de fonte para preencher a estrutura de objetos de destino sofrerem algum refactory (mudança de nomes, nomeclaturas, etc), os objetos destino obrigatoriamente deverão sofreram modificações.

    Eu geralmente uso esse tipo de estratégia de tradução (Entity to DTO) justamente para não impactar a camada cliente caso haja necessidade de estruturação do modelo.

    Esse tipo de tecnologia, na minha opinião, deve ser usado com muito cuidado.

  8. Sidney 18/06/2014 at 13:06 #

    Muito bom!

  9. Diego 26/06/2014 at 15:47 #

    Nico e Rafa,

    Com java 8 e um passo a mais perto da decomposição e pattern matching, será que os converters não passam a ser obsoletos?

    Tem dois posts extensos, porém muito legais sobre o assunto.

    http://benjiweber.co.uk/blog/2014/05/03/pattern-matching-in-java/
    http://kerflyn.wordpress.com/2012/05/09/towards-pattern-matching-in-java/

    Sem esquecer claro que as “case class” de scala fazem este papel muito bem!

  10. Raphael Lacerda 30/06/2014 at 16:36 #

    Excelente! Tem algum deles que não usa reflection?

  11. Daniel Matos 31/07/2016 at 21:20 #

    Boa noite Nico Steppat,
    Tudo joia?
    Saberia me responder se ModelMapper consegue resolver um problema referencia cíclica:
    Exemplo Usuario tem uma lista de produto e o produto tem um usuário. Sendo que a referencia usuário esta dentro de produto? Tipo um ciclo que ira estourar Stackoverflow?
    UsuarioDTO{List produto;}
    ProdutoDTO{UsuarioDTO usuario;}

    Ótimo post

  12. Daniel Matos 31/07/2016 at 21:28 #

    Respondendo a pergunta anterior minha, resolve sim, problemas de referencias ciclicas. Model Mapper ja virou meu melhor amigo

  13. Danilo Siqueira 06/11/2016 at 09:43 #

    Muito boa dica. Estou desenvolvendo uma API e certamente vou precisar.

  14. Alex Rocha 26/09/2018 at 09:04 #

    Sr. Nico,

    Parabéns! Obrigado por essa publicação realizada com muito amor e dedicação.

Deixe uma resposta