MapStruct: Simplificando mapeamento de DTOs em Java
No artigo de hoje nós aprenderemos de forma simples e direta o uso do framework Mapstruct para mapeamento de DTO’s em java por meio de exemplos práticos em um projeto criado do zero.
Porque utilizar um framework para mapeamento?
De fato é bastante simples fazer o mapeamento de variáveis entre DTO’s em java. Porém, a longo prazo, esse processo se torna trabalhoso e suscetível a erros, pois a montanha de código tende aumentar sem um padrão de mapeamento, se tornando cada vez mais difícil de ser entendido, incrementado e testado. Com MapStruct veremos como fazer isso de forma rápida, simples e padronizada.
Porque MapStruct ?
Segundo a documentação oficial, o MapStruct é um gerador de código que simplifica bastante a implementação de mapeamentos entre tipos de bean Java com base em uma abordagem de convenção sobre configuração. O código de mapeamento gerado usa invocações simples de método e, portanto, é rápido, seguro e fácil de entender.
Dependências e Plugins
MapStruct é compatível com projetos gerenciados pelo Maven, Gradle e Apache Ant. Acesse a documentação oficial para saber mais sobre o processo de instalação e suporte de IDE’S.
Vamos para prática!
Manipularemos classes relacionadas a Cliente e Bicicleta para simular possíveis fluxos relacionados a um sistema de bicicletário.
Criando interface de mapeamento
A anotação @Mapper indica ao MapStruct que a interface anotada será responsável pela declaração dos mapeamentos.
@Mapper(componentModel = “spring”)public interface BicicletarioMapper { ... }
Para fazer com que a interface seja gerenciada pelo Spring e possa ser feita a injeção de dependência nós utilizamos o argumento componentModel = “spring” dentro da anotação @Mapper.
1. Mapeando beans com o mesmo nome
Para nosso primeiro exemplo foi criada as classes ClienteOrigem e ClienteDestino, representando respectivamente a classe de origem dos registros e a classe que receberá os registros através do mapeamento.
ClienteOrigem:
public class ClienteOrigem {private String cpf;
private String nome;
private String telefone;// Getters and setters ...
ClienteDestino:
public class ClienteDestino {private String cpf;
private String nome;
private String telefone;// Getters and setters ...
Com MapStruct o mapeamento automático é definido pela compatibilidade entre tipo e nome de variável do objeto de origem e destino. Aplicando ao nosso exemplo, a variável nome, do tipo String, pertencente à classe ClienteOrigem, pode ser mapeada diretamente para a variável nome, do tipo String, pertencente à classe ClienteDestino, caso ambas as classes sejam definidas na interface de mapeamento.
Vejamos como ficaria a definição de mapeamento entre ClienteOrigem e ClienteDestino:
@Mapper(componentModel = "spring")
public interface BicicletarioMapping { ClienteDestino toClienteDestino (ClienteOrigem clienteOrigem);
}
Simples assim! Após o build do projeto com Maven a implementação gerada pelo MapStruct ficará da seguinte forma:
2. Mapeando beans com nomes distintos
Vamos supor que as classes de origem e destino contenham alguma variável que represente um mesmo atributo, mas que por algum motivo estejam com nomes diferentes. Para isso, vamos adicionar na classe ClienteOrigem a variável cep e em ClienteDestino a varável zipCode, ambas do tipo Integer.
ClienteOrigem
public class ClienteOrigem {private String cpf;
private String nome;
private String telefone;
private Integer cep;// Getters and setters ...
ClienteDestino
public class ClienteDestino {
private String cpf;
private String nome;
private String telefone;
private Integer zipCode;// Getters and setters ...
Para mapear variáveis com nomes distintos basta você adicionar a anotação @Mapping acima da declaração do método da interface, indicando ao MapStruct qual a variável de origem do mapeamento e qual a variável de destino:
@Mapping(source = "clienteOrigem.cep", target = "zipCode")
ClienteDestino toClienteDestino (ClienteOrigem clienteOrigem);
Com isso será gerada a implementação considerando os campos com nomes distintos:
3. Ignorando atributos durante mapeamento
Se você desejar ignorar qualquer atributo durante a conversão então será necessário referenciar qual o atributo do destino que será desconsiderado no código gerado.
@Mapping(target = "zipCode", ignore = true)
ClienteDestino toClienteDestino (ClienteOrigem clienteOrigem);
4. Mapeamentos aninhados
O MapStruct consegue lidar facilmente com mapeamento de objetos dentro de objetos em java. Caso exista uma definição de método para esses atributos aninhados então essa definição será tomada como referência para o mapeamento feito pelo MapStruct, caso contrário será gerado um mapeamento automático dos valores.
Para exemplificar, vamos criar uma variável bicicleta do tipo bicicletaOrigem na classe ClienteOrigem e uma variável bicicleta do tipo bicicletaDestino em ClienteDestino.
BicicletaOrigem:
public class BicicletaOrigem {private String marca;
private String cor;
private Integer Aro;// Getters and setters
...
BicicletaDestino:
public class BicicletaDestino {private String marca;
private String cor;
private Integer Aro;// Getters and setters
...
ClienteOrigem:
public class ClienteOrigem {private String cpf;
private String nome;
private String telefone;
private Integer cep;
private BicicletaOrigem bicicleta;// Getters and setters ...
ClienteDestino:
public class ClienteDestino {
private String cpf;
private String nome;
private String telefone;
private Integer zipCode;
private BicicletaDestino bicicleta;// Getters and setters ...
Como vemos, BicicletaOrigem e BicicletaDestino contém exatamente as mesmas variáveis, então não é necessária nenhuma implementação a mais.
O código gerado pelo MapStruct fica assim:
5. Mapeamento de Collections
Os atributos do tipo Collections com o mesmo tipo de elemento serão copiados criando uma nova instância do tipo Collections de destino que contém os elementos da propriedade de origem. Para atributos do tipo Collections com diferentes tipos de elementos, cada elemento será mapeado individualmente e adicionado à coleção de destino.
Para exemplificar, vamos alterar o atributo bicicleta para List.
ClienteOrigem:
public class ClienteOrigem {
...
private List<BicicletaOrigem> bicicleta;// Getters and setters ...
ClienteDestino:
public class ClienteDestino {
...
private List<BicicletaDestino> bicicleta;// Getters and setters ...
Dessa forma, sem precisar adicionar nenhuma anotação ou novo método de interface, o mapeamento será gerado conforme o quadro abaixo:
Conclusão
Na documentação oficial você poderá ter acesso a dezenas de casos de uso do MapStruct que podem ser úteis para seu projeto e que não foram abordadas nessa introdução.
Não há dúvidas que o MapStruct facilita muito a vida do desenvolvedor, tanto pela sua simplicidade de implementação como também pela quantidade de código que ao invés de ser escrito será deixado na responsabilidade do framework. Com isso, a programação se torna mais fluida, voltada principalmente à solução de problemas mais complexos e não mais a processos simples porém custosos, como o de mapeamento.
Se gostou da solução ou ficou com alguma dúvida sobre o que foi apresentado, deixe seu comentário. Eu espero que você tenha gostado dessa leitura e estou ansioso para ver você novamente! 😉
Meu nome é Pablo Manoel, sou desenvolvedor, entusiasta de novas tecnologias e tenho a missão de ser um solucionador de problemas cada vez melhor, conciliando trabalho, estudos e bem-estar.
A Mobicare e a Akross combinam os Melhores Talentos, Tecnologias de Ponta, Práticas Agile e DevOps com Capacidades Operacionais avançadas para ajudar Operadoras Telecom e grandes empresas a gerarem novas receitas e a melhorarem a experiência dos seus próprios clientes.
Se você gosta de inovar, trabalhar com tecnologia de ponta e está sempre buscando conhecimento, somos um match perfeito!