public interface ProductDao {
public Optional<ProductTable> findById(Long id)
public List<ProductTable> findActiveBySegmentId(Long segmentId)
}
Encapsula a tecnologia de acesso a dados estruturados, normalmente a banco de dados.
O padrão DAO - Data Access Object nasceu com a tecnologia J2EE e faz parte do catálogo Core J2EE Patterns de 2001. Ele nasce da necessidade de acesarmos bancos de dados de uma forma homogenea e orientada a objetos num mundo em que cada banco tem a sua própria tecnologia, paradigma e regras. O padrão pode ser utilizado sempre que falamos de dados estruturados sejam eles persistidos num simples arquivo local ou num banco de dados na nuvem.
Cada tipo de banco de dados conta com uma tecnologia especifica para o acessar. No mundo SQL a tecnologia já evoluiu ao ponto de termos drivers que fazem a mediação entre uma tecnologia com interfaces fixas e a tecnologia do banco de dados. Em Java temos o JDBC e em .NET o ODBC. Mas para tecnologias noSQL cada fabricante tem seu próprio padrão. Nem sempre ha o conceito de tabela ou algo semelhante e nem sempre é possível utilizar frases SQL. Muitas vez é necessário usar um API especifica.
O uso de API especificas, mesmo que padronizadas como o JDBC, implica em nuances particulares a cada tecnologia. Estas nuances não fazem parte do nosso software, mas temos que seguir as suas diretrizes. O padrão DAO serve para isolar todas estas diferenças do nosso software.
Poderiamos pensar que o padrão DAO tem como objetivo permitir mudar de tecnologia subjacente. Por exemplo, de um banco SQL com JDBC para um banco noSQL como o Mongo. Esta possibilidade de mudança existe, mas é uma consequência se aplicar o padrão. O objetivo principal é realmente encapsular os detalhes tecnicos da comunicação com o banco de dados.
O padrão DAO é uma especificação do padrão Service e como tal a primeira coisa é definir uma interface. É a interface que nos dá acesso aos dados. Esta interface utiliza objetos e dados, normalmente classes com campos em que cada propriedade corresponde a um dado no banco de dados. As classes de dados são mapeadas manualmente com os objetos da API de persistencia.
Depois de definida a interface, uma implementação especifica é criada conforme a tecnologia subjacente.
Um exemplo simples de uma interface DAO:
public interface ProductDao {
public Optional<ProductTable> findById(Long id)
public List<ProductTable> findActiveBySegmentId(Long segmentId)
}
A forma mais comum de DAO é aquela que utiliza JDBC/ODBC como tecnologia de persistência. Aqui as instruções são passadas em SQL para o banco de dados e a resposta é obtida em objetos especificos da tecnologia. Esses objetos devem então ser mapeados para os objetos que foram definidos no contrato.
Um esqueleto de uma implementação seria
public class JDBCProductDao extends AbstractSqlDao implements ProductDao {
public Optional<ProductTable> findById(Long id) {
try(ResultSet rs = execute("SELECT * FROM ProductTable WEHRE id = ?", id)){
if (rs.next()){
return Optional.of(readProductTable(new ProductTable(), rs));
}
}
return Optional.empty()
}
private ProductTable readProductTable(ProductTable row, ResultSet rs){
//.. mapeamento dos campos e dos dados em ResultSet para ProductTable
}
public List<ProductTable> findActiveBySegmentId(Long segmentId){
try(ResultSet rs = execute("SELECT * FROM ProductTable WEHRE segmentId = ?", segmentId)){
var list = new ArrayList();
while (rs.next()){
list.add(readProductTable(new ProductTable(), rs)));
}
return list;
}
return Optional.empty()
}
}
Para simplificar, abstraimos na AbstractSqlDao
a parte chata de abrir a conexão e fazer uso da API JDBC para a comunicação. Na prática você teria que implementar essa parte.
O importante é que termos que lidar com um objeto ResultSet
onde estão os dados e mapeá-los para o objeto de dados. Aqui usamos o sufixo "Table" para os dados, mas poderia ser usado outro sufixo, ou até nenhum, o importante é entender que estas classes são usadas para mapeamento de dados e apenas para isso, elas não são classes de domínio, não são Entidades e não devem conter lógicas.
Uma técnica mais comum hoje em dia é utilizar o Spring Data. Esta tecnologia permite que você defina apenas a interface e os objetos para os dados, e seguindo algumas convenções o Spring Data vai gerar - dinamicamente - a implementação.
Esta ténica é muito usada porque permite que, no fim de contas, o programador não tenha que fazer o mapeamento dos dados ou trabalhar com a API JDBC manualmente. Também permite trabalhar com outras API que não a de JDBC, por exemplo, com Mongo.
Para que esta magia funcione o Spring Data apenas existe que:
A interface do DAO seja anotada com @Repository
Os objetos de dados tenham um contrutor público sem parametros
Os objetos de dados sejam mapeados com a tabela do banco de dados usando algumas anotações especificas.
Os nomes dos métodos sigam uma convenção de nomenclatura de forma que a querie possa ser gerada do nome do método.
Nada de novo, mas promove bastante acoplamento com os objetos de dados e interface do DAO.
O padrão DAO é um padrão clássico e frequentemente o primeiro padrão com que o programador se depara. Com a introdução do Domain Driven Design começou a haver uma confusão entre o padrão DAO e o padrão Repository porque parece que fazem o mesmo.
Na realidade o padrão DAO visa estabelecer um contrato de persistencia que pode ser cumprido por comunicação com um banco de dados (ou outra forma persitida estruturada). O Repository apenas permite fazer parecer aos objetos de domínio que eles estão persistidos em algum lugar sem ser expecifico qual o modelo dessas perisistência.
O padrão DAO costuma ser aplicado tabela por tabela, em que cada tabela tem a sua interface. Isto funciona bem a princípio, mas começa a ficar complicado para operações mais avançadas como relatórios onde o uso de de operações de join e de projeção são usadas, ou seja, quando os dados lidos não vêm apenas de uma tabela e/ou não são todos os dados da tabela.
É comum em certos designs ver contrados com o sufixo DAO sendo implementados via JPA/Hibernate. Isto é um pouco contra producente. O JPA implementa o padrão Domain Store que é um pouco mais avançado que o DAO. O Domain Store funciona como um DAO universal que funciona para todas as tabelas do modelo. No caso de utilizear JPA/Hibernate é possível criar implementações DAO facilmente - inclusive o SPring Data fará automaticamente - mas neste caso seria mais vatanjoso usar o padrão Domain Store diretamente no Repository e não usar o padrão DAO
O padrão DAO é uma especialização do padrão Service cuja responsabilidade é mediar com o modelo de persistência e a tecnologia subjacente.
Como vimos os padrões Repository e Domain Store são muito próximo e podem facilmente se confundir com o padrão DAO.
Outro padrão relacionado é o Fastlane Reader. Este é um padrão que visa otimizar os recursos sobretudo quando usamos um cursor - como é o ResultSet
- para lermos os dados. Este padrão permite que apenas uma linha da tabela seja lida por vez de forma que apenas uma linha é mantida em memória ao longo das várias camadas da aplicação. É um padrão especialmente indicado para relatórios onde muitas linhas podem ter que ser lidas.
O padrão Data Mapper pode também ser bastante útil para mapear de, e para, os objetos de persistencia, especialmente se esse mapeamento é manual.
Finalmente, se pensamos que o nosso software pode mudar de tecnologia de persistencia, é interessante utilizar um objeto no padrão Factory ou no padrão Abstract Factory para prover a implementação correta da interface do DAO conforme a tecnologia sendo usada. Esta técnica é tambem interessante para prover uma implementação que possa ser usada em ambientes de testes.
[1] CoreJ2EE Patterns - Data Access Object (https://www.oracle.com/java/technologies/dataaccessobject.html)
[2] Deepak Alur, John Crupi, Dan Malks Core J2EE Patterns: Best Practices and Design Strategies: Prentice Hall Professional (2003)
[3] Data Access Object (https://en.wikipedia.org/wiki/Data_access_object)