Vale a pena abstrair? – Parte 2

O post “Vale a pena abstrair?” gerou muito mais comentários do que eu esperava. Infelizmente não pude postar uma resposta antes devido a diversas razões, mas agora vamos finalmente ao round 2 da discussão.

Primeiro, vamos deixar claro o objetivo do post anterior, pois muitos de vocês aparentemente entenderam algo diferente do que eu quis dizer (ou eu escrevi algo diferente do que eu estou pensando :-P). A minha intenção era mostrar que criar diversas interfaces e factories e camadas de abstração indiscriminadamente, na ilusão de que isso tornará mais fácil fazer mudanças no futuro, simplesmente acrescenta complexidade ao código e acaba sendo inútil no final. Ponto. Foi só isso. Os DAOs foram um exemplo, e eu os citei porque antigamente (3-4 anos atrás, veja só), era costume ter uma DAOFactory, que era uma interface, uma OracleDAOFactory, que era a implementação, o UsuarioDAO, interface, e o UsuarioOracleDAO, a implementação. Contudo, foi um exemplo infeliz, porque quase todos os comentários focaram no exemplo ao invés do assunto que ele ilustrava, mas houve vários pontos interessantes que gostaria de comentar. Vamos um de cada vez:

Fabio Kung:

Eu discordo no ponto de não abstrair o framework de ORM. Acho útil abstrair não para trocar fácil de banco de dados, mas sim para otimizações que possam ser necessárias. É comum acontecerem casos que você precisa abrir mão das vantagens do ORM e acessar o banco direto via jdbc, ou persistir o objeto em um lugar diferente (num arquivo xml, por exemplo) porque o banco é legado e você não pode criar outra tabela).

Nada num DAO sem interface impede isso. Se eu declarar meu DAO como:


public class UmDAOQualquer  {
    // ...
    public List<EntidadeQualquer> findByAlgumCriterio(String umaPropriedadeQualquer) {
        // ...
    }
}

O que te impede de implementar o método com JDBC ou XML? No caso de exceções, pode-se usar um modelo genérico de exceção ou ainda as exceções específicas disponíveis nas novas versões do Hibernate. Não é necessário ter interfaces pra isso.

Rubem Azenha:

Michael, o problema é que o pessoal as vezes pode usar isso como desculpa para fazer as coisas mal feitas…

No caso o DAO por exemplo, eu prefiro usar DAOs, com interface, abstraido e tudo mais. A questão não é só “e se a gente trocar de banco” mas também da testabilidade e da manutenabilidade (existe essa palavra?) da aplicação. Se você colocar no teu objeto de negócio a session direto do Hibernate, como você vai fazer os seus testes unitários? E se der um problema? Será que é no DAO ou na classe de negócio? Com o código isolado, fica *bem* mais fácil encontrar o problema, bem mais fácil de dar manutenção, bem mais fácil de reaproveitar.

Veja o ponto acima sobre interfaces. Para testes unitários, você pode criar seu DAO como subclasse do original, usar um framework de mock objects ou algo do gênero. Contudo, essa questão de testes unitários e a distinção entre negócio e acesso a banco de dados é interessante.

Por exemplo, em projetos que usam DAO extensivamente, já vi várias vezes classes de negócio que simplesmente delegavam para os DAOs, sem adicionar lógica nenhuma (as vezes convertiam exceções, mas isso deveria ser feito com aspectos, anyway). Nesse caso, o uso de DAOs é completamente desnecessário.

Em diversas situações, quando existem DAOs e classes de negócio interagindo, normalmente você tem, efetivamente, um problema de purismo OO. Eu sou a favor de manter a lógica em Java quando faz sentido, mas esse “quando faz sentido” é o que a maioria nunca aplica. Por exemplo, aplicar lógica de negócios em Java onde será necessário carregar um grande número de entidades em memória somente para filtrá-las quando isso poderia ser feito num select simplesmente limita a escalabilidade da aplicação.

Em outros casos, os metódos do DAO são resultado de otimizações e não um design natural. Por exemplo, tinha-se um processo lento em Java; muda-se isso para stored procedures em banco e, além de perder legibilidade, a performance degrada e faltam recursos presentes na linguagem. Então chega-se a um modelo híbrido em que parte é feita pela classe de negócios e parte pelo banco, através do DAO. E aí está o ponto: neste caso, testes unitários têm muito pouco valor, pois é a interação inteira que importa. Um teste integrado seria muito mais interessante neste caso ou talvez o DAO devesse ser eliminado e os métodos movidos para a classe de negócio.

Por fim, não estou condenando a separação entre DAO e camada de negócio; apenas mencionando que os casos mais comuns, na verdade os DAOs não deveriam existir ou a separação agrega pouco valor ao design, as vezes simplesmente tornando o mesmo mais complexo. Concordo que a separação deve existir sempre que necessário, mas a necessidade de interfaces em si só costuma surgir em raros casos.

Gyowanny:

Todos os argumentos são válidos, mas e se eu não quiser usar Hibernate? Se vocês só trabalharam a vida toda com aplicações de grande porte, usando JEE e tudo o mais, com certeza um framework de mapeamento O/R se faz necessário, mas já tive casos, com aplicações JSE de médio porte, onde o Hibernate deixou a aplicação pesada e então fez-se necessário utilizar o acesso a JDBC direto, ou seja, se eu tivesse abolido as interfaces DAO da aplicação o esforço para efetuar essa mudança seria muito maior, sem contar que eu perco a liberdade de poder usar outros frameworks de persistência.

Alguns esquecem que um dos requisitos básicos para um sistema bem projetado é a existência de acomplamento fraco ou abstração entre as camadas , principalmente na camada em questão.

Bem, aqui temos diversos pontos interessantes. Primeiro, que o Hibernate deixa sistemas de médio porte pesados. Eu já fiz alguns sistemas menores com Hibernate para uso pessoal e nunca tive problemas desse tipo. Normalmente, os problemas de performance que se tem com Hibernate são causados por falta de configuração apropriada e/ou mau uso das funcionalidades. Contudo, como não conheco o projeto, vou assumir que sua afirmação está correta.

Partindo desse pressuposto, se o problema de performance foi descoberto depois de uma parte significativa do código ter sido escrita, então o uso dos DAOs não foi um benefício para o projeto: o erro foi a falta de testes de performance, quer na definição da arquitetura, quer durante o desenvolvimento. Se o problema fosse detectado no começo do projeto, provavelmente não haveria diferença de tempo na substituição do Hibernate por outra solução, até porque você tem que levar em conta o tempo requerido para projetar e implementar os DAOs na comparação.

Não estou condenando o uso de DAOs nesse projeto especificamente, mas sim mostrando que o motivo usado para justificar o seu uso não mostra nenhum benefício real e sim que existia um outro problema.

Continuando…

kalecser:

Se não abstrair o acesso aos dados, seja ele via framework OR ou não, é impossível testar unitariamente o código, é impossível entregar uma funcionalidade sem modelar a parte de acesso a dados e a sua aplicação terá, de cara, uma dependência estática ainda mais gosmenta que as ditas interfaces e abstrações, o banco de dados :P

Sim, isso concordo. Mas na realidade, dificilmente não usamos Hibernate, que é um framework ORM, então criar factories e interfaces e níveis e níveis de abstração não ajuda aqui…

Emerson:

Eu uso a abstração para ler de duas fontes de dados de maneira transparente. Ex: Aqui onde trabalho usamos oracle e arquivos VSAM do mainframe. Determinado Business Object pode ser persistido ou em VSAM ou no Oracle. Pode ser feito um create() de um VSAM ou da base Oracle. Isso tudo por motivos que não vem ao caso aqui, porém é a nossa necessidade. Para nós é util essa abstração.

….

No meu caso como é Oracle e VSAM o Hibernate não resolve meu problema heheh.

Sim, Emerson, o seu caso parece mesmo ser uma excelente exemplo de benefício do uso dos DAOs e, nesse caso, até mesmo de interfaces. Aí sim vale a pena abstrair, porque você tem um caso de uso que justifica isso.

Um outro exemplo seria num produto que deve ser estendido pela equipe de desenvolvimento de quem compra e que pode se reutilizar das classes usadas por outros projetos do cliente. Nesse caso, também seria importante poder trocar o mecanismo de persistência.

Marcos Silva Pereira:

No meu caso, não é apenas abstrair o banco de dados, mas abstrair o acesso aos dados. Definir uma interface é mesmo desnecessario se vc já usa um ORM da vida, mas ao menos no meu caso, acho valido criar um componente para eu dizer: ei, me dê os dados e não me preocupar se ele usa criteria, hql, jdbc ou o que seja. A responsabilidade é dele, quero apenas os dados.

É justamente aí que talvez DAOs tenham uma utilidade que não seja necessariamente abstrair O banco de dados mas sim o acesso aos dados. É mais sobre Separation of Concerns do que sobre ter que mudar o tipo de repositorio.

Comentário interessante, mas que faz sentido somente quando não se cai nas situações que citei acima. Clareza e separação de responsabilidades são importantes; agora ficar apenas delegando e delegando métodos sem acrescentar comportamento é simplesmente adicionar complexidade ao código.

Os outros comentários foram mais ou menos semelhantes (ou concordaram comigo, hehehe), então não vou citá-los aqui. Se aqueles que escreveram acharem que esqueci de algum ponto, podem comentar nesse post.

Em resumo, só crie uma abstração quando ela fizer sentido para você. De forma mais prática: a menos que esteja desenvolvendo um produto ou framework, abstraia somente se houver mais de uma implementação e não apenas a “possibilidade de”, “e se um dia eu precisar” etc.

4 Responses to “Vale a pena abstrair? – Parte 2”

  1. “Por exemplo, em projetos que usam DAO extensivamente, já vi várias vezes classes de negócio que simplesmente delegavam para os DAOs, sem adicionar lógica nenhuma…” – eu vejo isso _sempre_, dia e noite! mais ainda, antes da classe de negócio tem de passar por uns 3 delegates diferentes, todos interfaceados…

  2. Thiago R S disse:

    Olá,

    muito interessante estes posts sobre abstração de DAO’s. Quero dizer que estou com vocês. A máxima abstração que costumo chegar é criar uma interface para o DAO e sua respectiva implementação. Isso quando devo usar DAO’s.

    Para o restante dos casos, estou evitando o pattern discutido. Prefiro colocar logo o hibernateTemplate (Spring) ou EntityManager (JPA) na minha camada de serviço. Depois garanto se tudo funca com um teste de integração. Testes unitários apenas em trechos que exigem uma atenção especial.

    Também tenho pouco interesse por DAO’s genéricos, mas isso é questão de gosto.

    Abraços
    Thiago

  3. Ramon Chiara disse:

    Creio que o que vc já disse no 1o post é certo!
    Não seja arquiteto/programador vidente, KISS, etc. etc. etc… :-)
    No entanto faltou o ponto mais importante disso tudo: como vc vai saber que sua aplicação precisa ou não de separação das interfaces? Veja, não só DAO, mas qquer uma (bom, vc já tinha dito isso rsrs)
    Nisso o TDD (independentemente de XP) ajuda!
    Se vc ir montando o teste, programando do jeito que vc quer que a coisa funcione, a arquitetura vai surgindo de uma forma muito bacana, onde será testável e simples. Provavelmente só vai surgir o DAO quando vc precisar mesmo. Lógico que tudo isso requer prática e experiência, como tudo na vida ;-)
    []s
    PS: não estou aqui pregando TDD, hein! :-) Só estou dizendo que é uma opção para que vc descubra em que pontos sua aplicação vai precisar de separação das interfaces!

  4. [...] Obviamente não estou pregando a total extinção de todos esses patterns (muito menos o DAO!), apenas lembrando que devemos medir muito bem a necessidade de criar mais classes e mais abstrações, em vez de atacar o problema com mais simplicidade, como o Michael Nascimento também já blogou (duas vezes). [...]