DAO+EJB: Estou fazendo certo minha arquitetura?

Olá ‘javamaníacos’. Tudo bem com vocês?.
Venho aqui pedir a sugestão de vocês quanto a uma arquiterura de Persistencia que desenvolvi para utilizar em meus projetos. Antes de detalhar a estrutura gostaria de discorrer sobre os fatores que me motivaram a construir tal arquitetura. Vou enumerá-las a pertir de agora:

I. Indepedência da camada de persstência
Uma coisa que sempre me atrapalhou bastante e me fez sofrer muita curva de aprendizado foi o fator de ter que escolher um framework ou forma de trabalhar com camada de persistência. Julgar a melhor é uma tarefa árdua quando se trta de ferramentas como JTA, JPA, Hibernate e até mesmo o uso de JDBC puro, que, às vezes, também tem suas vantagens. Então pensei: por que não construir uma arquiteutra que me permita abstrair minha camada de persistência indepedente do framework/metodologia que utilizarei? ou melhor, me permitir que fique livre para usar livremente quaisquer dessa tecnologia., com baixo acoplamento e maior coesão?!

II. Encapsulamento no lugar de Hernça
Agora, que já tenho mais experiência e, na falta de uma expressão melhor. fluência na linguagem, decidi revisar conceitos que deixei passar no início, e um que me chamou bastante atenção foi o princípoio que diz para se dar preferẽncia a encapsulamento e evitar o uso demasiado de Herança . Minha problemática a essa questão era o uso impulsivo de DAOs herdando de um DAO genérico, fazendo com que tivesse várias classes sem nenhuma utilidade maior que uma simples especificação e dezenas de declarações de atributos redundantes em classes Controllers. Me questionei se estava realmente reaproveitando o código ou simplesmente tentando deixar o código mais ‘bonito’.

III. EJB
De todos os motivos este foi o responsável por me despertar aos dois quetionamentos anteriores, o uso de EJB com injeção de depẽdencia me motivou bastante na forma de construir esta arquitetura. Agora, as classes de controle com métodos lotados de linhas ficara bem mais simples com o uso de um SessionBean, que simplificou e muito minha vida

Agora é hora de mostrar esta bendita arquitetura.
Bem para começar aqui está a classe abstrata que decidi criar. Uma classe abstrata declarando os principais métodos de um DAO, comando de persistência simples. Nomei-o de GenericDAOFacade, e já fica aqui uma pergunta, seria essa realmente a denominação certa que deveria utilizar?

public abstract class GenericDAOFacade<T> {

    public Class entidade;

    public GenericDAOFacade() {
    }

    public GenericDAOFacade(Class entidade) {
        this.entidade = entidade;
    }

    public abstract void save(T t);

    public abstract void update(T t);

    public abstract void saveOrUpdate(T t);

    public abstract List<T> list();

    public abstract T findById(Object id);

    public abstract void delete(T t);

    public abstract void deleteById(Object id);

    public abstract List<T> findByParam(String param, Object value);
    

    public Class getEntidade() {
        return entidade;
    }

    public void setEntidade(Class entidade) {
        this.entidade = entidade;
    }
}

Bem, acho que vocês devem ter percebido que o único motivo que a torna uma classe abstrata é o fato que ela própria especifica a que Classe, do tipo de Modelo, que uma instância deste DAO será responsável.

Continuando, segue abaixo uma IMplementação de um DAO Genérico, atendendo a minha primeira motivação. Com a classe abstrata fico livre para implementar as diversas formas de um DAO, seja com objeto Session do Hibernate, EntityManger do JPA, usar JDBC ou até mesmo gerenciar minha entidade com coleções. As classes GenricDAOImpl e HibernateGenericDAOImpl exemplificam a justificativa a qual me refiro.

public class GenericDAOImpl<T> extends GenericDAOFacade<T> {

    private List<T> lista;

    public GenericDAOImpl() {
        lista = new ArrayList<T>();
    }

    public GenericDAOImpl(Class entidade) {
        super(entidade);
        lista = new ArrayList<T>();
    }

    @Override
    public void save(T object) {
        lista.add(object);
    }

    @Override
    public void update(T object) {
        lista.indexOf(object);
    }

    @Override
    public List<T> list() {
        return lista;
    }

    @Override
    public void delete(T t) {
        lista.remove(t);
    }

    @Override
    public T findById(Object id) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void deleteById(Object id) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void saveOrUpdate(T t) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public List<T> findByParam(String param, Object id) {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    
    
}
public class HibernateGenericDAOImpl<T> extends GenericDAOFacade<T> {

    private Session session;

    public HibernateGenericDAOImpl() {
        session = HibernateUtil.getCurrentSession();
    }

    public HibernateGenericDAOImpl(Class entidade) {
        super(entidade);
        session = HibernateUtil.openSession();
    }

    @Override
    public void save(T t) {
        session.beginTransaction();
        session.save(t);
        session.getTransaction().commit();
    }

    @Override
    public void update(T t) {
        session.beginTransaction();
        session.update(t);
        session.getTransaction().commit();
    }

    @Override
    public List<T> list() {
        return session.createCriteria(entidade).list();
    }

    @Override
    public T findById(Object id) {
        return (T) session.createCriteria(entidade).add(Restrictions.eq("id", id)).uniqueResult();
    }

    @Override
    public void delete(T t) {
        session.beginTransaction();
        session.delete(t);
        session.getTransaction().commit();
    }

    @Override
    public void deleteById(Object id) {
        T t = findById(id);
        session.beginTransaction();
        session.delete(t);
        session.getTransaction().commit();
    }

    @Override
    public void saveOrUpdate(T t) {
        session.beginTransaction();
        session.saveOrUpdate(t);
        session.getTransaction().commit();
    }

    @Override
    public List<T> findByParam(String param, Object value) {
        return session.createCriteria(entidade).add(Restrictions.eq(param, value)).list();
    }
}

Bem, agora valendo-se da minha terceira motivação, segue abaixo o que considero o grande manda-chuva da arquitetura. A classe DAOFactory. Será ela um Stateless SessionBean que porverá instâncias de DAOs a minha aplicação abtraindo a metodologia que é utilizada. É ele que devolve uma instância de DAO válida para as classes de controle ou de negócio de uma forma transaparente, sem denotar qual tipo de persistância está sendo utilizada.

@Stateless
public class DaoFactory {

    private GenericDAOFacade genericDAO;
    
    public String teste(){
        return "teste";
    }

    public GenericDAOFacade getGenericDAO(Class clazz) {
        try {
            genericDAO = (GenericDAOFacade) Class.forName(ContantesAplicacaoUtil.GENERIC_DAO_CLASS).newInstance();
            genericDAO.setEntidade(clazz);
        } catch (InstantiationException ex) {
            Logger.getLogger(DaoFactory.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(DaoFactory.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(DaoFactory.class.getName()).log(Level.SEVERE, null, ex);
        }
        return genericDAO;
    }
}

E por fim, um exemplo para justificar o uso de EJB e uso de encapsulamento no lugar de Herança.

public class EmpresaManager {

    @Inject
    private DaoFactory daoFactory;

    public EmpresaManager() {
    }

    public EmpresaManager(DaoFactory daoFactory) {
        this.daoFactory = daoFactory;
    }

    public void cadastrarEmpresa(Empresa empresa) {
        empresa.setSenha(FacesUtils.md5(empresa.getSenha()));
        daoFactory.getGenericDAO(Empresa.class).saveOrUpdate(empresa);
    }

    public void cadastrarConta(Conta conta, Empresa empresa) {
        conta.setEmpresa(empresa);
        daoFactory.getGenericDAO(Conta.class).save(conta);
    }
    
    public List<Conta> buscarContaPorEmpresa(Empresa empresa){
        return daoFactory.getGenericDAO(Conta.class).findByParam("empresa", empresa);
    }

    public DaoFactory getDaoFactory() {
        return daoFactory;
    }

    public void setDaoFactory(DaoFactory daoFactory) {
        this.daoFactory = daoFactory;
    }
}

Isto seria um exemplo de uma classe de negócios com métodos bem específico ao caso de uso de um cadastro Empresa que posssui várias contas. Note duas coisas.

  1. O uso do DAOFactory para prover as instâncias de DAO necessárias, me dispensando a construção DAOs específicos e declaração de vários objetos que são idênticos e com a mesma funcionalidade.
  2. O uso de herança dispensado, aqui trat-se apenas dmétodos específicos. Minha dúvida é: Isso caracteriza ou não um BO?

Enfim, no fundo, quis compartilhar um pouco da minha visão sobre tal arquietura e pedir a opnião de vocês quanto ás duvidas levantadas e sobre qualquer outra coisa que vocês possam agregar a meu conceito. Desde já grato pela atenção.

[quote]Agora é hora de mostrar esta bendita arquitetura.
Bem para começar aqui está a classe abstrata que decidi criar. Uma classe abstrata declarando os principais métodos de um DAO, comando de persistência simples. Nomei-o de GenericDAOFacade, e já fica aqui uma pergunta, seria essa realmente a denominação certa que deveria utilizar? [/quote]

A faxada não seria a abstração do DAO, ou seja, uma interface entre o DAO e o Controle?

[]'s

getAdicted, é isso mesmo, é uma abstração realmente. Mas no caso, os controllers, até podem, instanciar, mas a idéia é fazer com que o DAOFactory fique responsável por isso.
O motivo de ter feito uma classe abstrata no lugar de uma interface foi por que preciso deixar a minha fachada ciente da classe que será utilizada na sua implementação, e evitar ter que repetir em todas a s implementações o acesso(getters/setters) do atributo Entidade.

Entendi, boa tarde, desculpe o mal jeito.

Olha o meu ai, Obs.: O DAO ainda não está completo:

public interface IAgriculturaDAO&lt;T, ID extends Serializable&gt; { 

	T findById(T entity, ID id);

	List&lt;T&gt; listAll(T entity);

	void save(T entity);
 
	void delete(T entity, ID id); 
 
	@SuppressWarnings("rawtypes")
	List&lt;T&gt; findByCriteria(CriteriaQuery... criterion);

	List&lt;T&gt; findBySQL(String sql);

	void update(T entity);

}
@SuppressWarnings("rawtypes")
@Stateless
@Local({ IAgriculturaDAO.class })
public class AgriculturaDAOImpl&lt;T, ID extends Serializable&gt; implements
		IAgriculturaDAO {

	@PersistenceContext
	private EntityManager entityManager;

	public EntityManager getEntityManager() {
		return entityManager;
	}

	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}

	@SuppressWarnings("unchecked")
	@Override
	public List&lt;T&gt; listAll(Object entity) {
		Query query = this.getEntityManager().createQuery(
				"from " + entity.getClass().getName());
		return query.getResultList();
	}

	@Override
	public void save(Object entity) {
		this.getEntityManager().persist(entity);
	}

	@Override
	public List&lt;T&gt; findByCriteria(CriteriaQuery... criterion) {
		// TODO Auto-generated method stub
		return null;
	}

	@SuppressWarnings("unchecked")
	@Override
	public List&lt;T&gt; findBySQL(String sql) {
		Query query = this.getEntityManager().createNativeQuery(sql);
		return query.getResultList();
	}

	@Override
	public void update(Object entity) {
		this.getEntityManager().merge(entity);
	}

	@Override
	public Object findById(Object entity, Serializable id) {
		return this.getEntityManager().getReference(entity.getClass(), id);

	}

	@Override
	public void delete(Object entity, Serializable id) {
		this.findById(entity, id);

	}

}

[code]@Stateless
public class AgriculturaFacade implements Serializable {

/**
 * 
 */
private static final long serialVersionUID = 1L;

@EJB
private IAgriculturaDAO&lt;Object, Serializable&gt; iAgriculturaDAO;

public void save(Object object) {
	iAgriculturaDAO.save(object);
}

public void delete(Object object, Serializable id) {
	iAgriculturaDAO.delete(object, id);
}

public void listAll(Object object) {
	iAgriculturaDAO.listAll(object);
}

public void update(Object object) {
	iAgriculturaDAO.update(object);
}

public void findById(Object object, Serializable id) {
	iAgriculturaDAO.findById(object, id);
}

}[/code]

//edit
JPA+EJB+JSF2

new GoF(0); :smiley:

[]'s

Bem getAdicted, é interessante a sua arquiterura, mas como disse no início o que me motivou a construir tal aquiterura é a ideía de ficar criando várias interfaces e classes que fazem simplesmente, as mesmas coisas. Agradeço sua ajuda, mas meu interesse á na forma de como evitar redundância e ter cautela no uso de herança.

Não desmerecendo seu código ou a forma como faz, que aparenta ser funcional, só que o que questiono é uma forma de economizar o uso da persitencia e focar no que é realmentente importante. Os requisitos funcionais ou objetos de negócio.

Só para exemplificar o quero dizer: Imagine um projeto que tenha por volta de 20 entidades, já imaginou quantas classes você construiria dessa forma, 20 daos, 20 fachadas 20 interfaces, viu? Minha ideía é justamente evitar ter que ficar criando várias coisas que pouco variam de uma para outra.

[quote=renatowebprog] Os requisitos funcionais ou objetos de negóócio.
[/quote]

Perfeito! E acrescento que por traz de um grande requisito funcional sempre existe um grande requisito não funcional.

Aproveitando a deixa, eu achei que o dev.rafael foi muito feliz nesse post, reforçando o que voce disse.

[]'s

Bem interessante mesmo, o post, pelo que vi, na verdade, o que tenho a agregar me minha arquiterura é a utilização de fachadas não para entidades, mas para um determinado caso de uso, assim não preciso sair escrevendo DAOs para cada entidade, mas apenas para uma tela. Uma fachada que resolva tudo que diz respeito a determinda fucionlaidade, bem interessante mesmo. Obrigado pela sugestão de post.

Eu trabalho um pouco diferente, uso apenas um Repository(ou DAO no seu caso) e várias interfaces de serviço.

interface Entity<T extends Serializable> extends Serializable { T getId(); void setId(T id); } interface Repository { void save(Entity<?> e); void update(Entity<?> e); void delete(Entity<?> e); <E extends Entity<?>> E get(E entity); <T> T getSingle(String jpql, Map<String, Object> parameters); <T> T getSingle(String jpql, Map<String, Object> parameters, Map<String, Object> options); <T> Collection<T> getCollection(String jpql, Map<String, Object> parameters); <T> Collection<T> getCollection(String jpql, Map<String, Object> parameters, Map<String, Object> options); void executeUpdate(String jpql, Map<String, Object> parameters); void executeUpdate(String jpql, Map<String, Object> parameters, Map<String, Object> options); } interface UsuarioService { Collection<Usuario> pesquisarPorNome(String nome, Boolean ativo); Usuario recuperar(Integer id); void salvar(Usuario u); void atualizar(Usuario u); void excluir(Usuario u); } @Stateless class UsuarioServiceImpl implements UsuarioService { private static final String HQL_CONSULTA_POR_NOME = "SELECT u FROM Usuario AS u WHERE u.nome LIKE :nome AND (:ativo IS NULL OR u.ativo = true) ORDER BY u.nome"; @EJB private Repository repository; ... @Override public void excluir(final Usuario u) { try { this.repository.delete(u); } catch (Exception e) { throw new ServiceException(e); } } ... public Collection<Usuario> pesquisarPorNome(final String nome, final Boolean ativo) { try { Map<String, Object> parameters = new HashMap<String, Object>(); parameters.put("nome", JPQLHelper.getLikeValue(nome, false)); if (ConditionUtils.isTrueAndNotNull(ativo)) { parameters.put("ativo", Boolean.TRUE); } else { parameters.put("ativo", null); } return this.repository.getCollection(UsuarioServiceImpl.HQL_CONSULTA_POR_NOME, parameters); } catch (Exception e) { throw new ServiceException(e); } } ... }

Já escrevi sistemas inteiros usando DAOs e Facades genericas, fazendo as consultas usando a própria entidade. Inicialmente é muito bom, faz-se as cosias muito rápido, porém depois o sistema perde a identidade, é tudo genérico mesmo, o que determinava o que seria feito era o controlador web(Action, ManagedBeans, etc). Então quando teria que customizar algumas chamadas especificas acabava que tinha que criar uma interface/classImpl para atender isso e depois alterava diversos locais(onde chama o Facade.save() deveria chamar xyzFacade.salvar()).
Hoje depois de muito estudar resolvi criar Services(Facades) para entidades, grupo de entidades, módulos, etc, dependendo do escopo. Eu posso ter um serviço com os métodos para gerenciar um Usuario(como acima) ou para gerenciar o módulo de acesso todo. Outro ponto é que todas as interfaces são independentes, não herdam de ninguém, então não preciso escrever o método um excluir(), por exemplo, apenas por que a interface pai obriga ou usar um excluir() genérico para uma entidade que não deve ser excluída.

Aqui está os fontes de quando fiz muito parecido da forma que vc fez(GenericService e GenericRepository):
http://code.google.com/p/rockapi/source/browse/trunk/?r=231#trunk%2Frockapi-domain%2Fsrc%2Fmain%2Fjava%2Fnet%2Fwoodstock%2Frockapi%2Fdomain

Com base no que vi no post indicado, implementei a seguinte fachada e vou adotá-la como padrão, Também fiz uma outra pequena alteração e fiz do DAOFactory um Singleton, com a proposta já definida.

@Stateful
public class EmpresaCRUD implements Serializable{

    private List<Empresa> empresas = new ArrayList<Empresa>();
    private List<Conta> contas = new ArrayList<Conta>();
    private GenericDAOFacade<Empresa> daoEmpresa;
    private GenericDAOFacade<Conta> daoConta;

    public EmpresaCRUD() {
    }

    @PostConstruct
    public void inicializar() {
        daoEmpresa = DaoFactory.getGenericDAO(Empresa.class);
        daoConta = DaoFactory.getGenericDAO(Conta.class);
        empresas = daoEmpresa.list();
    }

    public void cadastrarEmpresa(Empresa empresa) {
        empresa.setSenha(FacesUtils.md5(empresa.getSenha()));
        daoEmpresa.saveOrUpdate(empresa);
        empresas = daoEmpresa.list();
    }

    public void cadastrarConta(Conta conta, Empresa empresa) {
        daoConta.save(conta);
        contas = daoConta.list();
    }

    public void removeEmpresa(Empresa empresa) {
        daoEmpresa.delete(empresa);
        empresas = daoEmpresa.list();
    }

    public void removeConta(Conta conta) {
        daoConta.saveOrUpdate(conta);
    }

    //getters & setters
    public List<Conta> getContas(Empresa empresa) {
        if (empresa != null) {
            contas = daoConta.findByParam("empresa", empresa);
        }
        return contas;
    }

    public void setContas(List<Conta> contas) {
        this.contas = contas;
    }

    public List<Empresa> getEmpresas() {
        return empresas;
    }

    public void setEmpresas(List<Empresa> empresas) {
        this.empresas = empresas;
    }
}

Legal essa abordagem, trabalho em um arquitetura de persistência muito similar, só que no meu caso a DAOFactory decide qual subclasse do GenericDAO instanciar, passando o tipo como parâmetro no método Factory.

Por exemplo
DaoFactory.getInstance(Class clazz, String type)
A assinatura acima mostra o método implementado de forma acoplada com o nome da subclasse.
A implementação:
clazzDAO = Class.forName(clazz.getName() + type);
onde type = “Hibernate” ou “JDBC” …

Assim eu teria:
Interface CadastroEmpresaDAO que implementa o GenericDAO
Classe CadastroEmpresaHibernate que implementa CadastroEmpresa
E utilizaria o Factory
CadastroEmpresa empresaDAO = DaoFactory.getInstance(CadastroEmpresa.class, “Hibernate”);

Minha dúvida é
Como sua Factory está escolhendo a subclasse correta com este trecho de código:
genericDAO = (GenericDAOFacade) Class.forName(ContantesAplicacaoUtil.GENERIC_DAO_CLASS).newInstance();
genericDAO.setEntidade(clazz);

[EDITADO]
E o EJB é utilizado neste contexto apenas para utilizar a injeção de dependência da Factory?
A proposta do EJB não é criar uma camada de negócio para objetos distribuídos (RMI) e/ou controle de transações (JTA) ?