Por Aitor Santana
En el mundo del desarrollo de software, la gestión eficiente de datos es fundamental. Dentro de este contexto, los patrones de diseño como Data Access Object (DAO), Repository y Active Record desempeñan roles vitales al abordar la persistencia y el acceso a datos. Sin embargo, cada uno tiene enfoques distintos y se adapta de manera diferente a los proyectos. Profundicemos en las diferencias clave entre ellos, ya que son conceptos tan similares que muchas veces podemos llegar a confundirnos.
Data Access Object (DAO) se centra en separar la lógica de acceso a datos de la lógica de negocio. Proporciona una interfaz para interactuar con una fuente de datos específica, como una base de datos, ocultando los detalles de la implementación. Su objetivo principal es abstraer la interacción de la base de datos y proporcionar métodos para operaciones CRUD (Create, Read, Update, Delete).
Ejemplo de un DAO
public interface UserDAO {
User getById(UUID id);
List<User> getAll();
void save(UserDto user);
void update(User user);
void delete(UUID id);
}
Una posible implementación para ese DAO podría ser esta:
@Repository
public class PostgresUserDAOAdapter implements UserDAO {
private static final String ID = "id";
private static final String USERNAME = "username";
private static final String EMAIL = "email";
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public PostgresUserDAOAdapter(NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
@Override
public User getById(UUID id) {
return namedParameterJdbcTemplate.query("SELECT * FROM \"user\" WHERE id = :id",
Map.of(ID, id.toString()),
rs -> {
rs.next();
return new User(UUID.fromString(rs.getString(ID)), rs.getString(USERNAME), rs.getString(
EMAIL));
});
}
// Here the implementation of the other methods
@Override
public List<User> getAll() {}
@Override
public void save(UserDto user) {}
@Override
public void update(User user) {}
@Override
public void delete(UUID id) {}
}
En este caso tenemos una implementación de Postgres hecha en Spring, la cual nos va a proporcionar todas las operaciones necesarias para interactuar con nuestra entidad Usuario.
Por otro lado, el patrón Repository se centra en abstraer un conjunto de entidades o modelos de la aplicación. Proporciona una interfaz para acceder a un conjunto específico de datos, ofreciendo métodos para operaciones relacionadas con el dominio y la gestión de estas entidades.
Ejemplo de Repository
public interface UserRepository {
User findById(UUID id);
List<User> findAll();
void save(UserDto user);
void delete(UUID id);
}
Veamos un ejemplo de implementación en el que, además, sea necesario extraer datos de dos fuentes diferentes:
@Component
public class RestApiDAOAdapter implements UserRepository {
private final PostgresUserDAOAdapter userDAO;
private final UserClient userClient;
public RestApiDAOAdapter(PostgresUserDAOAdapter userDAO, UserClient userClient) {
this.userDAO = userDAO;
this.userClient = userClient;
}
@Override
public List<User> findAll() {
List<User> users = userDAO.getAll();
List<User> usersFromApi = userClient.retrieveUsers().stream().map(user -> new User(UUID.randomUUID(),
user.username(), user.email())).toList();
users.addAll(usersFromApi);
return users;
}
// Here the implementation of the other methods
@Override
public User findById(UUID id) {}
@Override
public void save(UserDto user) {}
@Override
public void delete(UUID id) {}
}
En este ejemplo tenemos la implementación de un repositorio, que por un lado se apoya en el DAO que creamos antes para interactuar con la base de datos y por el otro, hace una llamada a una API para traerse datos de usuarios que teníamos almacenados en otra fuente diferente. El repositorio hace nexo de unión entre las dos fuentes de infraestructura y nos devuelve lo que necesita nuestro dominio.
A diferencia del DAO, el Repository está más orientado al dominio y no necesariamente se asocia con una única fuente de datos. Puede manejar la lógica de obtener datos de múltiples fuentes y realizar operaciones específicas relacionadas con el dominio de la aplicación.
El Repository, en el contexto de Domain Driven Design (DDD), desempeña un papel crucial al encapsular la lógica de persistencia y consulta de las entidades y agregados del dominio. Su objetivo es proporcionar una capa de abstracción para interactuar con objetos del dominio sin exponer los detalles de implementación de la capa de persistencia.
En resumen, el Repository, especialmente en combinación con DDD, proporciona una capa de abstracción poderosa y flexible para gestionar entidades, permitiendo un desarrollo más estructurado y mantenible en aplicaciones orientadas al modelo de negocio.
El Active Record es un patrón que fusiona la representación de datos y la lógica de acceso en un solo objeto. Cada instancia del objeto Active Record refleja una fila en una tabla de la base de datos, integrando no solo los campos de la base de datos, sino también métodos para acceder, modificar y validar los datos.
Ejemplo de Active Record
@Component
public class PostgresUserActiveRecord {
@Value("${data.username}")
private String dataBaseUser;
@Value("${data.password}")
private String password;
private String id;
private String username;
private String email;
private Connection connection;
@PostConstruct
public void init() {
try {
connection = DriverManager.getConnection("jdbc:postgresql://localhost:5432/data_patterns",
dataBaseUser, password);
} catch (SQLException e) {
e.printStackTrace();
}
}
public User findById(UUID id) {
try(PreparedStatement ps = connection.prepareStatement("SELECT * FROM \"user\" WHERE id = ?")) {
ps.setString(1, id.toString());
ResultSet rs = ps.executeQuery();
if (rs.next()) {
return new User(UUID.fromString(rs.getString("id")), rs.getString("username"),
rs.getString("email"));
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
// Here other methods to interact with User Entity
public void save() {}
public void update() {}
public void delete() {}
}
El Active Record simplifica la interacción con la base de datos al tener una interfaz directa, pero puede generar un acoplamiento fuerte entre la lógica de la aplicación y la estructura de la base de datos.
El patrón Active Record es comúnmente utilizado por muchos Object-Relational Mapping (ORM). Los ORM, como Hibernate en Java, Entity Framework en .NET, SQLAlchemy en Python, entre otros, adoptan y aplican el patrón Active Record para mapear directamente las filas de la base de datos a objetos en el código.
La idea central detrás de un ORM es permitir que los desarrolladores trabajen con la base de datos utilizando objetos en lugar de escribir consultas SQL directamente. Estos frameworks se encargan de la asignación (mapeo) entre las estructuras de datos en la base de datos relacional y las representaciones de objetos en el lenguaje de programación.
Estás librerías adoptan este patrón para simplificar el desarrollo de la interacción con la base de datos a través de objetos en el código.
Enfoque y abstracción: DAO se centra en operaciones CRUD específicas para una fuente de datos, Repository proporciona una abstracción de entidades y operaciones orientadas al dominio, mientras que Active Record fusiona la representación de datos y la lógica de acceso en un solo objeto.
Relación con la fuente de datos: DAO se asocia típicamente con una fuente de datos específica, Repository puede manejar múltiples fuentes de datos y operaciones de dominio, y Active Record representa filas de la base de datos como objetos.
Si lo que queremos es interactuar únicamente con una base de datos con operaciones CRUD simples, este patrón encaja perfectamente.
Un posible escenario en el que podríamos aplicarlo es el de un sistema para la gestión de inventario, que lo único que necesite sea realizar operaciones básicas como agregar, eliminar o actualizar productos.
Para aplicaciones más complejas donde se deben manejar múltiples fuentes de datos, como bases de datos, servicios web o sistemas de archivos. Ideal para un dominio de negocio complejo.
Podríamos usarlo en un sistema de comercio electrónico que necesite acceder a diferentes fuentes de datos para gestionar productos, usuarios y pedidos, además de realizar operaciones específicas como buscar productos por categoría o usuarios por tipo.
Si la lógica de negocio y la estructura de la base de datos están altamente relacionadas, y la simplicidad en el acceso a datos es prioritaria por lo general es mejor usar este patrón.
Un posible ejemplo podría ser en una aplicación de gestión de blogs donde la estructura de las publicaciones refleja directamente la estructura de la base de datos. Cada publicación es un objeto que tiene métodos para editar, eliminar o recuperar comentarios asociados directamente desde la base de datos.
En resumen, cada patrón tiene su enfoque, la elección entre ellos depende de las necesidades del proyecto y la estructura de los datos. Comprender estas diferencias nos ayudará a tomar mejores decisiones a la hora de desarrollar la arquitectura de nuestro proyecto.
Los ejemplos totalmente desarrollados están disponibles aquí
¿Quieres más? te invitamos a suscribirte a nuestro boletín para avisarte cada vez que recopilemos contenido de calidad que compartir.
Si disfrutas leyendo nuestro blog, ¿imaginas lo divertido que sería trabajar con nosotros? ¿te gustaría?
Pero espera 🖐 que tenemos un conflicto interno. A nosotros las newsletter nos parecen 💩👎👹 Por eso hemos creado la LEAN LISTA, la primera lista zen, disfrutona y que suena a rock y reggaeton del sector de la programación. Todos hemos recibido newsletters por encima de nuestras posibilidades 😅 por eso este es el compromiso de la Lean Lista