Artigo no estilo Mentoring
Neste tipo de arquitetura, quando é necessário montar um CRUD para uma entidade Produto, cria-se uma interface chamada ProdutoService, por exemplo, que expõe os métodos responsáveis pela persistência. A implementação desta interface usa os objetos DAO para a execução das queries.
Em alguns casos, ainda são definidos DTOs (Data Transfer Objects) como retorno dos métodos da interface responsáveis por encapsular os dados dos objetos, diminuindo assim o número de chamadas ao DAO e motivando um aumento da performance. Certamente, esse tipo de abordagem, dado a sua complexidade, causa certo overhead na produção de um CRUD que não exija muita lógica de negócios envolvida.
Diante disto, este artigo mostrará uma forma mais produtiva para criação de CRUDs, usando Generics e deixando o DAO de lado.
Qual programador Java nunca foi incitado no debate sobre a complexidade, burocracia e “baixa produtividade” frente aos adeptos de outras tecnologias? De fato, a agilidade no desenvolvimento Java foi diversas vezes questionada e colocada à prova em vários momentos de sua história.
Em muitos casos, houve propensão ao uso de linguagens como PHP ou .NET em projetos de baixa complexidade, aqueles que podiam ser facilmente resolvidos com meia dúzia de CRUDs. De fato, a tecnologia Java adquiriu a fama de ser menos produtiva devido a sua versão “Enterprise” e o esforço contínuo de transformar tudo em componentes.
O advento do paradigma de “convenção sobre configuração” e das linguagens ágeis na JVM como Grails, Scala e ferramentas como o Spring Roo, vieram a dar um basta nesse assunto de uma vez por todas, já que se tornou possível criar pequenas aplicações em poucos minutos.
Além disso, a cada versão do JDK notamos melhorias significativas em benefício da produtividade, como as expressões lambda no recém-lançado Java 8.
Dentre essas melhorias está a introdução dos Generics no lançamento da J2SE 5. Generics foi uma evolução sem precedentes e permitiu aos programadores criar soluções baseadas em algoritmos generalizados para assim poupar muitas linhas de código.
Uma destas soluções é a que será mostrada neste artigo. Esta solução, além de aperfeiçoar o design da aplicação, se tornou uma “mão-na-roda” para a criação de CRUDs, pois com apenas uma interface e sua implementação será possível obter toda a infraestrutura para um CRUD de uma determinada entidade.
Uma das motivações para construção desta solução é o aumento da produtividade no desenvolvimento de projetos em Java. Muitas empresas contam com equipes multidisciplinares capazes de desenvolver projetos em diferentes tecnologias, como Java, .NET, PHP, Python, etc.
Geralmente nestas empresas o perfil do projeto afeta na decisão de qual tecnologia será usada, e em muitos casos o prazo de entrega é um dos fatores decisivos. Vamos supor que em algumas destas empresas apareça um projeto que possa ser resolvido apenas com alguns CRUDs, ou seja, sem a complexidade suficiente que justifique o uso de uma tecnologia enterprise como Java.
E também que o prazo de entrega fosse um fator determinante. Se você já vivenciou esse tipo de situação e assistiu desiludido a escolha por outra tecnologia que não fosse Java para a execução do projeto, certamente não está sozinho. Esse cenário já se repetiu inúmeras vezes, até porque, de fato, pode ser mais trabalhoso montar uma aplicação em Java comparado a outras tecnologias.
Analisando pela ótica da produtividade, como você implementa os seus CRUDs em Java? Certamente existem inúmeras formas diferentes, e uma das arquiteturas mais vistas por aí é com Spring e Hibernate.
Esta consiste na criação de serviços responsáveis pelas regras de negócio que delegam a função de persistência para os DAOs (Data Access Objects), como veremos no tópico a seguir.
O CRUD tradicional
Devido à solução supracitada e outras questões, o pattern DAO tornou-se bastante popular na plataforma Java. Um objeto que implementa o padrão DAO segue o princípio da responsabilidade única (Single Responsibility Principle) para isolar toda a lógica de acesso ao banco de dados dentro de um determinado domínio.
É nele que são incluídas as queries do banco de dados para buscar, incluir, atualizar e excluir dados, de uma forma transparente para o resto da aplicação.
Nesta arquitetura, os serviços responsáveis pelas regras de negócio são instanciados seguindo o princípio de IoC (Inversion of Control) para serem usados nas outras camadas da aplicação. Por sua vez, os DAOs são injetados nos serviços para serem usados na lógica dos métodos.
Figura 1. Diagrama de classes exemplo para um CRUD de Produto.
A Figura 1 mostra um exemplo de como ficaria uma camada de negócios para um CRUD de uma classe Produto. Basicamente, temos a interface de serviços ProdutoService, que define os métodos para salvar, excluir e buscar produtos. Temos também a implementação de ProdutoService, denominada ProdutoServiceImpl, que possui um atributo do tipo ProdutoDAO e que é injetado via IoC.
Assim como ProdutoService, a interface ProdutoDAO também deve conter os métodos para salvar, excluir e buscar produtos, pois serão usados pela ProdutoServiceImpl.
E por fim, temos a implementação de ProdutoDAO, denominada ProdutoDAOImpl, que pode usar classes do tipo HibernateDAOSupport nas implementações da interface para persistência dos objetos.
Da mesma forma que foi feito para o CRUD de Produtos, esta estratégia pode ser replicada para diversas outras entidades. A Figura 2 exemplifica um CRUD de Clientes exatamente igual ao de Produtos.
Com o tempo percebemos que as operações de persistência das entidades de nossos sistemas tendem a ser muito parecidas, senão idênticas. Por isso, montar um CRUD acaba sendo um processo repetitivo e pouco produtivo, simplesmente repassando chamadas de métodos de uma classe para outra, principalmente quando existem poucas regras de negócio específicas para cada entidade.
Figura 2. Diagrama de classes exemplo para um CRUD de Cliente.
Esse tipo de arquitetura é muito encontrada em projetos mais antigos e em empresas onde a análise da arquitetura de referência é colocada de lado frente àcorreria do dia a dia.
Em alguns casos, ainda é inserido o pattern Transfer Object (TO ou DTO) da J2EE para encapsular os dados trafegados entre os métodos, causando aumento na complexidade. Esse tipo de arquitetura é o caso perfeito para ser substituído pela abordagem de algoritmos genéricos, como a que será apresentada a seguir.
Tal abordagem consiste em montar um componente para fornecer uma infraestrutura que facilitará a criação de CRUDs. Embora existam algumas variações desta solução, a que será apresentada é uma das mais ...