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

4 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

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s