DTO fácil com o Doozer

Um dos patterns mais consolidado e utilizando em outras linguagens além do Java é o Pattern, Layer. Muitos devem ter ouvido falar de aplicações em 3 camadas ou em N camadas. Nesse tipo comum de arquitetura existe a camada de apresentação também chamada de UI e existe a camada de negocio, onde ficam as regras e o processamento em si.

Neste modelo arquitetural se faz necessário trafegar objetos entre essas camadas. Existem diversas formas de realizar essa tarefa. Se você desenvolve utilizando POJOS pode utilizar a técnica mais simples que é passar o próprio POJO entre as camadas. Ao passar o POJO entre as camadas podemos estar expondo detalhes que implementação que não desejamos além de se fazer necessário todo o grafo de dependências do objeto.

Em aplicações com remotabilidade, leia-se, EJB, WS-*, WS-REST ou qualquer outra forma de comunicação entre camadas e sistemas, você pode utilizar uma técnica um pouco maios apurada que diminui o acoplamento entre as camadas que é o Pattern DTO.

O Pattern DTO

Consiste em criar um objeto para tarsporte de dados, esse objeto não precisa ser igual aos POJOs de sua camada de negôcio. Esse DTO é um POJO também, porém ele não precisa esrtar normalizado.



Nota: O problema de utilizar essa abordagem é que você prode aumentar siginificativamente a proliferação de POJOS em seu projeto, isso em termos de gerência de configuração pode se tornar um problema.

Mas em muitos casos é uma boa usar o Pattern DTO. Quando utilizamos esse pattern precisamos em sintesse copiar dados de uma fonte pode ser o POJO da camada de apresentação por exemplo para o POJO da camada de negócios, essa copia tem o nome de binding.

O problema é ter que realizar isso de forma manual. Para resolver esse problema que existe o Doozer.

Doozer

O Doozer é um framework Java que serve para mapeamento de POJO para POJO, ele também é um Assembler.

Cenário de uso

Na figura a cima você pode ver o cenário de uso que lhes disse. Aqui temos uma arquitetura N camadas onde existe a troca de informações das camdas de negocio e da apresnetação(UI).

O Doozer faz a copia de dados de um POJO para o outro através de um XML de mapeamento, esse XML é muito flexível e permite que parametrizar os campos que serão copiados e como será essa cópia.

A utilização deste framework além de remover boa parte da complexidade de realizar a copia dos dados de grafos profundos de POJOS ele possibilita que o desenvolvedor foque os esforços no negocio e deixe o trabalho pesado com o Doozer. Para os gerentes o Doozer acaba aumentando a produtividade do desenvolvedor já que ele gasta menos tempo com a copia de objetos entre camadas.

Se você tem uma aplicação WEB com o core em EJB ou até mesmo Spring, se você utiliza DWR ou qualquer framework ajax para Java, valhe a pena considerar a utilização do Doozer como binder de seus POJOs.

Um exemplo prático

Fiz um exemplo simples e prático de como seria o uso do Doozer. Nesse exemplo imagine que você está nesse contexto do cenário de aplicações multi-camdas conforme mostrei a cima. Vamos ao código então.

Primeiro vamos as dependências, para usar o Doozer você irá precisar dos seguintes jars no seu classpath:
  • dozer-4.3.jar
  • commons-logging-1.1.jar
  • commons-lang-2.3.jar
  • commons-collections-3.2.jar
  • commons-beanutils-1.8.0.jar
Com essas dependências resolvidas podemos avançar para o código, basicamente existe um POJO que representa a camada de apresentação(view) imagine que esse cara poderia ser um Action do Struts ou até mesmo um ManagedBean do JSF.

Vamos ao código então, a classe se chama: TelaPojoCadastro.java


package com.blogspot.diegopacheco.doozer.view;

/**
* POJO que representa a camada de apresentacao(view) pode ser
* que ele representa um bean do Struts por exemplo ou um ManagedBean do JSF.
*
* @author Diego Pacheco
* @since 27/12/2008
* @version 1.0
*
*/
public class TelaPojoCadastro {

private String nome;
private String email;
private String rua;
private String numero;
private String cidade;
private String estado;
private String pais;

public TelaPojoCadastro() {}   

public TelaPojoCadastro(String cidade, String email, String estado,
String nome, String numero, String pais, String rua) {
super();
this.cidade = cidade;
this.email = email;
this.estado = estado;
this.nome = nome;
this.numero = numero;
this.pais = pais;
this.rua = rua;
}

// Getters and Setters

public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}

public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}

public String getRua() {
return rua;
}
public void setRua(String rua) {
this.rua = rua;
}

public String getNumero() {
return numero;
}
public void setNumero(String numero) {
this.numero = numero;
}

public String getCidade() {
return cidade;
}
public void setCidade(String cidade) {
this.cidade = cidade;
}

public String getEstado() {
return estado;
}
public void setEstado(String estado) {
this.estado = estado;
}

public String getPais() {
return pais;
}
public void setPais(String pais) {
this.pais = pais;
}

}

Você pode perceber que todos os atributos dessa classe são String, aqui fiz desse jeito por que como é um DTO ele pode ser alimentado por uma fonte JSON ou qualquer camada client EJB por exemplo.

Vamos as entidades de negocio, essas são classes do domínio que não são expostas as outras camadas, porem quando os dados vem pelo DTO TelaPojoCadastro precisamos passar os dados dele para as classes:
  • Pessoa
  • Endereco
  • Cidade
  • Estado
  • Pais
Quem vai fazer esse trabalho para nos é o Doozer, claro que alguns mapeamentos temos que informar para ele outros ele sabe por conta :). Então vamos ao código dessas classes, são meros POJOs de negocio.

Pessoa.java
package com.blogspot.diegopacheco.doozer.negocio;

/**
* POJO que representa a uma Pessoa da Camda de negocio. Esse POJO que será
* persistido, aqui poderiam existir mapeamentos do Hibernate por exemplo.
* 
* @author Diego Pacheco
* @since 27/12/2008
* @version 1.0
*
*/
public class Pessoa {

private String nome;
private String email;
private Endereco endereco;

public Pessoa() {}

public Pessoa(String email, Endereco endereco, String nome) {
super();
this.email = email;
this.endereco = endereco;
this.nome = nome;
}

// Getters and Setters

public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}

public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}

public Endereco getEndereco() {
return endereco;
}
public void setEndereco(Endereco endereco) {
this.endereco = endereco;
}

@Override
public String toString() {
return "Nome: " + nome + "\n" +
"Email: " + email + "\n" +
"Endereco: " + endereco;       
}

}

Endereco.java
package com.blogspot.diegopacheco.doozer.negocio;

/**
* POJO que representa um Endereco do negocio.
*
* @author Diego Pacheco
* @since 27/12/2008
* @version 1.0
*
*/
public class Endereco {

private Cidade  cidade;
private String  logradouro;
private Integer numero;

public Endereco() {}   

public Endereco(Cidade cidade, String logradouro, Integer numero) {
super();
this.cidade = cidade;
this.logradouro = logradouro;
this.numero = numero;
}

// Getters and Setters

public Cidade getCidade() {
return cidade;
}
public void setCidade(Cidade cidade) {
this.cidade = cidade;
}

public String getLogradouro() {
return logradouro;
}
public void setLogradouro(String logradouro) {
this.logradouro = logradouro;
}

public Integer getNumero() {
return numero;
}
public void setNumero(Integer numero) {
this.numero = numero;
}

@Override
public String toString() {
return "{\nCidade:  " + cidade.toString() + "\n" +
"Logradouro: " + logradouro + "\n" +
"Número: " + numero + "\n}";   
}

}

Cidade.java
package com.blogspot.diegopacheco.doozer.negocio;

/**
* POJO de negocio que representa uma Cidade.
*
* @author Diego Pacheco
* @since 27/12/2008
* @version 1.0
*
*/
public class Cidade {

private Estado estado;
private String nome;

public Cidade() {}

public Cidade(Estado estado, String nome) {
super();
this.estado = estado;
this.nome = nome;
}

// Getters and Setters

public Estado getEstado() {
return estado;
}
public void setEstado(Estado estado) {
this.estado = estado;
}

public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}   

@Override
public String toString() {
return nome + ", " + estado.getNome() + ", " + estado.getPais().getNome() ;
}

}

Estado.java
package com.blogspot.diegopacheco.doozer.negocio;

import java.util.List;

/**
* POJO de negocio que representa um Estado.
*
* @author Diego Pacheco
* @since 27/12/2008
* @version 1.0
*
*/
public class Estado {

private Pais pais;
private List cidades;
private String nome;

public Estado() {}

public Estado(String nome, Pais pais,List cidades) {
super();
this.nome = nome;
this.pais = pais;
this.cidades = cidades;
}

// Getters and Setters

public Pais getPais() {
return pais;
}
public void setPais(Pais pais) {
this.pais = pais;
}

public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}

public List getCidades() {
return cidades;
}
public void setCidades(List cidades) {
this.cidades = cidades;
}

}   

Pais.java
package com.blogspot.diegopacheco.doozer.negocio;

import java.util.List;

/**
* POJO de negocio que representa um Pais.
*
* @author Diego Pacheco
* @since 27/12/2008
* @version 1.0
*
*/
public class Pais {

private String nome;
private List estados;

public Pais() {}

public Pais(List estados, String nome) {
super();
this.estados = estados;
this.nome = nome;
}

// Getters and Setters

public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}

public List getEstados() {
return estados;
}
public void setEstados(List estados) {
this.estados = estados;
}   

}

Uma vez visto os pojos de negocio agora vamos ao uso do Doozer para realizar o bind entre esses pojos. Criei duas classes utilitárias para facilitar o trabalho.

DoozerViewNegocioMapper.java
package com.blogspot.diegopacheco.doozer.mapper;

import com.blogspot.diegopacheco.doozer.util.ListUtils;

import net.sf.dozer.util.mapping.DozerBeanMapper;
import net.sf.dozer.util.mapping.MapperIF;

/**
* Classe utilitária que faz o mapeamento do Doozer.
*
* @author Diego Pacheco
* @since 27/12/2008
* @version 1.0
*
*/
public class DoozerViewNegocioMapper {

/**
* Instancia o mapeador do Doozer, aqui são passados todos os arquivos
* de mapeamento xml.
*/
private static MapperIF mapper = new DozerBeanMapper(ListUtils.createAndAdd(
"pojos-mappings-pessoa.xml",
"pojos-mappings-endereco.xml",
"pojos-mappings-cidade.xml",
"pojos-mappings-estado.xml",
"pojos-mappings-pais.xml"
));

/**
* Metodo que faz o mapeamento de fato.
* @param source Objeto que tem os dados de origem.
* @param destination Class que deve ser criada como destino
* @return Instancia da Class de destino
*/
@SuppressWarnings("unchecked")
public static Object map(Object source,Class destination){               
return mapper.map(source, destination);
}

}

Essa classe acima é quem invoca o Doozer e informa para ele onde estão os arquivos de mapeamento, o Doozer tem integração com o Spring e você pode coloca-lo no contexto do Spring.

Para facilitar o exemplo criei a classe ListUtils.Java que tem um metodo que já cria uma lista e adiciona um var args de objetos na mesma, para que isso fosse feito in-line. Isso foi apenas para não repitir código.

ListUtils.Java
package com.blogspot.diegopacheco.doozer.util;

import java.util.ArrayList;
import java.util.List;

/**
* Classe utilitária que cria uma List e já adiciona valores
* e devolve a lista criada com os valores.
*
* @author Diego Pacheco
* @since 27/12/2008
* @version 1.0
*
*/
public class ListUtils {   

/**
* Metodo que cria uma List e já adiciona conteudo e
* retorna a List.
*
* @param values var args de valores.
* @return List
*/
@SuppressWarnings("unchecked")
public static List createAndAdd(Object ...values){
List l = new ArrayList();
for(Object o : values){
l.add(o);
}
return l;
}

}

Agora vamos ao mapeamento do Doozer, eu poderia ter colocado em um arquivo mas sepaei por questões de organização. O Doozer tem um mode default, quando copiamos de um pojo A para um pojo B ele copia automaticamamente os atributos de mesmo nome, se você não quer esse comportamento deve usar a exclusão de campos. 

pojos-mappings-pessoa.xml 





com.blogspot.diegopacheco.doozer.view.TelaPojoCadastro
com.blogspot.diegopacheco.doozer.negocio.Pessoa   
  

Aqui ele irá pegar do pojo TelaPojoCadastro as propriedades nome e email por que tem o mesmo nome de propriedades no pojo Pessoa. pojos-mappings-endereco.xml




com.blogspot.diegopacheco.doozer.view.TelaPojoCadastro
com.blogspot.diegopacheco.doozer.negocio.Endereco

rua
logradouro
   

cidade
cidade

  

Aqui estou dizendo que do pojo TelaPojoCadastro eu quero copiar a propriedade rua para a propriedade logradouro do pojo de Endereco, aqui estou também excluido as propriedades cidade com a tag field-exclude. Por que no pojo de origem é um String e no outro é um objeto, isso poderia ser feito de forma automatica se eu tivesse criado um conversor. Como não o fiz estou ignorando essa copia e farei de outra forma. pojos-mappings-cidade.xml




com.blogspot.diegopacheco.doozer.view.TelaPojoCadastro
com.blogspot.diegopacheco.doozer.negocio.Cidade

cidade
nome
 

estado
estado
 
  

Aqui estou copiando do pojo TelaPojoCadastro novamente para o pojo Cidade e estou excluido o estado pelo menos motivo do mapeamento aterior é uma List de objetos e no outro é uma String. pojos-mappings-estado.xml




com.blogspot.diegopacheco.doozer.view.TelaPojoCadastro
com.blogspot.diegopacheco.doozer.negocio.Estado

estado
nome
 

pais
pais
 
  

Acima vemos a cópia do Estado e para finalizar vamos fazer o mapeamento da copia do pojo Pais. pojos-mappings-pais.xml




com.blogspot.diegopacheco.doozer.view.TelaPojoCadastro
com.blogspot.diegopacheco.doozer.negocio.Pais

pais
nome
 
  

Feito todos os mapeamentos vamos a ação. Para mostrar o Doozer funcionando criei a classe CadastroMapper.Java o código dela você confere a baixo. CadastroMapper.java
package com.blogspot.diegopacheco.doozer.mapper;

import com.blogspot.diegopacheco.doozer.negocio.Cidade;
import com.blogspot.diegopacheco.doozer.negocio.Endereco;
import com.blogspot.diegopacheco.doozer.negocio.Estado;
import com.blogspot.diegopacheco.doozer.negocio.Pais;
import com.blogspot.diegopacheco.doozer.negocio.Pessoa;
import com.blogspot.diegopacheco.doozer.util.ListUtils;
import com.blogspot.diegopacheco.doozer.view.TelaPojoCadastro;

/**
* Classe que executa o mapeamento através do Doozer com a
* classe utilitária que criei chamada de
*
* @author Diego Pacheco
* @since 27/12/2008
* @version 1.0
*
*/
public class CadastroMapper {   

@SuppressWarnings("unchecked")
public static void main(String[] args) {

// Crio o pojo de cadastro, imagine que esse pojo represeta a tela
// pode ser um action do Struts por exemplo
// ou a requisição de um client diferente.
TelaPojoCadastro pojoCadastro = new TelaPojoCadastro();
pojoCadastro.setCidade("Gravataí");
pojoCadastro.setEmail("dmetallica@gmail.com");
pojoCadastro.setEstado("RS");
pojoCadastro.setNome("Diego Pacheco");
pojoCadastro.setNumero("1");
pojoCadastro.setPais("Brasil");
pojoCadastro.setRua("xxxxxxxxyyyyyzzzz");

// Aqui estou invocando o Doozer e fazendo o mapeamento do pojo pojoCadastro para
// o pojo de Pessoa, Cidade, Estado, Pais e Endereco repare que a fonte eh a mesma.
Pessoa   pessoa     = (Pessoa)   DoozerViewNegocioMapper.map(pojoCadastro, Pessoa.class);
Cidade   cidade     = (Cidade)   DoozerViewNegocioMapper.map(pojoCadastro, Cidade.class);
Estado   estado     = (Estado)   DoozerViewNegocioMapper.map(pojoCadastro, Estado.class);
Pais        pais       = (Pais)     DoozerViewNegocioMapper.map(pojoCadastro, Pais.class);
Endereco endereco   = (Endereco) DoozerViewNegocioMapper.map(pojoCadastro, Endereco.class);       

// Aqui temos que fazer o set na pata.
estado.setPais(pais);
cidade.setEstado(estado);
endereco.setCidade(cidade);
pessoa.setEndereco(endereco);   

// Faço as setagems reversas, simulando algo do tipo usamos no mapeamento do Hibernate.
pais.setEstados(ListUtils.createAndAdd(estado));
estado.setCidades(ListUtils.createAndAdd(cidade));       

// por fim o resultado é mostrado no console.
System.out.println(pessoa);
}   
}
Nesse exemplo istanciei um pojo TelaPojoCadastro para simular os dados vindos desse DTO e fiz o Doozer realizar a copia dos dados pojo a pojo, isso poderia ter sido feito com apenas uma linha:
DoozerViewNegocioMapper.map(pojoCadastro, Pessoa.class);
Se eu tivesse criado um conversor para cada pojo e associado os mesmos ao Doozer. Como não fiz isso a cada subpojo do pojo Pessoa eu tive que mandar o Doozer fazer outro bind e no fim fazer o setter na pata. As aplicações para o Doozer são em aplicações mulicamadas, integração de sistemas dentro de um ESB por exemplo entre outras. Seria possível criar um sistema de cadastro todo visual e esse sistema gerar os xmls de mapeamento do Doozer, a vantagem? Assim os programadores não precisariam mexer no Doozer, quando um campo muda-se de novo não seria necessário mexer no código somente no mapeamento. Você pode conferir os fontes em um projeto do eclipse aqui.

Popular posts from this blog

Kafka Streams with Java 15

Rust and Java Interoperability

HMAC in Java