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

Como anda a qualidade do seu projeto com Spring?

Quando pensamos em software, principalmente os de médio e longo prazo, um dos fatores que, com certeza vai machucar o time de desenvolvimento, é o momento onde começamos a modificar ou implementar novas funcionalidades no projeto. É nesse momento que os parentes dos antigos programadores começam a ser xingados e, com o passar do tempo, até uma reescrita do sistema começa a ser cogitada.

Acompanhamento e medição da qualidade de código é um fator super importante para a saúde do sistema. Para projetos que envolvem times, é sempre importante lembrar que, provavelmente, as pessoas do seu time vão sair e outras vão chegar. Buscando justamente colaborar com o mundo de desenvolvimento nesse aspecto, meu amigo Maurício Aniche (autor do livro de TDD da Casa do Código) vem trabalhando em projetos que nos ajudam a tirar métricas de qualidade do nosso código.

E para a felicidade geral dos times que usam Spring como alicerce dos seus sistemas, ele publicou um projeto focado em fornecer métricas para softwares que usem o framework. O Springlint pode ser executado como um plugin do maven ou até mesmo diretamente através do jar.

   mvn com.github.mauricioaniche:springlint-maven-plugin:0.4:springlint

Ele fornece uma saída indicando justamente os pontos onde estamos mandando bem e também todos os pontos onde o springlint considera que estamos pecando.

relatorio-springlint

A saída dele é uma página HTML. Na figura acima vemos o que ele fala de cada classe anotada com @Controller. Cada quadradinho verde revela o nome da classe :).

Existem métricas que já são estabelecidas no mercado como:

  1. Métricas de Acoplamento (CBO e RFC), pois não queremos classes acopladas.
  2. Coesão (LCOM), pois uma classe não coesa também não é bom.
  3. Complexidade ciclomática (WMC), pois um código muito complicado é difícil de manter.

Além dessas, também existem métricas criadas por ele durante os estudos do mestrado e do seu atual doutorado.

  1. Promiscuous Controller: Quando um Controller oferece muitas ações diferentes para seus clientes.
  2. Smart Controller: Quando há muito fluxo dentro de um só Controller.
  3. Meddling Service: Quando um Service acessa o banco de dados diretamente.
  4. Smart repositories: Quando um Repositório tem lógicas muito complicadas.
  5. Laborious Repository Method: Quando um único método em um Repositório faz mais de uma coisa só no banco de dados.
  6. Fat Repository: Quando um Repositório lida com muitas entidades de uma só vez.

Este é o tipo de informação que você deve buscar o tempo todo nos seus projetos. Muito simples de executar e com um resultado muito interessante.

Fiquei um bom tempo sem postar, mas o motivo foi nobre! Meu filho nasceu e eu passei 1 mês num curso super prático de cuidados de bebês! Semana que vem, na segunda, tem post novo!