Comprendiendo las diferencias entre DAO, Repository y Active Record
23-01-2025
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): Separación de la lógica de acceso a datos
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.
Repository abstracción de entidades y conjuntos de datos
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.
Repository y su relación con Domain Driven Design (DDD)
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.
Beneficios en el contexto de DDD
- Separación de responsabilidades: El Repository permite separar la lógica del dominio de los detalles de acceso a los datos, lo que conduce a un diseño más claro y mantenible.
- Facilita el modelado del dominio: Al ocultar los detalles de como sucede la persistencia, el Repository simplifica la interacción con el modelo del dominio, permitiendo un enfoque más centrado en el negocio.
- Coherencia en la gestión de entidades: Proporciona métodos cohesivos para operar con entidades específicas del dominio, manteniendo una interfaz consistente para su gestión.
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.
Active Record: Integración de datos y lógica 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.
Relación de Active Record con los ORM
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.
Diferencias clave y aplicaciones:
-
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.
¿Cuándo usar qué?
DAO
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.
Repository
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.
Active Record
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.
Conclusión
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í