Submarino.com.br

Repository

Objetivo

Concentrar e abstrair regras de pesquisa em objectos do domínio.

Propósito

O conceito do Repository ganhou fama pelo trabalho de Eric Evans[1] sobre Domain-Driven Development e está incluso no catálogo de Martin Fowler[2].O repositório serve como uma abstracção para pesquisa em uma abstrata colecção de instâncias de uma Entidade em um Domínio. A colecção de entidades nunca é visível ou directamente acessível e é apenas um conceito para ajudar a entender o propósito do Repositório. Sem o repositório, cada objecto que necessitasse procurar alguma instância especifica ou algum conjunto de instâncias teria que adquirir a colecção de todas as instâncias e iterar por ela até encontrar as instâncias que precisa. Se dois objectos precisassem procurar a mesma instância ou conjunto de instâncias, ambos seriam obrigados a desenvolver a mesma lógica. Isto é uma clara violação do Principio DRY (Dont Repeat Yourseflt) e significa que ganharíamos se existisse um objecto cuja responsabilidade fosse a de manter essas pesquisas e simplesmente devolver o resultado a quem o pedir. Esta é a ideia do Repositório, um objecto que concentra as regras de pesquisa e simplesmente responde com o resultado da pesquisa. O chamador não sabe qual é a regra nem sabe de onde os objectos vêem

Problema

As regras de pesquisa de uma instância ou conjunto de instâncias dependem fortemente das regras de domínio associadas ao significa de ditas instâncias. Por outro lado, as regras de pesquisa são traduzidas em comandos , como por exemplo, comandos SQL para um Banco de Dados Relacional. No passado o padrão DAO foi usado para esconder qual o comando e qual o banco de dados em uso ao custo de deixar dentro do DAO a regra de domínio explicitada no SQL. Quando a regra mudava, o SQL tinha que ser alterado. Algumas variantes existiam onde o SQL era deixado em um arquivo caso precisasse ser alterado depois. O padrão DAO prometia integrar sistemas tanto com banco de dados relacionais, tanto com LDAP ou XML, e mesmo com banco de dados diferentes simultâneamente. Contudo, foi ficando claro que a cada tecnologia nova uma nova instrução de pesquisa tinha que ser escrita na linguagem ou idioma que essa tecnologia entendia. Então uma simples regra como: Encontre os usuários activos - seria escrita de três formas diferentes usando XPath para XML, SQL para banco dados e Ldap-query para LDAP. O resultado essas pesquisas seria então inserido em um objecto comum , que seria a instância de uma entidade Usuário. Para quem usasse o DAO funcionaria, mas para quem é responsável de manter as regras actualizadas é um pesadelo porque estava sendo desrespeitado do Principio DRY. A mesma pesquisa estáva repetida em três linguagens diferentes.

Solução

A solução que o padrão Repositório oferece é simples. Separa-se a definição da instrução da sua execução. No principio, a ideia era manter o SQL escrito/construido no repositório e passado ao DAO apenas para execução. Isto funcionava em aplicações em que apenas bancos relacionais são usados. Contudo, rapidamente se entendeu que enviar o SQL já pronto não encaixa bem com as diferentes tecnologias de banco de dados pois existem muitos dialectos diferentes. Então recorreu-se ao padrão QueryObject em que um objeto especialmente estruturado para representar a instrução de pesquisa e não uma simples string seria usado. O repositório tem portanto a responsabilidade de montar objetos no padrão QueryObject e enviá-los a um outro objecto capaz de executar essa instrução de pesquisa. Esse outro objecto segue o padrão Interpreter sendo capaz de entender como executar a instrução e retornar as instâncias correspondentes.

Desta forma a verdadeira localização das instâncias e a tecnologia para as acessar é totalmente isolada, não apenas dos pesquisadores das instâncias, mas ainda, do construtor de tais pesquisas. Desta forma ao modificar a tecnologia de acesso e/ou a localização das instancias apenas o interpretador precisa ser modificado.

Se a regra de domínio mudar, se mais ou menos constrangimentos precisarem ser feitos na instrução de pesquisa, apenas o repositório precisa ser modificado, comprovando que o padrão repositório realmente oferece uma boa Separação de Responsabilidade.

Um detalhe importante do repositório é que ele nunca muda o estado das instâncias, nem cria instâncias novas. Esse papel é melhor administrado por um objecto no padrão Service.

Implementação

A implementação do Repositório é simples, a dificuldade está nas implementações dos padrões QueryObject e Interpreter. O repositorio não tem preocupação com a transação em que está sendo usado. Isso é responsabilidade do objeto pesquisador. O Interpreter é, ou contém, normalmente algum tipo de estratégia de persistência associada. O repositorio não deve depender dessa estratégia já que ao modificar a estratégia o repositorio não deve ser modificado. Contudo, existe um acoplamento intrinseco entre o repositorio e o Interpreter na forma do Modelo de Dominio. Este modelo pode ser explicito ou abstracto, mas o Interpreter tem que entender as mesmas entidades e atributos das entidades que o repositório. Não adianta que o Repositório construa um QueryObject pedindo para procurar instâncias de usuário utilizando o campo de nome, e o Interpreter não saber que o campo ?nome? existe, ou como se pesquisa por ele. Abstrair o modelo de Dominio de forma que ambos saibam se comunicar é o real desafío do padrão Repository e não a implementação em si. Também por esta razão a forma de Interpreter mais eficaz para associar ao padrão Repository é a do padrão DomainStore.

Não é necessário que exista um objecto Repositório para cada entidade do domínio. Apenas para aquelas que são alvo de pesquisa isolada. Ou seja, por exemplo, se você tem um objecto Pedido que contém objectos ItemPedido, normalmente você não pesquisa os itens isoladamente, você pesquisa pelo pedido, e ao obter o pedido, os itens já estão associados. Estes objectos que normalmente são o foco da atenção no domínio e que podem trazer consigo outros objectos são chamados de Agregados. O padrão Repository é especialmente útil para agregados pois, dependendo da situação pode ser necessário realizar mais do que uma pesquisa ao Interpreter para obter todos os dados necessários para montar o agregado.

Os métodos do Repositório não precisam apenas retornar instâncias ou conjuntos de instâncias, mas também podem retornar informações agregadas como a soma de um valor especifico. Por exemplo, o Repositório de uma Conta pode ter um método que retorna o saldo da conta em vez de utilizarmos os repositório de movimentos de conta para retornar toda a lista de movimentos e calcular a partir deles quanto a conta tem agora. O mesmo se aplica a contagens , verificação de existência, e algumas funções de projecção de dado como médias, por exemplo. Internamente o Repositório é livre de fazer as pesquisas que entender e retornar o resultado.

Uma implementação simples do padrão repositório é apenas um conjunto de métodos com significado de dominio que pesquisam entidades ou em entidades. O padrão Repository é apenas um agregador de métodos de pesquisa e de certa forma se assemelha ao padrão Registry, mas em que o registro é feito no código e não pelo utilizador da classe. A implementação de repositorio, como a de Registry, não precisa de uma interface e uma implementação, apenas a implementação. Um exemplo seria algo como:

			public class UserRepository {
			
				private DomainStore store;
				
				public UserRepository (DomainStore store){
					this.store = store;
				}
				
				public User findByUsername(String username){
				
					QueryCriteria<User> criteria = new QueryCriteria(User.class);
					criteria.add(new PropertyCriteria("username", Operator.EQ, username);
					
					QueryResult<User> result = store.execute(criteria);
					
					return result.findFirst();
				
				}
				
				public List<User> findAllActive(){
				
					QueryCriteria<User> criteria = new QueryCriteria(User.class);
					criteria.add(new PropertyCriteria("active", Operator.EQ, true);
					
					QueryResult<User> result = store.execute(criteria);
					
					return result.findAll();
				
				}
			
			}
			
			
Código 1:

No exemplo é usado o DomainStore em vez do Interpreter pois normalmente o DomainStore é o tipo de interpreter que estamos interessados em usar. Idealmente a API de QueryObject (exemplificada superfialmente com o objecto QueryCriteria) deve ser independente do DomainStore de forma a manter o desacoplamento e poder mudar a implementação do DomainStore sem ter que reescrever todas as pesquisas em uma nova API. As pesquisas do exemplo são simples, mas a complexidade da pesquisa pode ser qualquer e arbitráriamente complexa. Inclusive o repositorio pode realizar mais do que uma pesquisa para poder computar uma resposta.

Padrões relacionados

Já referimos os padrão QueryObject e Interpreter que são essenciais ao padrão Repository mas ele, também, se associa muito bem ao padrão DomainStore que segue o padrão Interepter mas já contém uma serie de conhecimentos sobre o Modelo de Dominio que lhe permitem trabalhar melhor e com mais eficiência. A existência de implementações de mercado do padrão DomainStore como o Hibernate ou o JPA permitem que a tarefa mais árdua que é criar a API para o QueryObject e o Interpreter já esteja pronta. O Hibernate e o JPA utilizam o conceito de Criteria como uma especialização do padrão CompositeObject, para a implementação de QueryObject e implementam formas de executar essa Criteria provendo assim a funcionalidade associada ao Interpreter.

O DAO é uma padrão muito referido ao falar de Repositorio porque teve um papel da historia do desenvolvimento do padrão Repositorio, mas nunca ha que confundir os dois. Eles servem propósitos diferentes em camadas diferentes. O repositório é um padrão da camada de domínio mais relacionado às regras de negócio e do domínio enquanto o DAO é um padrão da camada de integração mais ligado a regras de tecnologia e a protocolos de dados. O DAO, quando bem desenhado é uma peça de infraestrutura capaz de ser utilizado em vários sistemas diferentes, enquanto o repositório é especifico a um sistema e a um ?modo de pensar? o domínio. O DAO pode existir e ser usado mesmo em sistemas sem camada de dominio, o repositorio não.

Referências

[1] Domain Driven Design

URL: Domain Driven Design http://books.google.com.br/books?id=7dlaMs0SECsC

[2] EAA Catalog

URL: EAA Catalog http://martinfowler.com/eaaCatalog/Repository.html