Este post faz parte de uma série sobre Spring, JPA e JSF. Caso algum
assunto abordado aqui não esteja claro, consulte este link: Spring + JPA.
Quando utilizamos Business Delegate (BD), temos como principal objetivo reduzir o acoplamento entre a camada de visão propriamente dita e os serviços. Neste exemplo que venho construindo desde alguns posts anteriores essa abordagem talvez não seja necessária, mas a aplicação já ficará preparada para evoluir, se for necessário.
Vamos criar um pacote chamado delegate e duas classes semelhantes às classes da camada de controle (ou serviços):
Quando utilizamos Business Delegate (BD), temos como principal objetivo reduzir o acoplamento entre a camada de visão propriamente dita e os serviços. Neste exemplo que venho construindo desde alguns posts anteriores essa abordagem talvez não seja necessária, mas a aplicação já ficará preparada para evoluir, se for necessário.
Vamos criar um pacote chamado delegate e duas classes semelhantes às classes da camada de controle (ou serviços):
package delegate; import entidades.Entidade; import java.io.Serializable; import java.util.List; import service.AbstractService; public abstract class AbstractBD<T extends Entidade> { protected AbstractService<T> service; public abstract void setService(AbstractService<T> service); public void salvar(T entidade) { service.salvar(entidade); } public void salvarLista(List<T> lista) { service.salvarLista(lista); } public T carregar(Class classe, Serializable codigo) { return service.carregar(classe, codigo); } public List<T> listar(Class classe) { return service.listar(classe); } public List<T> consultaPersonalizada(String consulta) { return service.consultaPersonalizada(consulta); } public List<T> consultaNativa(String consulta) { return service.consultaNativa(consulta); } }
package delegate; import java.io.Serializable; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import service.AbstractService; @Component(value = "bd") public class GenericBD extends AbstractBD implements Serializable { @Override @Value("#{genericService}") public void setService(AbstractService service) { this.service = service; } }
Não temos muito o que comentar aqui. Como aconteceu com as classes "Service", temos os métodos que serão utilizados, injeção de dependências dos componentes do Spring que serão necessários, e só. O único ponto relevante é o baixo acoplamento. Mudanças podem ocorrer nos serviços sem que a camada de visão sofra qualquer alteração, e vice-versa. Além do fato de que a camada de serviços trabalha com transações e a camada de visão não percebe isso. Esse é mais um motivo para ressaltar a importância da programação voltada a interfaces (ou voltada a classes abstratas).
Com essa tecnologia, podemos ter mais de um tipo de serviço (serviços para bancos de dados e serviços para arquivos xml, por exemplo) controlados por vários business delegates. Isso permitiria mudar a maneira como se faz a persistência dos dados sem modificar nada na camada de visão. Mas é preciso ainda mais um nível de desacoplamento que pode ser obtido implementando um facade (fachada), que será responsável por reduzir a complexidade dos BDs e fornecer uma classe com métodos que a camada de visão pode entender com maior facilidade. Por exemplo, com base em uma escolha do usuário ou por meio de um arquivo de configuração, o facade poderia decidir se o método "salvar" deve instanciar a gravação em arquivos xml ou em bancos de dados, mas a visão continuaria pedindo apenas para "salvar" os dados, sem saber como isto seria feito.
Com essa tecnologia, podemos ter mais de um tipo de serviço (serviços para bancos de dados e serviços para arquivos xml, por exemplo) controlados por vários business delegates. Isso permitiria mudar a maneira como se faz a persistência dos dados sem modificar nada na camada de visão. Mas é preciso ainda mais um nível de desacoplamento que pode ser obtido implementando um facade (fachada), que será responsável por reduzir a complexidade dos BDs e fornecer uma classe com métodos que a camada de visão pode entender com maior facilidade. Por exemplo, com base em uma escolha do usuário ou por meio de um arquivo de configuração, o facade poderia decidir se o método "salvar" deve instanciar a gravação em arquivos xml ou em bancos de dados, mas a visão continuaria pedindo apenas para "salvar" os dados, sem saber como isto seria feito.
Vou mostrar a seguir o meu modelo de classe facade, que já é utilizado em vários projetos. No mesmo pacote delegate, criamos a classe facadeBD:
package delegate; import entidades.Entidade; import java.io.Serializable; import java.util.List; import javax.faces.application.FacesMessage; import javax.faces.bean.ManagedBean; import javax.faces.bean.ViewScoped; import javax.faces.context.FacesContext; import org.primefaces.context.RequestContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component("facadeBD") @ManagedBean(name = "facadeBD") @ViewScoped public class FacadeBD implements Serializable { public static final short SALVAR = 1; public static final short SALVARLISTA = 2; public static final short LISTAR = 3; public static final short CARREGAR = 4; public static final short CONSULTAPERSONALIZADA = 5; public static final short CONSULTANATIVA = 6; private Class classe; private Serializable chavePrimaria; private Entidade objetoRetorno; private Entidade entidade; private List listaRetorno; private String consulta; private List listaEntidades; @Autowired private GenericBD bd; public FacadeBD() { } public void salvar(Entidade entidade) { this.entidade = entidade; executar(SALVAR); } public void salvarLista(List listaEntidades) { this.listaEntidades = listaEntidades; executar(SALVARLISTA); } public List listar(Class classe) { this.classe = classe; executar(LISTAR); return listaRetorno; } public Entidade carregar(Class classe, Serializable chavePrimaria) { this.classe = classe; this.chavePrimaria = chavePrimaria; executar(CARREGAR); return objetoRetorno; } public List consultaPersonalizada(String consulta) { this.consulta = consulta; executar(CONSULTAPERSONALIZADA); return listaRetorno; } public List consultaNativa(String consulta) { this.consulta = consulta; executar(CONSULTANATIVA); return listaRetorno; } public Boolean mensagemNova(FacesMessage mensagem) { Boolean adicionar = true; List<FacesMessage> lm = FacesContext.getCurrentInstance().getMessageList(); if (lm != null && lm.size() > 0) { for (FacesMessage fm : lm) { if (fm.getDetail().trim().equals(mensagem.getDetail().trim())) { adicionar = false; } } } return adicionar; } public void executar(short metodo) { FacesMessage mens; String mensagem = ""; try { if (metodo == SALVAR) { bd.salvar(entidade); } if (metodo == SALVARLISTA) { bd.salvarLista(listaEntidades); } if (metodo == LISTAR) { listaRetorno = bd.listar(classe); } if (metodo == CARREGAR) { objetoRetorno = bd.carregar(classe, chavePrimaria); } if (metodo == CONSULTAPERSONALIZADA) { listaRetorno = bd.consultaPersonalizada(consulta); } if (metodo == CONSULTANATIVA) { listaRetorno = bd.consultaNativa(consulta); } } catch (Exception e) { System.out.println("===== erro ===="); if (classe != null) { System.out.println("Classe: " + classe.getSimpleName()); } else { System.out.println("Classe: null"); } System.out.println("Método: " + metodo); System.out.println("Consulta: " + consulta); System.out.println("Chave: " + chavePrimaria); e.printStackTrace(); System.out.println("==============="); mens = new FacesMessage(FacesMessage.SEVERITY_FATAL, "Erro!", e.getMessage()); if (mensagemNova(mens)) { FacesContext.getCurrentInstance().addMessage(null, mens); } RequestContext.getCurrentInstance().update("growl"); } finally { if (!mensagem.isEmpty()) { mens = new FacesMessage(FacesMessage.SEVERITY_INFO, "Sucesso", mensagem); if (mensagemNova(mens)) { FacesContext.getCurrentInstance().addMessage(null, mens); } RequestContext.getCurrentInstance().update("growl"); } } } public GenericBD getBd() { return bd; } public void setBd(GenericBD bd) { this.bd = bd; } }
- Como podemos ver na anotação da linha 15, facadeBD é um managed bean, e poderá ser injetado por meio do JSF em outro managed bean com o mesmo escopo (@ViewScoped).
- facadeBD também é um componente no contexto do Spring, e pode receber uma injeção do GenericBD (linhas 32 e 33). Aqui cabe uma observação: para integrar o Spring com o JSF permitindo injeção de dependências entre os contextos, utilizamos uma pequena configuração no arquivo faces-config.xml:
<?xml version='1.0' encoding='UTF-8'?> <faces-config version="2.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"> <application> <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver> </application> </faces-config>
- No trecho compreendido entre as linhas 38 e 71, temos a implementação dos métodos que serão utilizados pela camada de visão. Para salvar uma entidade que foi instanciada no objeto autor, por exemplo, a visão só saberá invocar "salvar(autor)". Todo o processamento de transações, conexão com o banco de dados, decisão sobre inserir ou atualizar, e outros detalhes, serão processados em suas respectivas camadas, de maneira transparente.
- O método executar que inicia na linha 86, desvia para o business delegate bd que foi injetado pelo Spring, todo o restante do processamento.
O restante do código resume-se aos gets e sets e processamento de mensagens do JSF.
No próximo post, a criação de um controlador do JSF que fará a integração entre as páginas e o facadeBD.
O método executar ficou no ar. Exemplos, exemplos e mais exemplos. Quando se deixam coisas "NO AR" se autodestre a bela intenção de ensinar. Lembre sempre que aquilo que para você parece "obvio" para a grande maioria não é.
ResponderExcluir