Hibernate Session, Repositórios, DAO

22 respostas
MauricioAniche

Pessoal,

No meu tópico anterior, discutimos um pouco sobre quem deveria ser responsável por persistir a informação no banco. No livro do Nilsson, ele sugere algo do tipo:

ISession session = SessaoDoHibernate();

RepositorioDeClientes repositorio = new RepositorioDeClientes(session);

repositorio.adiciona(x);

reposiorio.deleta(y);

session.BeginTransaction().Commit();

A implementacao de adiciona() no caso seria
_session.Save(x);
ou
vocês repassam esse session pra um DAO e este sim executa o Save()? Pq aí eu não vejo muita vantagem em ter diversos DAOs, já que o repositorio acaba recebendo o ISession do NHibernate!

Ou vocês utilizam a estratégia do Service, igual o Sérgio explicou no post anterior?

[]'s
Mauricio

22 Respostas

Alessandro_Lazarotti

DAO traz a abstração de em qual banco voce vai persistir, neste caso representado pela Session do hibernate (que pra ser honesto é um pouco diferente que um DAO, talvez um “DomainStore” versão “DataMapper” rs, mas admita por momento que ele seja seu DAO)… Na verdade, não é necessário “vários DAOs” (em relação a representação e não a infra), o que se precisa é de vários repositórios.

Alessandro_Lazarotti

PS: o código fica melhor com o DAO injetado (do que simplesmente uma IoC manual pelo consrtrutor), pelo menos as dependencias não ficam evidentes no domínio.

MauricioAniche

Alessandro,

O problema é que o ISession tem que ser injetado pelo construtor mesmo, pois eu posso ter diferentes Units of Work em diferentes repositórios. A idéia é de injetar o ISession (Unit Of Work) e depois deixar o UnitOfWork persistir todos os dados.

Minha dúvida é se vocês usam o próprio ISession do HHibernate como Unit of Work, ou vocês abstraem e criam seu próprio Unit of Work e implementam um NHibernateUnitOfWork…

Ou vocês no fundo, usam outra estratégia para persistir os dados, como Services, igual o Sérgio explicou! Essa estratégia do Unit of Work me agradou bastante, o código fica bem bacana!

Consegui me explicar? :slight_smile:

[]'s
Mauricio

Alessandro_Lazarotti

Mas qual é a diferença de você injetar na mão ou um framework fazer a inversão pra você?

Eu prefiro um abstractDao do que usar diretamente a implementação do Hibernate, por exemplo. No implementar eu tenho liberdade de fugir do JPA e usar features do Hibernate ou preferir seguir a risca a especificação do JavaPersistence, essa é “uma” das vantagem de abstrair. Claro que para ter um efeito melhor, as queries tbm deveriam ser abstraídas por QueryObjects… embora de um trampo maior, suas dependencias ficam menores. Mas apenas abstrair os gerenciadores, já podem ser interessantes, olhando para o repository e não vendo nenhum EntityManager por exemplo.

OBS: Os repositórios neste modelo não seriam simples “delegadores”, eles conteriam as regras para a consulta. Tbm deixo claro que essa é UMA possível implementação, para cada caso pode ocorrer uma engenharia diferente para a resolução do problema motivado pelo padrão.

MauricioAniche

Alessandro,

Nunca implementei realmente um framework de IoC, então me corrija se eu estiver errado: Com a injeção sendo feita de forma automática, eu não tenho a instância do objeto que foi injetado, a não ser que eu pegue através de um getUnitOfWork() ou algo assim, correto? E caso eu queira a mesma instância em 2 repositórios diferentes, como eu faria?

E como esse UnitOfWork é responsável pela minha persistência, como eu o chamaria? Através de um get() no repositório?

// rep1 e rep2 tem instancias diferentes do meu UoW, correto?
Repositorio1 rep1 = FrameworkDeIoC.cria(Repositorio1);
Repositorio2 rep2 = FrameworkDeIoC.cria(Repositorio2);

rep1.getUnitOfWork().persistAll(); // isso?

Como você resolve isso?

[]'s
Mauricio

sergiotaborda

Isto é tipicamente o codigo dentro de um UnitOfWork.
O Session não é um UnitOfWork é um DAO. ( Na realiaade é uma parte do DomainStore que é o Hibernate… mas ok)

A tecnologia de persistencia ( aka a API de persistencia) tem que ser chamada do DAO. Caso contrário vc tem um vinculo forte (acoplamento) com essa API. Esse vinculo não é necessáriamente ruim. Vc tem que escolher se quer usar a API directamente e se ferrar depois, ou usar o DAO e não se ferrar depois. Mas se o sistema so for funcionar por 1 mes tlv esse “depois” nunca chegue e portanto a escolha é menos relevante.
A ideia é: lei de murphy : vc vai se ferrar de qualquer jeito: use DAO.

Faça UnitOfWork ser um objeto explicito com transação controladada fora dele. É mais fácil.
Pense no UnitOfWork como um Command cuja execução é especial: é dentro de uma transação e num ambiente que controla concurrencia.

MauricioAniche

Sérgio,

É nisso que eu queria chegar… Você injeta um IUnitOfWork no repositório. A minha dúvida é como é essa interface…

Algo do tipo resolveria, concorda?

public interface IUnitOfWork
{
  void add(Object o);
  void delete(Object o);
  void update(Object o);
  void persistAll();
}

Os Unit of Works de vocês tem algo a mais do que isso?

E aí tanto o Repositorio quanto o Unit of Work fica acoplado ao DAO? Por exemplo…

class Repositorio {
   DAO _dao; // injetado
   Unit _unit; // injetado

  public void adiciona(Pedido p) { 
    _unit.add(p);
  }

  public List<Pedido> PegaTodos() {
     return _dao.pegaTodos();
  }
}

class UnitOfWork extends IUnitOfWork {
  DAO _dao; // injetado

  public void add(Object o) {
    _dao.save(o);
  }

  public void persistAll() {
    _dao.commit();
  }
}

É por aí?

[]'s
Mauricio

sergiotaborda

MauricioAniche:
Sérgio,

É nisso que eu queria chegar… Você injeta um IUnitOfWork no repositório. A minha dúvida é como é essa interface…

Algo do tipo resolveria, concorda?

public interface IUnitOfWork
{
  void add(Object o);
  void delete(Object o);
  void update(Object o);
  void persistAll();
}

Os Unit of Works de vocês tem algo a mais do que isso?

Esse é o padrão UnitOfWork tal como definido pelo Fowler. Na prática não encontro essa forma do padrão util.
Eu perfiro um versão um pouco diferente (que chamo de WorkUnit para não confundir).

abstract classs WorkUnit {

  public abstract  String getWorkResource();
  public abstract void doLocks(WorkContext context);
  public abstract void doWork(WorkContext context);
}

class WorkUnitExecutor{ // ponto de invocação dos trabalhos

       public void execute (WorkUnit wu){
               // faz queue da unidade conforme o recurso 
           
               WorkContext wc = new WorkContext (wu)
               wc.execute();
       }
}

class WorkContext {
     @Inject DAO realDAO;
     WorkDAO workDAO = new WorkDAO();
     

      public void lockResource(String resource){
           // faz o lock do recurso. controla locks anteriores.
        // em caso de problema envia um OptimisticLockException.
      } 

      execute(){
           Transacion t= ...;
           // joga workDAO no thread
           try{
             t.begin() 
             this.wu.doLocks(this);
             this.wu.doWork(this);
             workDAO.commitTO(daoReal);
             
             t.commit();
          } catch (){
             t.rollback();
          } finally {
                 releaseLocks();
                 // retira workDAO do thread
          }
      }

}

// uso 


TransferWorkUnit  wu = new TransferWorkUnit  ( // parametros como contas, valores etc..);
WorkExecutor.getInstance().execute(wu);

class TransferWorkUnit  extends WorkUnit  {

   public void doWork(WorkContext wc){
         RepositoryA r = Repositories.getRepository(RepositoryA.class); 

        // faz algo com r
  }
}

A ideia é que existe um objetivo do tipo comando (o WorkUnit) cuja execução é controlada através de um contexto. Ele pode usar esse contexto para fazer lock de recurso para controlar concorrencia ( pois a do banco não é suficiente já que pode nem existir um banco…).

O contexto usa um DAO especial. Este dao recebe todos as ações mas não as executa, apenas as coloca numa lista. Só quando acontece o commit é que o DAO joga os comandos para um DAO real.

Aqui é extremamente importante que o Repositorio possa ser injetado com o DAO. O repositorio é obtido através de um Registry que olha um threadLocal pelo DAO que está valendo. O WrokContext manipula esse threadlocal para que o Repositorio seja injetado com o DAO “dummy” . Dentro do codigo do workunit que é o que o programador escreve não ha diferença nenhuma já que ele está proibido de usar o DAO , tem que usar o respositório sempre que mexe com regras de negocio.

No fim tudo é revertido para a forma original.
Vc perguntou como eu fazia… :lol:
O meu WorkDAO seria o equivalente ao UnitOfWork do Fowler.

Repare que o UnitOfWork é um buffer do DAO , ou seja, as acções são iguais aos do DAO mas elas não acontecem realmente até que ha o commit final. Ele é um DAO com um método commit().
Nessa estrutura realmente o Repositorio tem que ser injetado com o DAO certo, especialmente em operação que manipulam os dados dentro de uma transação. E o UnitOfWork tem que ser injetado com o DAO real
Não faz sentido o Repositorio ter um UnitOfWork porque o UnitOfWork é opcional. Num select vc não usa UoW.

A ideia seria mais ou menos assim :

class MyService {

@Inject DAO daoReal

public void algumServiço(){
try{
UoWDAO dao = new UoWDAO(daoReal);

Repositorio r = new Repositorio(dao);

// faz algo com r

dao.commit(); 
} catch (){
// não faz dao.commit o que é equivalente a roolback
}

}
}

Nota: a annotaçao @Inject é só para dizer que deve ser injetado. Não é uma especificação de como a injeção deve acontecer. Básicamente significa “obtenha isto de alguma forma”

MauricioAniche

Oi Sérgio,

Nesse seu último exemplo, você passando UoWDAO pro repositório, essa interface deve conter também os métodos do DAO, de listagem, e tudo mais, concorda?

Estou com um projeto aqui chamado SmartCA, não me lembro da onde eu baixei, mas é um exemplo de DDD em .NET. Olha a abordagem dele, e me diga o que você acha.

public interface IUnitOfWorkRepository
    {
        void PersistNewItem(IEntity item);
        void PersistUpdatedItem(IEntity item);
        void PersistDeletedItem(IEntity item);
    }

    public class Repository : OutrasInterfaces, IUnitOfWorkRepository {

       private IUnitOfWork unitOfWork;

      // aqui os metodos add() , remove() apenas chamam as funcoes do IUnitOfWork
      // e implementa os metodos de IUnitOfWorkRepository que realmente persistem os dados no banco.
      // aqui tb tem funcoes de listagem como FindBy() e etc...
        public void Add(T item)
        {
            if (this.unitOfWork != null)
            {
                this.unitOfWork.RegisterAdded(item, this);
            }
        }
    }

    public interface IUnitOfWork
    {
        void RegisterAdded(IEntity entity, IUnitOfWorkRepository repository);
        void RegisterChanged(IEntity entity, IUnitOfWorkRepository repository);
        void RegisterRemoved(IEntity entity, IUnitOfWorkRepository repository);
        void Commit();
        object Key { get; }
        IClientTransactionRepository ClientTransactionRepository { get; }
    }

    public class UnitOfWork : IUnitOfWork {

       // aqui os metodos guardam as classes em colecoes, e no Commit(), elas passam a IEntity
       // para os IUnitOfWorkRepository, esses que realmente executam a persistencia
      
       // segue codigo do commit() para melhor entendimento
        public void Commit()
        {
            using (TransactionScope scope = new TransactionScope())
            {
                foreach (IEntity entity in this.deletedEntities.Keys)
                {
                    this.deletedEntities[entity].PersistDeletedItem(entity);
                    this.clientTransactionRepository.Add(
                        new ClientTransaction(this.key, 
                        TransactionType.Delete, entity));
                }

                foreach (IEntity entity in this.addedEntities.Keys)
                {
                    this.addedEntities[entity].PersistNewItem(entity);
                    this.clientTransactionRepository.Add(
                        new ClientTransaction(this.key,
                        TransactionType.Insert, entity));
                }

                foreach (IEntity entity in this.changedEntities.Keys)
                {
                    this.changedEntities[entity].PersistUpdatedItem(entity);
                    this.clientTransactionRepository.Add(
                        new ClientTransaction(this.key,
                        TransactionType.Update, entity));
                }

                scope.Complete();
            }

            this.deletedEntities.Clear();
            this.addedEntities.Clear();
            this.changedEntities.Clear();
            this.key = Guid.NewGuid();
        }
    }

Conseguiu entender? Essa implementação também é bastante interessante, não?

[]'s

sergiotaborda

MauricioAniche:
Oi Sérgio,

Nesse seu último exemplo, você passando UoWDAO pro repositório, essa interface deve conter também os métodos do DAO, de listagem, e tudo mais, concorda?

sim esqueci de dizer que UoWDAO implementa DAO

É isso ai. Só achei estranho esta parte :

public class Repository : OutrasInterfaces, IUnitOfWorkRepository {

       private IUnitOfWork unitOfWork;

      // aqui os metodos add() , remove() apenas chamam as funcoes do IUnitOfWork
      // e implementa os metodos de IUnitOfWorkRepository que realmente persistem os dados no banco.
      // aqui tb tem funcoes de listagem como FindBy() e etc...
        public void Add(T item)
        {
            if (this.unitOfWork != null)
            {
                this.unitOfWork.RegisterAdded(item, this);
            }
        }
    }

Se o unitOfWork é nulo o repositorio não faz nada ? … meio estranho.
Por outro lado, quem chama o método Commit ? Não é o repositorio , certo ?
Logo o repositorio não precisa saber que existe um UoW , apenas o DAO. E o DAO é obrigatorio existir ou dá exception.

É que nesse exemplo em .NET não ha DAO e o UoW faz o papel tanto de UnitOfWork como de DAO.
Eu só acho que é melhor separar as resposabilidades. Caso contrário eu poderia escrever este codigo sem sentido

public class Repository : OutrasInterfaces, IUnitOfWorkRepository {

       private IUnitOfWork unitOfWork;

      // aqui os metodos add() , remove() apenas chamam as funcoes do IUnitOfWork
      // e implementa os metodos de IUnitOfWorkRepository que realmente persistem os dados no banco.
      // aqui tb tem funcoes de listagem como FindBy() e etc...
        public void Add(T item)
        {
            if (this.unitOfWork != null)
            {
                this.unitOfWork.RegisterAdded(item, this);
               this.unitOfWork.commit();  // !!! Já estraguei a transação
            }
        }
    }
MauricioAniche

Sérgio,

Essa discussão tá ficando interessante e me ajudando demais! :slight_smile:

Só não entendi uma coisa, você falou que tem que separar as responsabilidades, mas você disse que UoWDAO implementa DAO, ou seja, UoW é um Unit of Work e também é um DAO? Você passa o DAO real (que foi injetado) para o UoW… Não fica meio estranho você ter o DAO na camada de aplicação?

Não é tão casual assim separar as responsabilidades desses dois, né?! :wink:

[]'s
Mauricio

sergiotaborda

MauricioAniche:
Sérgio,

Essa discussão tá ficando interessante e me ajudando demais! :slight_smile:

Só não entendi uma coisa, você falou que tem que separar as responsabilidades, mas você disse que UoWDAO implementa DAO, ou seja, UoW é um Unit of Work e também é um DAO?

ah! mas um DAO não é um UnitOfWork. Essa é a separação. Um Unit of Work tem mais responsabilidades. Ele tem que implementar commit(); É essa responsabilidade que o DAO não tem, nem deve ter.

Eu não o “tenho”. E ele não está em lado nenhum. Eu apenas preciso de o injetar.
Repare que o uso do UoW como um DAO é um simplicifador. Vc é livre de criar o UoW com a interface que quiser. Contudo, em algum ponto, o DAO real será chamado. Então porque não dentro do commit() ?

Não entendi o que quer dizer com esta frase. Separação de responsabilidade é o dia-a-dia. É o que separa sistemas bem feitos de gambiarras…

MauricioAniche

Sérgio,

Sim, essa é a idéia, nada de gambiarras…

Então vamos ver se eu entendi… O Unit Of Work vai receber um DAO, meu Repositorio vai receber meu UoW. Meu UoW então vai ter que disponibilizar toda a funcionalidade do DAO para o repositorio… Imagine o seguinte caso… Tenho 2 repositorios… Clientes e Pedidos…

class RepositorioPedidos {
  IUnitOfWork _uow; // injetado no construtor

  Pedido PegaPorId(int id);
  adiciona(Pedido p);
  altera(Pedido p);
  remove(Pedido p);
}

class RepositorioClientes {
  IUnitOfWork _uow; // injetado no construtor

  List<Cliente> PegaTodos();
  adiciona(Cliente p);
  altera(Cliente p);
  remove(Cliente p);
}

Repare que eles tem em comum apenas os métodos de persistência, mas na listagem cada um tem o seu. Vc sugere meu IUnitOfWork ter métodos do tipo FindBy(QueryObject qo), FindByPk(), e coisas do tipo ?

public interface IUnitOfWork {
  void adiciona(object o);
  void altera(object o);
  void remove(object o);
  void commit();

  T findByPk(T obj);
}

[]'s
Mauricio

MauricioAniche

Sérgio,

Que é a idéia do NWorkSpace, do Nilsson, correto? :wink:

http://www.jaoo.dk/jaoo2006/articles/nworkspace.jsp

[]'s
Mauricio

sergiotaborda

MauricioAniche:
Sérgio,

Sim, essa é a idéia, nada de gambiarras…

Então vamos ver se eu entendi… O Unit Of Work vai receber um DAO, meu Repositorio vai receber meu UoW. Meu UoW então vai ter que disponibilizar toda a funcionalidade do DAO para o repositorio… Imagine o seguinte caso… Tenho 2 repositorios… Clientes e Pedidos…

Não. O Unit Of Work vai receber um DAO, meu Repositorio vai receber um DAO, que pode ser ou não um UoW. Meu UoW então vai ter que disponibilizar toda a funcionalidade do DAO para o repositorio de forma “bufferizada”… e o método commit.

O ponto é que o repositorio não pode saber da existencia do UoW, mas ele tem que usá-lo como se fosse um DAO normal.

Sim. Na realidade vc só precisa de um método desses FindBy(QueryObject qo). findbyPk é um caso particular de FindBy(QueryObject qo).
Porquê eu preciso disso ? Porque durante a execução do trabalho vou ter que fazer pesquisas. Essas pesquisas têm tb que ser executadas. O UoW sendo um DAO tem que as fazer. O detalhe é como ele implementa isso. A principio ele simplesmente delega ao DAO real. Contudo em alguns casos vc quer ter um sistema misto, ou seja,
quando vc faz o query vc quer trazer dados no dao real e dados que vc incluio previamente no trabalho. Algo assim

Repositorio r = ... 

r.insere(item);

QueryObject qo = QueryObject.allItens();

List<Item> all = r.find(qo);

a pesquisa já inclui o item inserido com r.insere().
Isto é contornável e muitas vezes a delegação directa é suficiente.

por exemplo

Repositorio r = ... 

r.insere(item);

QueryObject qo = QueryObject.allItens();

List<Item> all = r.find(qo); // não tras o adicionado antes

all.add(item); // agora sim
MauricioAniche

Sérgio,

Perfeito, obrigado! :wink:

[]'s
Mauricio

pcalcado

Mauricio,

A única coisa que um unit of Work faz é listar os objetos afetados por uma transação para que eles sejam atualizados no banco de dados. Você não deve se preocupar em implementar um Unit of Work relacionao dà persistência, é exatamente para isso você usa um ORM como hibernate:

O seu primeiro exemplo já faz uso de Unit of Work provida pelo hibernate, a única coisa aconselhável a alterar é, como o Alessandro falou, ter as dependências injetadas e demarcar transações ao invés de fazer controle manual.

Quanto à chamar ou não um DAO, se você modela seu Repositório como uma interface ao invés de uma classe basta fazer o DAO (que chama o save() na Session/UoW) implementá-lo.

dreampeppers99

E porque não criar três métodos para os repositórios. (baseado na idéia que os Dao's vão compor o Repository)

3 métodos:
inicia()
finaliza()
reverte()

public abstract Repositorio()<T,PK>{
add(T)
remove(T)
T find(PK)
inicia()
finaliza()
reverte()
//...
}
public class RepositorioProdutdo() extends Repositorio(Produto, Long){
//.. métodos específicos do repositório produto
}
Dai quando for usar os mesmos
//... em algum service da vida.

RepositorioCliente repo = new RepositorioCliente();
Cliente cliente = new Cliente();
repo.inicia();
repo.add(cliente);
repo.finaliza();
//quando houver execao
repo.reverte()

Além do mais se houver mudanças para o modo que uso as transações basta alterar num único local.(menos trabalho)

Porque não usar algo semelhante?

sergiotaborda

dreampeppers99:
E porque não criar três métodos para os repositórios. (baseado na idéia que os Dao’s vão compor o Repository)

3 métodos:
inicia()
finaliza()
reverte()

Porque o repositorio não é responsável por controlar transações. Aliás o DAO também não é. Dai a necessidade de um outro objeto para fazer isso. O UnitOfWork é um objeto que ajuda a fazer isso quando existe uma necessidade de controlar concurrencia além de transação e/ou quando o DAO não ha outros mecanismo de transação.
Em ambiente JEE provavelmente vc escreve um serviço anotado com @Transaction e pronto. Mas isso nem sempre é suficiente e fora do ambiente JEE o controle tem que ser mais fino. Contudo, o controle não pode ser adicionado nos objetos que vc já tem pois isso iria sobre-carregar os objetos com responsabilidades que não lhes competem.

pcalcado

Um Repositroy (i.e. a interface exposta pelo que implementa o Repository) não deveria saber sobre transações, isso não faz parte do modelo. Mantêr transações sendo gerenciadas pelo DAO causa uma confusão bem grande com transações reentrantes. O livro Domain-Driven Design fala porque você não deve manter transações sendo gerenciadas pelo repositório -ou sua implementação- e porque você deve deixar transações serem gerenciadas pelo cliente do repositório.

Em geral, o cliente é que conhece o caso de uso e é quem sabe quando algo está completo ou não, o repositório, entities e etc são apenas partes de um processo maior e não sabem qundo é hora de fazer commit.

rponte

O controle transacional via Spring, ou ambiente JEE, ou com AOP, ou mesmo por um filtro podem ser considerados como UnitOfWork, certo? Ou seja, podemos considerar as demarcações como nossa UnitOfWork, correto? O que é bem mais simples e prático que fazer o controle manual através de um objeto como apresentado nos códigos acima.

sergiotaborda

Não. Não podem. UnitOfWork tem caracteristicas que esses outros não têm

  1. é um controle explicito na forma de objeto
  2. serve para controlar unidades de trabalho isso inclui transação mas tb concorrência. Esse concorrência
    é feita em modo objeto ou seja, não tem a haver com o contexto transacional, ou melhor, é uma extensão dele.

UnitOfWork é o controle explicito de concorrência durante a escrita para um banco

Pela propria def do Fowler “Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.”

Os dados são afetados pela transação, mas a responsabilidade do objeto é controlar a concorrencia.
A transação continua ortogonal, mas a concorrencia não.

Criado 28 de abril de 2008
Ultima resposta 3 de mai. de 2008
Respostas 22
Participantes 6