Repository mais perto do Model

Há 11 anos atrás era escrito no blog da caelum um post sobre como seria legal que os nossos modelos tivessem acesso, de alguma forma, a um conjunto de objetos onde eles poderiam fazer queries. Essa ideia, também conhecida como Repository, já foi discutida zilhares de vezes por aí. Inclusive a discussão recai que, na maioria das vezes, a versão de Repository da aplicação na verdade é um simples Dao.

Independente da nomenclatura, eu acho legal dar mais poder para os models desde que não ferre o design/performance/xpto do projeto como um todo.

Para ficar mais fácil de visualizar, vamos pensar num exemplo de código. Preciso escrever uma funcionalidade que, dado um cliente, eu preciso agora buscar a lista de orçamentos dele desde um ano xpto. Algo assim:

    Cliente cliente = recuperaCliente();
    List<Orcamento> orcamentosCliente = orcamentos.buscaTodosAPartirDeDeterminadoAno(2000);

Esse é o fluxo normal, você tem um objeto cliente, mas para buscar informações sobre ele você precisa acessar o Dao. Caso clássico de comportamento separado de estado…  Eu queria que isso funcionasse da seguinte forma:

    Cliente cliente = recuperaCliente();
    cliente.orcamentosAPartirDeDeterminadoAno(2000);

O meu desejo veio a tona, mas como que eu vou conseguir navegar na lista de orçamentos? Um primeiro pensamento pode ser o seguinte:

    public class Cliente {
      ...
      @OneToMany(mappedBy="cliente")
      private List<Orcamento> orcamentos;

      public List<Orcamento> orcamentosAPartirDeDeterminadoAno(int ano){
        return orcamentos.stream()
          //o metodo que verifica o ano pode ficar dentro do orcamento...
          .filter(orcamento -> orcamento.getAno() >= ano)
          .collect(Collectors.toList());
      }
    }

O problema é que aqui estamos violando a regra de não ferrar o projeto. A depender do número de orçamentos, é muito mais rápido fazer uma query no banco e trazer o resultado. De fato então precisamos do Repository/Dao dentro do nosso modelo, já que uma query mais esperta é realmente necessária. Uma possível solução seria essa:

    public class Cliente {
      ...

      public List<Orcamento> orcamentosAPartirDeDeterminadoAno(int ano){
        return this.clienteRepository.buscaOrcamentosAPartirDoAno(this,ano);
      }
    }

Aqui vem o código que é ok para o DDD, mas que causa angustia nos nossos olhos. Não somos acostumados a isso, mesmo que o tema já tenha sido blogado, em português, faz mais de 10 anos :P. Uma vez que você supere isso e queira se aventurar, vem o segundo problema: como fazer para a instância do Repository aparecer dentro do meu model? Abaixo temos uma primeira abordagem.

    public class OrcamentoController{

      @GetMapping(...)
      @ResponseBody
      public List<Orcamento> orcamentosAPartirDeDeterminadoAno(Model model,int ano,@AuthenticationPrincipal Cliente cliente){
        cliente.setRepository(this.clienteRepository);
        return this.orcamentoRepository.buscaOrcamentosAPartirDoAno(this,ano);
      }
    }

Eu não acho ruim essa solução, só não gosto que ela fique exposta no meio do controller, service ou qualquer outra camada que não seja exatamente onde o objeto foi carregado. Como costumo usar o Spring Data JPA, e não queria escrever delegates para adicionar a lógica de setar o Repository, tentei uma outra solução.

    
    @Entity
    @EntityListeners(RepositoryAwareListener.class)
    public class Cliente {

      ...
    	
      private transient ClienteRepository repository;

      public List<Orcamento> orcamentosAPartirDeDeterminadoAno(int ano){
        return this.repository.buscaOrcamentosAPartirDoAno(this,ano);
      }

    }




    public class RepositoryAwareListener {

    	@PostLoad
    	public void postLoad(Object entity) throws Exception {

    		Repositories repositories = new Repositories(ApplicationContextHolder.getInstance());
    		Optional<Object> repository = repositories.getRepositoryFor(entity.getClass());

    		try {
    			Field repositoryField = entity.getClass().getDeclaredField("repository");
    			repositoryField.setAccessible(true);
    			repositoryField.set(entity, repository.get());
    		} catch (NoSuchFieldException noSuchFieldException) {
    			throw new RuntimeException(
    					"é necessário definir o atributo com nome **repository** na classe "
    							+ entity.getClass(),
    					noSuchFieldException);
    		}

    	}
    }

Caso eu precise do Repository disponível no meu modelo, eu anoto ele com um EntityListener da JPA e aí tem um listener que eu escrevi responsável por descobrir o Repository correto e setar na entidade via reflection. Esse não é um código fácil de ler, mas também ele deve ser pouco alterado durante o desenvolvimento e manutenção do sistema, por isso que não me dói muito. Uma parte legal dele é que o Spring Data já fornece uma abstração onde você pode buscar pelo Repository específico de uma entidade :).

    		Repositories repositories = new Repositories(ApplicationContextHolder.getInstance());
    		Optional<Object> repository = repositories.getRepositoryFor(entity.getClass());

Sempre vale a pena dar uma olhada/pesquisada por classes mais internas do Spring, geralmente tem algo que funciona bem para a sua necessidade do momento.

Para conseguir o ApplicationContext, eu usei a mesma tática já citada aqui no blog. Por sinal, eu só fiz isso porque não consegui achar um jeito do Spring gerenciar o Listener… Quem souber, me avise!!

Eu acho esse jeito legal e realmente não me incomoda acessar o Dao/Repository daquele ponto do código. Dado que achei um jeito razoável de fazer a injeção, consigo viver bem. E para mim o benefício claro é que você da mais contexto ao uso dos métodos do seu Repository, minimizando a chance de passar parâmetros inapropriados. A parte ruim, claramente, foi essa volta que eu tive de dar para jogar o objeto lá dentro, sem contar que é uma solução não padrão(pelo menos eu acho). Um outro ponto que exige observação é que múltiplas invocações do método vão gerar diversas queries, coisa que não aconteceria usando um lazy load do Hibernate por exemplo.

E você, em 2018, o que acha? 11 anos depois ela continua estranha? Será que vale a pena testar?

Advertisements

7 thoughts on “Repository mais perto do Model

  1. Oi Alberto, achei legal, porém achei estranha a parte em que vc faz
    cliente.orcamentosAPartirDeDeterminadoAno(2000);
    Isso te retorna uma lista de orçamentos ou seta os orçamentos no cliente a partir do qual vc ta chamando?
    Se setar, isso não gera inconsistência do objeto? Parece uma mistureba de info no client.
    Abraço!

    Like

  2. Oi Alberto,

    Muito legal o post, mas que tal uma solução mais “nativa” do Spring, como esta aqui usando `AutowireCapableBeanFactory`: https://gist.github.com/rponte/86d74b27ca4f86d4875391e56ade51e0

    Outra solução mais rebuscada (embora eu não curta muito), é utilizar a anotação @Configurable juntamente com AspectJ Weaver do Spring, dessa forma a injeção ocorre naturalmente em objeto instanciados fora do container do Spring, isto é, via operador `new`.

    O que acha?

    Like

  3. Olá Alberto,

    Muito legal o post! Lembro quando comecei a ver mais o conceito de repositório um tempo atrás, mas que no final das contas acabavam como um DAO em nossos projetos mesmo.

    Eu acho muito interessante este tipo de abordagem e utilizaria sem nenhum problema, só estava pensando se ficaria bom ter no nosso modelo consultas com regras de negócios mais complexas, que não seriam apenas uma consulta simples. Neste caso, você acha que seria mais legal apenas para abordagens mais simples ou qualquer uma, mesmo com regras de negócio?

    Like

    • Putz, para mim essa ainda é uma pergunta muito complicada… Depois que faço uma primeira implementação, eu tenho a tendência de experimentar mais um pouco para ir verificando as possibilidades. O que talvez vá me guiando é: se acho que o pedaço de código faz parte do domínio, talvez eu experimente deixar mais perto do model.

      Liked by 1 person

    • Complementando a resposta do Alberto…

      Modelo acessando camada de infra é sempre delicado, mesmo com o uso de repositórios (que na minha opinião são DAOs com outro sufixo na maioria gritante dos casos). Embora o livro de DDD fale que o modelo pode acessar um repositório, o ideal é que esse repositório represente um conceito de negócio, distanciando-se assim da implementação e detalhes de intraestrutura. Ou seja, na minha interpretação, o repositório deve representar um conceito dentro do domínio, e não simplesmente uma fonte de dados genérica como um DAO.

      Uma maneira de resolver isso é criando uma camada de indireção para desacoplar o máximo possível modelo e infra; puxando um gancho pros principios SOLID, poderíamos criar uma interface no modelo para isso (Interface Segregation) e, daí, deixar nossa infra implementar esta interface. Por fim, usando a solução desse post deixamos o Spring fazer a sua “cola” mágica. Para entender melhor, criei um gist rápido aqui: https://gist.github.com/rponte/e1100e2c056adcd2d3a4de66c450bca2

      O que acham?

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s