Repositório, Dao e sua relação com a Entidade

[quote=sergiotaborda]Exato. Repare como a herança não está de acordo com o que vc mesmo disse.

  1. O repositorio é único para cada entidade. Se existem várias implementações possiveis, então não é único. Logo, o repositorio não pode ser uma interface. Tem que ser um objeto concreto ( não abstrato, não interface, não estático)
  2. O repositorio comunica com um ou mais DAO. Primeiro, herança não é comunicar. Comunicar significa uma relação de cooperação (de composição). Segundo, se o DAO for a implementação de repositorio então não tem como chamar mais do que um DAO.

Acho que isto já basta para mostrar que o Repositorio é um objeto distinto do DAO. Quando digo distintio quero dizer que um não RepositoryPessoaherda ou implementa o outro. A relação é Reposiotorio—>(1…*) DAO e não Repositorio—|> DAO nem DAO—|>Repositorio[/quote]

Bem, agora que você deu essa aula sobre Repository e DAO deixa eu monstrar um pequeno exemplo (agora mais real):

Entidade Pessoa:

[code]public class Produto {
private int id;
private String nome;
private int quantidade;

public Produto(int id, String nome, int quantidade) {
this.id = id;
this.nome = nome;
this.quantidade = quantidade;
}

public int getId() {
return this.id;
}
public String getNome() {
return this.nome;
}

public boolean checaQuantidade() {
if(this.quantidade > 2) {
    return true;
}else{
    return false;
}
}

}[/code]

Classe “de ação” Inventario

[code]public class Inventario {
private List produtos = new ArrayList();

public void adiciona(Produto produto) {
if(produto.checaQuantidade()) {
    System.out.println("Produto adicionado. (" + produto.getNome() + ")");
    this.produtos.add(produto);
}
}

public boolean salva() {
if(!(this.produtos.isEmpty())) {
   RepositoryInventarioImpl repo = new RepositoryInventarioImpl();
   repo.salvar(this.produtos);

   return true;
}else{
    return false;
}
}

}[/code]

RepositoryInventarioImpl:

[code]public class RepositoryInventarioImpl implements RepositoryInventario {

public boolean salvar(List vetorProdutos) {
RepositoryProdutoImpl repopdr = new RepositoryProdutoImpl();

try {
    boolean pdrstatus = repopdr.salvar(vetorProdutos);

    return pdrstatus;
}catch(Exception e) {
    // lança exception
}

return false;
}

}[/code]

RepositoryProdutoImpl:

[code]public class RepositoryProdutoImpl {

public boolean salvar(List vetorProdutos) {
Iterator iterator = vetorProdutos.iterator();

JDBCProdutoDao dao = new JDBCProdutoDao();
try {
    while(iterator.hasNext()) {
	Produto pdr = (Produto) iterator.next();
	dao.salvar(pdr); // manda a interação pro DAO...
    }
}catch(Exception e) {
    // lança a exception (joga para frente)
}

return true;
}

}[/code]

e a app teste…

[code] public static void main(String[] args) {
Produto pdr1 = new Produto(1, “Livro OOP”, 1);
Produto pdr2 = new Produto(2, “Livro Design Patterns”, 3);
Produto pdr3 = new Produto(3, “Abobora”, 4);
Produto pdr4 = new Produto(4, “Pringles :)”, 5);

Inventario estoque = new Inventario();
estoque.adiciona(pdr1);
estoque.adiciona(pdr2);
estoque.adiciona(pdr3);
estoque.adiciona(pdr4);

if(estoque.salva()) {
    System.out.println("Produtos Persistidos no database suavemente");
}else{
    System.out.println("'Faiou' a persistência...");
}
}[/code]

Bom. O que acontece é o seguinte:

  1. Crio alguns objetos Produtos
  2. “Inicializo” o Inventario
  3. Adiciono os produtos 1 a 1 no Inventario
  4. Chamo o estoque.salva() que irá persistir as informações no Inventario.

Agora vem o principal:
5. No Inventario#salva é chamado o RepositoryInventarioImpl#salvar() que recebe um List com os produtos do Inventario.
6. Esse Repository por sua vez, “delega” a ação de adicionar os produtos para outro Repository (como você sugeriu (explico abaixo o por quê fiz)), o RepositoryProdutoImpl.
7. RepositoryProdutoImpl, recebe esse List e como é tarefa dele, prepara os dados para serem repassados a DAO’s necessária - neste caso somente 1 DAO, a JDBCProdutoDao.
8. JDBCProdutoDao se comunica com o database via jdbc e retorna um bool que é transmitido até chegar no Inventario#salvar(), ou, em caso de exceptions, dispara a Exception e essa se propagará até a Inventario#salvar() também.

Legal, agora os motivos disto (na minha opinião e entendimento à sua explicação):

RepositoryInventarioImpl existe, pois talvez eu possa ter alguma rotina de persistência na parte de inventário, logo, eu faço uma façade da RepositoryProdutoImpl para “juntar as rotinas”. Façade simples, onde não impede a comunicação direta com o RepositoryProdutoImpl. (como pede o próprio pattern).

Isso quer dizer que caso eu venha a ter a necessidade de persistir alguma outra informação relacionada à entidade Produtos - como o usuário que fez a alteração no estoque, criando uma espécie de log - durante um ação de salvar(), eu poderia muito bem deixar a cargo do meu RepositoryInventarioImpl receber as novas entidades (via parametros no comportamento) e cuidar de persisti-las para mim, correto ?

Certo. Nada de mau teria caso eu repassasse o objeto Pessoa para a DAO, pois até onde sei, é na DAO que eu crio o Statement, e passo os valores usando bindParam, por exemplo.

Jóia. DAO retorna o renomado ResultSet para o Repositório responsável, o qual se vira para criar os objetos de retorno.

Então o Repository basicamente garante que o objeto seja “traduzido” para o idioma do banco de dados/XML/TXT, e ao mesmo tempo, “traduz” tabelas e linhas em objetos que o sistema entende. Ele é nosso tradutor, nosso modem (analogia ruim, mas vale).

[quote=sergiotaborda]Mas tome atenção que o codigo salva() em pessoa não é necessário para o Repositorio.
Esse codigo em pessoa é o uso de um outro padrão (ActiveRecord).[/quote]
Verdade. No ActiveRecord a implementação da persistência é feita diretamente na entidade. Já com o Repository aplicado, ele é quem cuida disto.

huum! O Registry é o cara que “evita” que eu fique fazendo instâncias de Repositórios em todos comportamentos de persistência, como eu fiz no exemplo acima:

[code]public boolean salva() {
if(!(this.produtos.isEmpty())) {
RepositoryInventarioImpl repo = new RepositoryInventarioImpl();
repo.salvar(this.produtos);

   return true;
}else{
    return false;
}
}[/code]

Ele me auxilia no “encapsulamento” dessas instâncias… seria meio que uma fábrica (factory) então ?

[quote=sergiotaborda]É que com active record qualquer objeto pode mandar salvar a pessoa, mesmo quando não deveria.
O salvar é uma ação protegida por validações e outros processos do sistema que não cabem dentro do objeto pessoa ou do método salvar. [/quote]
Certo. No caso, o Inventario é uma classe que apenas possui comportamentos de persistência, logo, neste caso em especifico, não há problema em trabalharmos com o salvar() nele, correto ?

Falando nisto, veja:

public void adiciona(Produto produto) { if(produto.checaQuantidade()) { System.out.println("Produto adicionado. (" + produto.getNome() + ")"); this.produtos.add(produto); } }

Antes de jogar no List mais um objeto Produto, o Inventario comunica-se com o objeto Produto que foi passado a ele para ter certeza de uma regra em espeficico. Caso, negativo nada é feito, do contrário, o produto entra na lista do Inventario.
A pergunta é: validações de regra de negócio é preferível ficar sempre no modelo, correto ? Pois uma possível implementação seria validar no RepositoryProdutoImpl, mas eu mesmo não achei muitos motivos para isto…

Certo. Repare que no exemplo, eu mando um List de Produtos ao repositório para serem persistidos. Repare também que é o Repository quem faz a interação item a item e adiciona usando a DAO em jogo. Logo, no meu exemplo, eu não conseguiria “pular” o Repositório (algo como: Inventario -> ProdutoDAO), pois estou dependendo da interação. Por isto, estaria meu repositório executando tarefas que não é obrigação dele ? Ou seja, essa interação deveria ficar lá no Inventario#salvar(), ou o Repositório pode colocar a mão-na-massa também ?

Realmente você explicou de forma excelente ! Sem dúvidas foi bem útil para mim !

Abraços e muito obrigado ! (y)

Repare como vc mesmo assume que esta classe não é uma entidade. é uma “acção”.
Na realidade ela está mais para serviço do que para entidade. (repare que não tem estado, e portanto não tem identidade, logo não é uma entidade).

Isso leva à conclusão que Inventário não é uma entidade e sim um serviço. Como tal, o inventário não tem repostorio.

Só paa deixar claro que o inventário é um serviço chamarei a classe de InventarioService.

Outra pista de que Inventário não é uma entidade é que o repositorio de inventário salva produtos. Então ele é um repositorio de produtos. Então não é um repositorio de inventário. Este paradoxo mostra que tb que inventário não é uma entidade e não precisa de repositorio.

Por outro lado vc está usando Inventário como uma coleção de produtos como se pode ver no main.
Na realidade vc precisa de um objeto ajudante para ser essa coleção e o inventário trabalha com essa coleção

Chamemos este objeto ajudante de ProductBag.
Remova o repositorioinventario e rearranje o inventário assim:


public class ProductBag  implements Iterable<Produto>{
 private List produtos = new ArrayList();

     public ProductBag  adiciona(Produto produto) {
           this.produtos.add(produto);
           return this; // permite encadeamento de métodos para simplificar o uso
    }

    public Iterator<Produto> iterator(){
         return produtos.iterator();
   }

   public boolean isEmpty(){
 return pordutos.isEmpty();
   }

}

public class Inventario {
   

    public boolean salva(ProductBag bag) {

        if(bag.isEmpty())) {
              return false;
        }

        List<Produto> produtosASalvar = new LinkedList<Produto>();
         for ( Produto produto : bag){
             if(produto.checaQuantidade()) {
                 System.out.println("Produto adicionado. (" + produto.getNome() + ")");
                 produtosASalvar.add(produto);
             }

         }
	

        RepositoryProdutoImpl repopdr = new RepositoryProdutoImpl();
	return repopdr.salvar(produtosASalvar);

	}
    }

    }
}[/code]




[code]    public static void main(String[] args) {
	Produto pdr1 = new Produto(1, "Livro OOP", 1);
	Produto pdr2 = new Produto(2, "Livro Design Patterns", 3);
	Produto pdr3 = new Produto(3, "Abobora", 4);
	Produto pdr4 = new Produto(4, "Pringles :)", 5);

	ProductBag estoque = new ProductBag ()
	.adiciona(pdr1)
	.adiciona(pdr2)
	.adiciona(pdr3)
	.adiciona(pdr4);

        Inventario inventario = new Inventario();
        
	if(inventario .salva(estoque )) {
	    System.out.println("Produtos Persistidos no database suavemente");
	}else{
	    System.out.println("'Faiou' a persistência...");
	}
    }

O inventário atua controlando quais produtos do bag podem ser adicionado ou não. Isso é um exemplo conceptual de uma regra
de dominio/negocio.

Poderia, mas seria bem mais facil criar uma outra entidade (auditoriaProduto) o serviço receber o usuário e invocar o repositorio de produto normalmente, e depois o repositorio de AuditoriaProduto com referencia ao produto , usuário, data e resto dos paramentros de auditoria. Estes “cutting concerns” - coias que temos que fazer que não pertencem ao dominio, mas ao sistema - são melhores de tratar em serviços. Aliás , o resto da sua aplicação só deve mexer com serviços quando vai alterar alguma coisa.
É por o que eles fazem tende a alterar-se com o tempo.

Não teria nada de mal em principio. Ao passar o objeto ara o DAO e o DAO passar o objeto de volta, isso significa que o DAO está acoplado ao objeto. Isso provavelmente significa que cada entidade tem o seu proprio DAO. Isso funciona, é possivel de usar assim, é sobretudo util quando usa bancos legados. É um trade-off possivel em certas circustancias. Mas se vc está fazendo um sistema do zero, onde pode criar seu proprio modelo de dominio e de dados, o DAO não têm por quê ser acoplado. É melhor usar um DomainStore como o JPA ou o Hibernate ( ou fazer o seu próprio) .
Portanto, o “mal” e o “bem” dependem das cisrcunstancias. Das escolhas que vc tem que fazer, dos constrangimentos com que tem que lidar ( tempo, pessoal, tecnológico , legado, etc… )

Conceptualmente não tem nenhum problema a sua escolha… desde que seja tomada com consciência.

a realidade o modem é a camada de persistencia (DAO, ou DomainStore). Mas dependendo da implementação da camada de persistencia tlbv o repositorio tenha que fazer algumas tarefas como as que falei. O ideal é que ele não tenha que as fazer ( dai a importancia do DomainStore), mas caso sejam necessárias a responsabilidade é do repositorio. Básicamente, quando mais inteligente a sua camada de persistencia, menos trabalho o repositorio faz.

Não. Fabrica é fábrica e registro é registro. Registro é tipo um livro. Vc escreve (normalmente uma vez) e depois le (várias vezes). Fabrica é um objeto que cria o objeto. O Registro pode usar uma fábrica e ser um pouco inteligente, contudo, são dois padrões diferentes. Note que no exemplo do HibernateRepository eu digo que poderia usar um Factory em vez de dar o new directamente. Fabricas são substitudos de “new”. Registros são substitutos de Maps globais e variáveis static.

Mas sim, com o registro vc evita ter que saber qual é o repositorio certo. Vc configura isso no inicio do sistema ( ou deixa o registro se virar sozinho fazendo algumas convenções) e depois só chama pelos repositorios - sem saber a verdadeira implementação deles ! (encapsulamento : essa é a vantagem)

Outro detalhe final. Vc ainda faz seus repositorios implementarem um interface. Entenda que a interface e a implemnetação estão fortemente acopladas. tão fortemente que nunca existirão duas classes implementando a mesma interface. Logo, a interface é ínutil. Vc pode herdar de um repositorio genérico (padrão Layer Supertype) , mas não separar interface e implementação. O repositorio tem que ser concreto ! já vimos que não faz sentido não ser concreto. Lembre-se disso. Vai poupar muita codificação desnecessária.

[quote=sergiotaborda]Repare como vc mesmo assume que esta classe não é uma entidade. é uma “acção”.
Na realidade ela está mais para serviço do que para entidade. (repare que não tem estado, e portanto não tem identidade, logo não é uma entidade). [/quote]

aaah sim! Repository só aplica-se as entidades do sistema. Camadas de serviços não possui Repository ou DAO pois elas apenas executam ações para alguma entidade.
Aproveitando, deixa eu lhe perguntar: neste caso tudo bem, pois o Repository numa ação de findAll() por exemplo, retornaria uma coleção de Produto[] para o Inventário. Mas vamos supor uma situação em que não haja uma classe de serviço para atrelar à entidade. Eu acho que ficaria estranho um objeto Pessoa receber de RepositoryPessoa uma coleção de Pessoa[], não acha ? Como eu poderia proceder nestes casos ?

[quote=sergiotaborda]Por outro lado vc está usando Inventário como uma coleção de produtos como se pode ver no main.
Na realidade vc precisa de um objeto ajudante para ser essa coleção e o inventário trabalha com essa coleção

Chamemos este objeto ajudante de ProductBag. [/quote]

Com isso você sugere que nas classes de serviço eu não deva “manipular” as entidades da forma como eu fiz (criando uma coleção de Produtos) ? Elas (classe de serviço) devem sempre executar as ações e repassar o resultado, nada além disso ?

Vejamos: neste caso teriamos uma nova entidade (AuditoriaProduto), o RepositórioAuditoriaProduto e estes seriam manipulados pela camada de serviço Inventario igualmente o produto, penso. Neste caso, o serviçoInventario iria persistir o objeto no sistema e na sequência iria fazer a auditoria usando o RepositorioAuditoriaProduto.

Algo como:
Inventario#salva() --> RepositorioProduto -> Dao -> database
Inventario#salva() --> RepositorioAuditoriaProduto -> Dao(da auditoria) -> XML file

Como neste exemplo a Auditoria é uma regra para toda persistência do Produto, poderia eu deixar a implementação da entidade AuditoriaProduto a cargo da entidade Produto, não ? Funcionaria meio que uma composição. Produto morre, auditoria morre também. Parece-me lógico, o que acha ?

Certo. Entendi !

Pois é. Estava reparando isso enquanto montava os testes aqui. Nesses casos a interface(interface mesmo) de nada serve/ajuda no Repositório. Obrigado pelo toque !

Abraço!

A discussão estava interessante… :frowning: