quinta-feira, 26 de setembro de 2013

A camada de visão - Business Delegate

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):

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.

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.

Um comentário:

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