Em qual DAO colocar minha SQL?

Separação de responsabilidades é fundamental em qualquer sistema orientado a objetos. Afinal, a vida já nos ensinou que aquele código ASP que escreve HTML, contém regras de negócio, faz pipoca e acessa banco de dados é impossível de ser mantido a longo prazo. E uma das boas práticas em separar responsabilidades (e que os desenvolvedores já fazem bem há bastante tempo) é isolar acesso a banco de dados do resto do código. Sempre colocamos nossas consultas SQLs em nossos DAOs.

O problema é quando nosso sistema cresce, e passa a ter muitos DAOs e SQLs. A pergunta que vem a cabeça é: onde colocar essa SQL? Veja, por exemplo, a SQL abaixo. Em qual DAO ela pertence? Pertence a Projects, Commits ou Artifacts?

SELECT 
 p.name as projectName,
 c.id as commitId,
 a.name as artifactName,
 a.path as artifactPath
FROM
 Projects p
JOIN
 Commits c on c.project_id = p.id
JOIN
 Artifacts a on a.commit_id = c.id
WHERE
 p.repository = 'Apache'; 

Não é uma decisão fácil. O desenvolvedor precisa conhecer bem o contexto do domínio para tomar essa decisão. No exemplo acima, talvez o melhor lugar para essa SQL seria o ArtifactDAO, afinal essa busca devolve uma lista de artefatos. E se por acaso ele tomar uma má decisão e colocar esse método em um DAO que não é o melhor? Outro desenvolvedor, ao procurar pela SQL, não a encontrará, e provavelmente escreverá uma réplica dela em outro lugar. E código repetido é sempre problema.

Descobrir métodos que podem não estar em seus melhores DAOs pode ser algo útil quando pensamos em medir a dívida técnica dos nossos projetos. Para isso, podemos fazer uso de heurísticas para detectar esse tipo de método. Heurísticas e métricas de código são maneiras rápidas e baratas para detectar problemas em nosso código.

Decidimos então bolar algumas regras, baseadas em nossa experiência, de quando um método está no DAO mais correto. Imagine que estamos olhando para os métodos da classe XDao. A boa prática é que XDao seja a classe responsável por acesso aos dados da entidade X. Então, basicamente olhamos para a assinatura de cada método desse DAO: se na assinatura, ele de alguma forma faz uso de X (retorna X, recebe X como parâmetro, faz uso de algum sub-tipo de X, etc), então esse método está no lugar certo.

Obviamente é difícil garantir que se um método apresentar alguma dessas características, ele está no DAO correto. Mas, dado que nossa experiência dizia que na maioria dos casos, isso era verdadeiro, decidimos avaliar se estávamos certos. E para isso olhamos para todos os DAOs de 3 projetos nossos. Criamos um protótipo de ferramenta que olha a assinatura de todos os métodos dos nossos DAOs e nos diz se o método está ou não dentro dessas regras.

Com o resultado em mãos, o apresentamos para um desenvolvedor do projeto dizer se concordava ou não com o resultado da heurística. E, para nossa surpresa, a heurística acerta na grande maioria das vezes!

Para um dos projetos, tínhamos 590 métodos dentro de DAOs, dentro dos quais a ferramenta disse que 79 deles possivelmente estavam em lugares errados. Isso quer dizer em torno de 14% dos métodos. Quando um desenvolvedor do projeto olhou todos eles, ele concordou com a ferramenta em 59 métodos, ou seja, em 75% dos casos! Tivemos resultados parecidos para os outros 2 projetos avaliados: Em um deles, de 13 métodos marcados como possivelmente errados, o desenvolvedor concordou em 8 deles (60%). No outro, dos 33 errados, o desenvolvedor concordou em 16 deles (50%).

Ou seja, a heurística parece fazer sentido. Veja que a ideia de uma métrica de código não é dizer precisamente se uma classe tem ou não determinado problema, mas sim dar um bom indicativo. E veja, dos quase 600 métodos, a ferramenta selecionou apenas 79. E inspecionar manualmente 79 métodos é algo que sua equipe consegue fazer com um custo razoável (ainda mais sabendo que o acerto da heurística é superior a 50%); já inspecionar manualmente 600 métodos custa caro.

Por fim, lembre-se que escrever código de qualidade é difícil. Qualquer ferramenta que nos avise de possíveis problemas é de grande ajuda. Se você gostou do assunto, pode ler mais sobre o paper que publicamos no MTD2014, o Workshop Internacional sobre Dívida Técnica, que aconteceu esse ano, em Victoria/Canadá. Você pode ver a simples implementação do nosso protótipo no Github, e até executá-la em seus projetos.

E nos seus projetos? Você sofre muito para encontrar uma determinada SQL? Já repetiu código nos DAOs, sem querer?

15 Comentários

  1. Kalil Peixoto 16/12/2014 at 09:57 #

    Maurício,

    Parabéns pelo post. Realmente acredito que ao avaliar aonde implementar seus métodos de pesquisa é necessário ter um excelente domínio da sua aplicação. O problema é o que muitas vezes a equipe muda, se renova, e ensinamentos passados vão se perdendo, começando a famosa “colcha de retalhos”. Creio que o processo de build automatizado, integrado com ferramentas de métricas de código pode auxiliar, se não na resolução completa, na diminuição destes problemas.

  2. Flávio Almeida 16/12/2014 at 12:18 #

    Boa Maurício. Realmente, “Qualquer ferramenta que nos avise de possíveis problemas é de grande ajuda”, mas ainda há aqueles que acham perda de tempo investir nessas tecnologias, quando na verdade ganhariam tempo.

  3. Raphael Lacerda 16/12/2014 at 12:25 #

    Ai cara, direito eu perco um tempo me perguntando mesmo onde deveria ir determinado método.

    Exemplo: TodosAlunos e TodasTurmas; Onde devo colocar os métodos para listar os alunos de uma turma?

    todosAlunos.listarAlunosDaTurma(Turma t);
    todasTurmas.listarAlunosDaTurma(Turm t);

    ??

    Outro domínio chato é Endereço. Pois endereço temos CEP, Cidade, Pais e por aí vaí.

    Coloco tudo no repositório de Endereços?

    No meu último projeto eu deixei o repositório de endereços somente com consultas relacionadas a endereços e criei um serviço acima dele.

    O que acha?

    public class ServicoEndereco implements Serializable {

    private static final long serialVersionUID = 1L;
    @Inject
    private TodosCeps todosCeps;
    @Inject
    private TodosPaises todosPaises;
    @Inject
    private TodasCidades todasCidades;
    @Inject
    private TodasUFs todasUFs;

    public List listaPaises() {
    return todosPaises.listar();
    }

    public List listaSiglasUFs() {
    return todasUFs.listaSiglasUFs();
    }

    public List autoCompletarCep(String cep) {
    return todosCeps.autoCompletarCep(cep);
    }

    public List autoCompletarCidade(String cidade) {
    return todasCidades.autoCompletarCidade(cidade);
    }

    public Cidade consultaCidadePorNome(String nome) {
    return todasCidades.consultaCidadePorNome(nome);
    }

    }

  4. Maurício Aniche 17/12/2014 at 11:47 #

    Oi Rapha,

    Difícil mesmo, né!? Acho que não há uma resposta certa pra isso. Mas talvez algo que seja combinado na equipe.

    Por exemplo, no caso de Turma e Aluno, como Turma é “maior” que aluno, colocar no TurmaDAO o método listaAlunos(Turma t) pode fazer algum sentido.

    O que acha?

  5. Raphael Lacerda 17/12/2014 at 13:25 #

    Realmente não sei, mas o que o seu algoritmo de análise diria?

    Pois ambos retornariam List e ambos recebem Turma como argumento.

    Geralmente eu faço assim:

    Se no relacionamento está mapeado unidirecionalmente de Turma para Aluno

    turma.getAlunos(),

    então eu coloco no DAO de Turma.

    Agora se está mapeado unidirecionamente de Aluno para Turma

    aluno.getTurma(),

    então eu coloco no DAO de Aluno.

  6. Rafael Ponte 17/12/2014 at 18:39 #

    Muito bacana, Maurício!

    Raphael, não é algo simples de decidir e como o Aniche comentou, definir o que é certo ou errado é mais complicado ainda. Enquanto trabalhamos com CRUDs simples tudo tem seu lugar, mas quanto partimos para relacionamentos e consultas mais complexas o problema surge!

    Se tomarmos DDD como base, o ideal é ter um agregado raiz (Aggregate Root) para simplificar a idéia e nunca esquecer de levar em conta o contexto do domínio na qual estamos inserido no momento. Nesse caso podemos considerar a Turma como esse agregado raiz, por exemplo.

    Enfim, não é fácil mesmo!

  7. Mariane Machado 18/12/2014 at 09:46 #

    Excelentes questionamentos. Curiosa pra saber o que a ferramenta vai dizer dos daos do projeto atual em que estou trabalhando.

    A propósito, vocês podem botar no github um readme pra saber como utiliza-la?

  8. Maurício Aniche 19/12/2014 at 11:25 #

    Oi Mariane,

    Verdade, não coloquei um README, né!? Mas acabei de colocar, super simples, veja se funciona pra você.

    E se rodar no seu projeto, me manda um e-mail, pq tenho interesse em ver o resultado, ok?

    Um abraço!

  9. Paulo Júnior 19/12/2014 at 21:22 #

    Parabéns pelo post e pelo artigo. Só fiquei com uma dúvida a respeito da diferença entre os termos (ou melhor, das metáforas) “technical debt” e “code smell” ou “bad smell”. Os dois últimos vocês não citam no artigo, mas são parecidos com o conceito de “technical debt”.

    Sei que Martin Fowler fala sobre esses três termos, mas não consegui achar (em uma busca superficial, confesso) a diferença entre eles. Vocês sabem algo a respeito?

    Por exemplo: dois code smells bastante conhecidos são “code duplication” e “god class”. Eles seriam technical debts?

    Obrigado. Paulo.

  10. Maurício Aniche 05/01/2015 at 14:40 #

    Oi Paulo,

    Technical Debt é o termo genérico para toda aquela má decisão que você tomou no passado, e que faz mal pro seu código.

    E você pode tomar várias más decisões, certo? Desde escrever um método com 300 linhas de código, até colocar métodos nos DAOs errados. Code smells são um possível technical debt.

    O ponto é que technical debts é algo em um nível maior ainda. Dê uma olhada nos artigos do MTD 2014. Lá tem um artigo, de autoria brasileira, sobre possíveis categorias de dívidas técnicas.

    Um abraço!

  11. Dalton 05/01/2015 at 14:49 #

    Interessante a ferramenta.
    Eu costumo utilizar a seguinte convenção: Se o método retorna X ou List (ou se for pra salvar, atualizar ou deletar X) então ele tem que estar no DAO de X, mesmo que o contexto da busca leve a utilizar um DAO Y. Por exemplo, eu tenho uma turma e quero buscar os alunos dela (como já falaram nos comentários). Como eu estou buscando os alunos da turma, eu repasso essa tarefa ao DAO de Alunos, passando uma Turma como parâmetro, em vez de buscar os alunos da turma no DAO Turma. Essa convenção é imparcial a contextos e é eficaz pra mim.

  12. Fred 06/01/2015 at 10:51 #

    Como praticamente todos disseram, é bem difícil falar o que é correto (ou mais correto) e errado. Eu sempre penso no domínio, o método que preciso criar se encaixa mais em qual domínio, Aluno ou Turma? Se ficar difícil chegar a essa conclusão eu faço o que o R.Lacerda falou, se ainda ficar difícil, ai eu tento levar em consideração o contexto da aplicação como um todo. Quando faz sentido em mais de 1 DAO, eu posso colocar no DAO que eu ache que vai crescer menos, mais fácil para manter.

  13. Mateus Medice 06/01/2015 at 16:50 #

    Muito bom o post. Parabéns!

    Abs

  14. Lucas Lopes 07/01/2015 at 11:09 #

    Parabéns pelo artigo! É engraçado ver que existem outras pessoas que pensam da mesma forma pois sempre tentei repassar estas técnicas de decisão da DAO aos membros da minha equipe.

    Nos meus projetos ainda existe um outro caso que acontece em consultas para relatórios consolidados. Normalmente estas consultas são complexas, envolvem muitas entidades, são grandes e retornam DTOs. Neste caso, a fim de não “poluir” as outras DAOs eu crio uma específica para o relatório em questão ou em alguns caso um DAO para vários relatórios.

  15. Danilo Muñoz 05/02/2015 at 09:15 #

    Excelente artigo! Parabéns!

    Uma outra checagem relativa aos DAOs que acho interessante, é realizar a contagem das conexões utilizadas e a devolução destas. Por exemplo: MyPool#getConnection / MyPool#putConnection ou MyPool#getConnection / connection.close()

    Eu sei que existem meios do próprio pool de conexões alertar quando uma conexão não é encerrada, mas creio que quando falamos de checagem de código, esta é uma análise simples e que pode ajudar a prevenir problemas futuros.

Deixe uma resposta