ModelMapper – Convertendo objetos de maneira simples

Durante o desenvolvimento de nossas aplicações frequentemente temos a necessidade de representar nosso modelo de domínio de negócio em outras estruturas.

Um exemplo dessa situação é quando temos um serviço Rest que retorna os dados de uma requisição em formato JSON.

As classes de domínio da aplicação são compostas por entidades que representam o modelo do banco de dados do sistema. Usualmente em situações como esta, precisamos mapear as informações das entidades para objetos de transferência, conhecidos como DTO’s (Data Transfer Object). Após realizar esse mapeamento as informações, presentes nos objetos DTO’s, são convertidos para o formato definido (JSON, XML, entre outros) e enviados para o cliente que chamou a execução do serviço.

O processo de conversão de objetos na maioria das vezes é trabalhoso, repetitivo e verboso, nos leva a criar métodos ou classes específicas para fazer esse trabalho gerando código desnecessário.

Para analisarmos uma situação parecida com o descrito acima, vamos imaginar um conjunto de três entidades, PessoaEntidade, EnderecoEntidade e TelefoneEntidade que precisam ser mapeadas para seus respectivos objetos de transferência, PessoaDTO, EnderecoDTO e TelefoneDTO.

As Listagens 1 e 2, mostram respectivamente as classes classes de entidade e os objetos de transferência.

/*Métodos getters e setters das entidades omitidos*/
public class PessoaEntidade {
    private Long id;
    private String nome;
    private Long cpfCnpj;
    private EnderecoEntidade endereco;
    private List<TelefoneEntidade> telefones;
}

public class EnderecoEntidade {
    private String rua;
    private Integer numero;
    private String complemento;
    private String cep;
}

public class TelefoneEntidade {
    private Integer ddd;
    private Integer prefixo;
    private Integer numero;
}
Listagem 1: Classes de Entidade do sistema.
/*Métodos getters e setters dos DTO's omitidos*/
public class PessoaDTO {
    private Long idPessoa;
    private String nomePessoa;
    private Long cpfCnpjPessoa;
    private EnderecoDTO endereco = new EnderecoDTO();
    private List<TelefoneDTO> telefones;
}

public class EnderecoDTO {
   private String ruaNome;
    private Integer numeroCasa;
    private String complementoLogradouro;
    private String cepLogradouro;
}

public class TelefoneDTO {
    private Integer dddTelefone;
    private Integer prefixoTelefone;
    private Integer numeroTelefone;
}
Listagem 2: Classes de transferência de objetos.

Como podemos observar na Listagem 1, a classe PessoaEntidade possui outros dois objetos tornando o código de conversão ainda mais verboso. Antes de analisarmos o modelmapper em ação, vamos ver como ficaria o código para converter PessoaEntidade para PessoaDTO de forma manual na Listagem 3.

PessoaEntidade pessoaEntidade = obterPessoa();

PessoaDTO pessoaDTO = new PessoaDTO();
pessoaDTO.setIdPessoa(pessoaEntidade.getId());
pessoaDTO.setCpfCnpjPessoa(pessoaEntidade.getCpfCnpj());
pessoaDTO.setNomePessoa(pessoaEntidade.getNome());
         
EnderecoDTO enderecoDTO = new EnderecoDTO();
enderecoDTO.setRuaNome(pessoaEntidade.getEndereco().getRua());
enderecoDTO.setNumeroCasa(pessoaEntidade.getEndereco().getNumero());
enderecoDTO.setCepLogradouro(pessoaEntidade.getEndereco().
        getComplemento());
enderecoDTO.setCepLogradouro(pessoaEntidade.getEndereco().getCep());
        
pessoaDTO.setEndereco(enderecoDTO);
        
List telefones = pessoaEntidade.getTelefones();
        
List telefonesDTO = new ArrayList();
      
for (TelefoneEntidade telEntidade : telefones) { 
	TelefoneDTO telefoneDTO = new TelefoneDTO();
	telefoneDTO.setDddTelefone(telEntidade.getDdd());
	telefoneDTO.setPrefixoTelefone(telEntidade.getPrefixo());
	telefoneDTO.setNumeroTelefone(telEntidade.getNumero());

	telefonesDTO.add(telefoneDTO); 
}
        
pessoaDTO.setTelefones(telefonesDTO);
Listagem 3: Mapeamento tradicional de objetos.

Como mostra a listagem acima, primeiramente executamos o método obterPessoa(), que realizaria uma consulta para trazer uma pessoa do banco de dados ou de um arquivo .txt, por exemplo, entretanto como esse não é nosso objetivo vamos abstrair esse método.

O ponto principal que devemos observar é a quantidade considerável de código que escrevemos para converter/mapear as entidades, do domínio do banco de dados, para os objetos que serão retornados pelo sistema.

Para facilitar o mapeamento entre objetos e tornar esse processo menos trabalhoso, como mostrado na Listagem 3, temos o Framework ModelMapper, que automatiza esse processo seguindo a abordagem “convenção sobre configuração”.

Fazendo um contra-ponto ao código apresentado na Listagem 3, a Listagem 4 mostra como a conversão entre objetos é realizada utilizando o ModelMapper:

 PessoaEntidade pessoaEntidade = obterPessoa();

 ModelMapper mapper = new ModelMapper();
 PessoaDTO dto = mapper.map(pessoaEntidade, PessoaDTO.class);
        
 System.out.println("Nome: " + dto.getNome());
 System.out.println("Numero Casa: " + dto.getEndereco().getNumeroCasa());
 System.out.println("Nome Rua : " + dto.getEndereco().getRuaNome());
 System.out.println("Complemento: " + dto.getEndereco().getComplementoLogradouro());
 System.out.println("Cep: " + dto.getEndereco().getCepLogradouro());
Listagem 4: Mapeamento de objetos com ModelMapper.

Bem menos código! O ModelMapper realizou o mapeamento de todos os dados presentes na entidade Pessoa para PessoaDTO, inclusive os dados presentes nos objetos internos da entidade Pessoa como Endereço e a lista de telefones.

Se voltarmos na Listagem 2, que mostra os objetos de transferência, um detalhe importante é que os atributos desses objetos não precisam ter exatamente o mesmo nome dos atributos das entidades. A única regra nesse caso, para que o modelmapper funcione corretamente, é que os atributos dos objetos de transferência recebam como prefixo o nome do respectivo atributo da entidade, por exemplo: Na entidade Endereco temos o atributo “numero”, dessa forma no objeto de transferência EnderecoDTO devemos ter o atributo correspondente numero, no caso “numeroCasa” e assim por diante.

Internamente, o ModelMapper executou os métodos getters() dos atributos das entidades e enviou as informações para os atributos correspondentes nos DTO’s corretamente, pois como dito anteriormente o framework trabalha com o conceito de convenção, com isso ele é capaz de realizar o mapeamento dos objetos da maneira correta.

Entretanto, existem casos em que é necessário configurar o framework quando queremos realizar mapeamentos mais específicos, geralmente em situações onde não é tão evidente a correlação entre os atributos das entidades e dos objetos de transferência.

Vamos imaginar que ao invés do objeto EnderecoDTO ter os mesmos atributos da entidade Endereco, ele tenha apenas um atributo chamado enderecoCompleto. Esse atributo receberia o resultado da concatenação de todos os atributos da entidade Endereco.

O ModelMapper permite que seja feito uma configuração programática de modo que esse mapeamento seja realizado. Vamos ver o resultado na Listagem 5.

PessoaEntidade pessoaEntidade = obterPessoa();
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.LOOSE);
       
Converter<EnderecoEntidade, String> conversor = new Converter<EnderecoEntidade, String>() {

    public String convert(MappingContext<EnderecoEntidade, String> context) {
         EnderecoEntidade entidade = context.getSource();
         return entidade.getRua() + " " + entidade.getNumero() +  " " + 
               entidade.getComplemento() + " " + entidade.getCep();
     }
;

mapper.addConverter(conversor);

PessoaDTO dto = mapper.map(pessoaEntidade, PessoaDTO.class);

System.out.println("Nome: " + dto.getNome());
System.out.println("Endereco: " + dto.getEndereco().getEnderecoCompleto());
Listagem 5: Criando um converter de mapeamento.

Na Listagem 5, criamos um Converter que concatenará todos os atributos da classe EnderecoEntidade em uma String. Esse “conversor” é passado como parâmetro para o método addConverter() da classe ModelMapper, com isso quando o processo de mapeamento for realizado o atributo enderecoCompleto de EnderecoDTO receberá o endereço completo da pessoa.

Como apresentado neste post, o Framework ModelMapper tem como objetivo facilitar o processo de mapeamento de objetos buscando descomplicar e agilizar uma tarefa tão comum no dia a dia do desenvolvimento. Com isso, acredito que vale a pena analisar a possibilidade de introduzi-la como mais uma ferramenta em nossa caixa para ser utilizada quando necessário.

REFERÊNCIAS

http://modelmapper.org/

Site oficial do Framework ModelMapper

http://modelmapper.org/user-manual/

Documentação do ModelMapper

http://modelmapper.org/examples/

Exemplos de código aplicando o ModelMapper

Por EDMAR BREGAGNOLI

Postado em: 18 de abril de 2016

Confira outros artigos do nosso blog

[Webinar] Profile de aplicações Java com Oracle Mission Control e Flight Recorder

24 de julho de 2017

Danival Calegari

Criando Mocks de serviços REST com SoapUI

27 de junho de 2017

Monise Costa

Three laws that enable agile software development

09 de março de 2017

Celso Gonçalves Junior

Medindo performance de uma API REST

21 de fevereiro de 2017

Monise Costa

Deixe seu comentário