Паттерн DAO в Java: реализация и принципы для разработчиков

Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Java-разработчики, заинтересованные в улучшении архитектуры своих приложений
  • Специалисты по программированию, желающие внедрить паттерны проектирования в свои проекты
  • Архитекторы программного обеспечения, ищущие решения для управления сложной логикой доступа к данным

    Разработка корпоративных Java-приложений часто сталкивается с вызовом: как элегантно отделить бизнес-логику от механизмов доступа к данным? Здесь на сцену выходит Data Access Object (DAO) — архитектурный паттерн, позволяющий изолировать слой доступа к данным от остальной части приложения. Это не просто теоретическая концепция, а рабочий инструмент, значительно упрощающий разработку и поддержку кода. Давайте разберемся, как правильно реализовать и применить DAO в Java-проектах для создания гибкой, тестируемой и масштабируемой архитектуры. 🚀

Хотите уверенно внедрять паттерны проектирования в реальные Java-проекты? Курс Java-разработки от Skypro детально раскрывает не только DAO, но и другие архитектурные решения через практические задачи. Вы научитесь писать чистый, поддерживаемый код, который легко масштабируется. Уже через 3 месяца вы сможете применять промышленные стандарты проектирования в своих проектах!

Что такое паттерн DAO и зачем он нужен в Java

Data Access Object (DAO) — это структурный паттерн проектирования, который обеспечивает абстрактный интерфейс к источникам данных или механизмам хранения. Он изолирует бизнес-логику от логики работы с базами данных, создавая четкое разделение ответственности в коде. 📊

Представьте DAO как специализированного посредника, который знает все о том, как извлекать, сохранять и обновлять данные, но не вмешивается в то, как эти данные используются в бизнес-логике приложения.

Андрей Карпов, технический директор

Несколько лет назад мы столкнулись с проблемой, ставшей классической для многих команд — модернизация устаревшего Java-приложения с прямыми JDBC-запросами, разбросанными по всей кодовой базе. При каждом изменении структуры БД приходилось искать и обновлять запросы в десятках классов.

Внедрение DAO стало поворотным моментом. Мы сосредоточили весь код доступа к данным в специализированных классах с унифицированными интерфейсами. Когда через полгода потребовалось мигрировать с MySQL на PostgreSQL, изменения затронули только реализации DAO, а бизнес-логика осталась нетронутой. То, что раньше заняло бы недели, было выполнено за несколько дней.

Основные преимущества использования DAO в Java-проектах:

  • Инкапсуляция логики доступа к данным — детали работы с хранилищем скрыты за абстрактным интерфейсом
  • Упрощение тестирования — возможность легко подменять реальные реализации DAO на мок-объекты
  • Повышение гибкости архитектуры — возможность изменять источник данных без влияния на бизнес-логику
  • Снижение дублирования кода — централизация операций с данными
  • Улучшение сопровождаемости — чистое разделение ответственности между компонентами
Сценарий Без DAO С DAO
Изменение СУБД Необходимость правки SQL во многих классах Изменения только в реализациях DAO
Оптимизация запросов Поиск всех мест использования запроса Изменение одного метода в DAO
Модульное тестирование Сложная изоляция бизнес-логики от БД Легкое создание мок-реализаций DAO
Поддержка кода Высокая связность компонентов Четкое разделение ответственности

DAO особенно полезен в крупных корпоративных приложениях, где требуется работа с несколькими источниками данных или где возможны изменения в структуре хранения данных. Этот паттерн активно применяется в экосистеме Spring и широко распространен в проектах, использующих Java Persistence API (JPA).

Пошаговый план для смены профессии

Архитектура и ключевые компоненты паттерна DAO

Архитектура DAO строится на нескольких ключевых компонентах, которые совместно обеспечивают эффективное разделение ответственности. Правильно спроектированный DAO действует как мост между бизнес-логикой и механизмами хранения данных. 🌉

Основные элементы архитектуры DAO включают:

  • DAO интерфейс — определяет контракт для операций с данными
  • DAO реализация — конкретная имплементация интерфейса для работы с определенным типом хранилища
  • Модель данных (Entity) — объекты, представляющие структуры данных в приложении
  • Фабрика DAO (опционально) — компонент для создания конкретных экземпляров DAO
  • Источник данных — база данных, файловая система или другое хранилище

Рассмотрим типичную структуру DAO-компонентов:

Java
Скопировать код
// Модель данных
public class User {
private Long id;
private String username;
private String email;
// Геттеры, сеттеры, конструкторы
}

// DAO интерфейс
public interface UserDao {
User findById(Long id);
List<User> findAll();
void save(User user);
void update(User user);
void delete(Long id);
}

// Конкретная реализация DAO для JDBC
public class JdbcUserDao implements UserDao {
private DataSource dataSource;

public JdbcUserDao(DataSource dataSource) {
this.dataSource = dataSource;
}

@Override
public User findById(Long id) {
// Реализация с использованием JDBC
}

// Другие методы
}

// Фабрика DAO
public class DaoFactory {
public static UserDao createUserDao() {
return new JdbcUserDao(getDataSource());
}

private static DataSource getDataSource() {
// Настройка и получение источника данных
}
}

Такая структура обеспечивает несколько важных характеристик:

  1. Слабую связность — бизнес-логика взаимодействует только с интерфейсом DAO, не зная деталей реализации
  2. Взаимозаменяемость — различные реализации DAO могут использоваться без изменения клиентского кода
  3. Единую точку изменений — при изменении способа хранения данных модифицируется только соответствующая реализация
Компонент Назначение Пример в Java
DAO интерфейс Определение контракта доступа к данным interface UserDao
DAO реализация Конкретный механизм работы с данными class JdbcUserDao
Модель данных Представление бизнес-сущностей class User
Фабрика DAO Создание экземпляров DAO class DaoFactory
Источник данных Механизм доступа к хранилищу interface DataSource

DAO можно реализовывать разными способами в зависимости от технологического стека проекта. В экосистеме Java распространены реализации с использованием JDBC, JPA/Hibernate, Spring Data и других технологий.

Реализация DAO в Java: пошаговая инструкция с кодом

Давайте рассмотрим полную реализацию DAO на примере работы с сущностью "Продукт" в интернет-магазине, используя JDBC как базовую технологию доступа к данным. Я разобью процесс на последовательные шаги, которые вы можете адаптировать для своих проектов. 💻

Шаг 1: Создание сущности Product

Java
Скопировать код
public class Product {
private Long id;
private String name;
private String description;
private BigDecimal price;
private int stock;

// Конструктор без параметров
public Product() {}

// Конструктор с параметрами
public Product(Long id, String name, String description, BigDecimal price, int stock) {
this.id = id;
this.name = name;
this.description = description;
this.price = price;
this.stock = stock;
}

// Геттеры и сеттеры
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }

public String getName() { return name; }
public void setName(String name) { this.name = name; }

public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }

public BigDecimal getPrice() { return price; }
public void setPrice(BigDecimal price) { this.price = price; }

public int getStock() { return stock; }
public void setStock(int stock) { this.stock = stock; }
}

Шаг 2: Определение DAO интерфейса

Java
Скопировать код
public interface ProductDao {
Product findById(Long id) throws DaoException;
List<Product> findAll() throws DaoException;
List<Product> findByPriceRange(BigDecimal min, BigDecimal max) throws DaoException;
Long save(Product product) throws DaoException;
void update(Product product) throws DaoException;
void delete(Long id) throws DaoException;
}

Шаг 3: Создание класса исключений для DAO

Java
Скопировать код
public class DaoException extends Exception {
public DaoException(String message) {
super(message);
}

public DaoException(String message, Throwable cause) {
super(message, cause);
}
}

Шаг 4: Создание утилитарного класса для работы с базой данных

Java
Скопировать код
public class DatabaseUtil {
private static HikariDataSource dataSource;

static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/shop");
config.setUsername("username");
config.setPassword("password");
config.setMaximumPoolSize(10);
dataSource = new HikariDataSource(config);
}

public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}

public static void close(Connection conn, PreparedStatement ps, ResultSet rs) {
try {
if (rs != null) rs.close();
} catch (SQLException e) {
e.printStackTrace();
}

try {
if (ps != null) ps.close();
} catch (SQLException e) {
e.printStackTrace();
}

try {
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

Шаг 5: Реализация DAO с использованием JDBC

Java
Скопировать код
public class JdbcProductDao implements ProductDao {

@Override
public Product findById(Long id) throws DaoException {
String sql = "SELECT * FROM products WHERE id = ?";
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
conn = DatabaseUtil.getConnection();
ps = conn.prepareStatement(sql);
ps.setLong(1, id);
rs = ps.executeQuery();

if (rs.next()) {
return mapResultSetToProduct(rs);
} else {
return null;
}
} catch (SQLException e) {
throw new DaoException("Error finding product by ID: " + id, e);
} finally {
DatabaseUtil.close(conn, ps, rs);
}
}

@Override
public List<Product> findAll() throws DaoException {
String sql = "SELECT * FROM products";
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
List<Product> products = new ArrayList<>();

try {
conn = DatabaseUtil.getConnection();
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();

while (rs.next()) {
products.add(mapResultSetToProduct(rs));
}

return products;
} catch (SQLException e) {
throw new DaoException("Error finding all products", e);
} finally {
DatabaseUtil.close(conn, ps, rs);
}
}

@Override
public List<Product> findByPriceRange(BigDecimal min, BigDecimal max) throws DaoException {
String sql = "SELECT * FROM products WHERE price BETWEEN ? AND ?";
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
List<Product> products = new ArrayList<>();

try {
conn = DatabaseUtil.getConnection();
ps = conn.prepareStatement(sql);
ps.setBigDecimal(1, min);
ps.setBigDecimal(2, max);
rs = ps.executeQuery();

while (rs.next()) {
products.add(mapResultSetToProduct(rs));
}

return products;
} catch (SQLException e) {
throw new DaoException("Error finding products in price range", e);
} finally {
DatabaseUtil.close(conn, ps, rs);
}
}

@Override
public Long save(Product product) throws DaoException {
String sql = "INSERT INTO products (name, description, price, stock) VALUES (?, ?, ?, ?)";
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
conn = DatabaseUtil.getConnection();
ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, product.getName());
ps.setString(2, product.getDescription());
ps.setBigDecimal(3, product.getPrice());
ps.setInt(4, product.getStock());

int affectedRows = ps.executeUpdate();

if (affectedRows == 0) {
throw new DaoException("Creating product failed, no rows affected.");
}

rs = ps.getGeneratedKeys();
if (rs.next()) {
return rs.getLong(1);
} else {
throw new DaoException("Creating product failed, no ID obtained.");
}
} catch (SQLException e) {
throw new DaoException("Error saving product", e);
} finally {
DatabaseUtil.close(conn, ps, rs);
}
}

@Override
public void update(Product product) throws DaoException {
String sql = "UPDATE products SET name = ?, description = ?, price = ?, stock = ? WHERE id = ?";
Connection conn = null;
PreparedStatement ps = null;

try {
conn = DatabaseUtil.getConnection();
ps = conn.prepareStatement(sql);
ps.setString(1, product.getName());
ps.setString(2, product.getDescription());
ps.setBigDecimal(3, product.getPrice());
ps.setInt(4, product.getStock());
ps.setLong(5, product.getId());

int affectedRows = ps.executeUpdate();

if (affectedRows == 0) {
throw new DaoException("Updating product failed, no rows affected.");
}
} catch (SQLException e) {
throw new DaoException("Error updating product", e);
} finally {
DatabaseUtil.close(conn, ps, null);
}
}

@Override
public void delete(Long id) throws DaoException {
String sql = "DELETE FROM products WHERE id = ?";
Connection conn = null;
PreparedStatement ps = null;

try {
conn = DatabaseUtil.getConnection();
ps = conn.prepareStatement(sql);
ps.setLong(1, id);

int affectedRows = ps.executeUpdate();

if (affectedRows == 0) {
throw new DaoException("Deleting product failed, no rows affected.");
}
} catch (SQLException e) {
throw new DaoException("Error deleting product", e);
} finally {
DatabaseUtil.close(conn, ps, null);
}
}

private Product mapResultSetToProduct(ResultSet rs) throws SQLException {
Product product = new Product();
product.setId(rs.getLong("id"));
product.setName(rs.getString("name"));
product.setDescription(rs.getString("description"));
product.setPrice(rs.getBigDecimal("price"));
product.setStock(rs.getInt("stock"));
return product;
}
}

Шаг 6: Создание фабрики для DAO (опционально)

Java
Скопировать код
public class DaoFactory {
public static ProductDao createProductDao() {
return new JdbcProductDao();
}
}

Шаг 7: Пример использования DAO в сервисном слое

Java
Скопировать код
public class ProductService {
private ProductDao productDao;

public ProductService() {
this.productDao = DaoFactory.createProductDao();
}

public Product getProductById(Long id) throws ServiceException {
try {
return productDao.findById(id);
} catch (DaoException e) {
throw new ServiceException("Error getting product by ID", e);
}
}

public List<Product> getDiscountedProducts(BigDecimal maxPrice) throws ServiceException {
try {
return productDao.findByPriceRange(BigDecimal.ZERO, maxPrice);
} catch (DaoException e) {
throw new ServiceException("Error getting discounted products", e);
}
}

public Long createProduct(Product product) throws ServiceException {
try {
return productDao.save(product);
} catch (DaoException e) {
throw new ServiceException("Error creating product", e);
}
}
}

Этот пример демонстрирует полный цикл реализации DAO в Java с использованием JDBC. Для работы с другими технологиями (JPA, Spring Data) сама структура остается похожей, но детали реализации будут отличаться.

Ключевые моменты, на которые стоит обратить внимание:

  • Все операции с базой данных инкапсулированы в DAO-классе
  • Бизнес-логика (в сервисном слое) работает только с интерфейсами DAO
  • Ресурсы базы данных (соединения, запросы) корректно освобождаются
  • Используется специализированный класс исключений для DAO-уровня
  • Connection pool (HikariCP) применяется для эффективного управления соединениями

Практические аспекты применения DAO в Java-проектах

Переходя от теории к практике, рассмотрим ряд реальных сценариев и рекомендаций по эффективному использованию паттерна DAO в промышленных Java-проектах. 🛠️

Елена Соколова, архитектор программного обеспечения

В финтех-проекте, над которым работала наша команда, мы столкнулись с интересным вызовом: приложение должно было поддерживать разные хранилища данных для разных типов сущностей (SQL для транзакционных данных, NoSQL для аналитики, кэширование в Redis для часто запрашиваемой информации).

Мы использовали DAO как единую точку доступа к данным, создав для каждой сущности собственный интерфейс и несколько реализаций под разные хранилища. Поверх этого уровня мы построили слой "репозиториев", который управлял стратегией выбора конкретного DAO в зависимости от контекста и требований к производительности.

Этот подход позволил нам безболезненно масштабировать систему, добавляя новые хранилища без изменения бизнес-логики. Когда возникла необходимость перенести часть данных из MongoDB в Cassandra, мы просто добавили новую реализацию DAO, и вся система продолжала работать без единого изменения в сервисном слое.

Интеграция DAO с фреймворками и библиотеками

Паттерн DAO гибко интегрируется с различными Java-фреймворками:

  • Spring Framework — использование DAO с @Repository, абстракциями JdbcTemplate и транзакционным управлением
  • Hibernate/JPA — создание DAO поверх EntityManager для более простого управления персистентностью
  • Spring Data — расширение интерфейсов JpaRepository для автоматизации операций доступа к данным
  • MyBatis — комбинирование DAO с декларативными SQL-маппингами

Пример интеграции DAO с Spring и JPA:

Java
Скопировать код
@Repository
public class JpaProductDao implements ProductDao {

@PersistenceContext
private EntityManager entityManager;

@Override
public Product findById(Long id) {
return entityManager.find(Product.class, id);
}

@Override
public List<Product> findAll() {
return entityManager
.createQuery("SELECT p FROM Product p", Product.class)
.getResultList();
}

@Override
@Transactional
public void save(Product product) {
if (product.getId() == null) {
entityManager.persist(product);
} else {
entityManager.merge(product);
}
}

// Другие методы
}

Оптимизация производительности

При реализации DAO важно учитывать аспекты производительности:

Техника Применение Эффект
Пакетная обработка Операции с большими наборами данных Снижение количества обращений к БД
Ленивая загрузка Загрузка связанных сущностей по требованию Уменьшение объема получаемых данных
Кэширование Хранение результатов часто выполняемых запросов Повышение скорости доступа к данным
Пагинация Работа с большими списками данных Экономия памяти и уменьшение времени ответа

Пример реализации пагинации в DAO:

Java
Скопировать код
public interface ProductDao {
// Другие методы

List<Product> findAllPaginated(int offset, int limit) throws DaoException;
long count() throws DaoException;
}

public class JdbcProductDao implements ProductDao {
// Другие методы

@Override
public List<Product> findAllPaginated(int offset, int limit) throws DaoException {
String sql = "SELECT * FROM products LIMIT ?, ?";
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
List<Product> products = new ArrayList<>();

try {
conn = DatabaseUtil.getConnection();
ps = conn.prepareStatement(sql);
ps.setInt(1, offset);
ps.setInt(2, limit);
rs = ps.executeQuery();

while (rs.next()) {
products.add(mapResultSetToProduct(rs));
}

return products;
} catch (SQLException e) {
throw new DaoException("Error finding products with pagination", e);
} finally {
DatabaseUtil.close(conn, ps, rs);
}
}

@Override
public long count() throws DaoException {
String sql = "SELECT COUNT(*) FROM products";
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
conn = DatabaseUtil.getConnection();
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();

if (rs.next()) {
return rs.getLong(1);
}

return 0;
} catch (SQLException e) {
throw new DaoException("Error counting products", e);
} finally {
DatabaseUtil.close(conn, ps, rs);
}
}
}

Тестирование DAO

Эффективное тестирование DAO включает несколько подходов:

  1. Модульное тестирование с использованием in-memory БД (H2, HSQLDB)
  2. Интеграционное тестирование с тестовыми контейнерами (Testcontainers)
  3. Мок-тестирование для проверки взаимодействия сервисов с DAO

Пример тестирования DAO с использованием JUnit и H2:

Java
Скопировать код
@RunWith(SpringRunner.class)
@DataJpaTest
public class ProductDaoTest {

@Autowired
private ProductDao productDao;

@Test
public void testFindById() {
// Подготовка тестовых данных
Product product = new Product();
product.setName("Test Product");
product.setPrice(new BigDecimal("99.99"));
Long id = productDao.save(product);

// Выполнение тестируемого метода
Product foundProduct = productDao.findById(id);

// Проверка результата
assertNotNull(foundProduct);
assertEquals("Test Product", foundProduct.getName());
assertEquals(0, new BigDecimal("99.99").compareTo(foundProduct.getPrice()));
}

@Test
public void testFindByPriceRange() {
// Подготовка тестовых данных
Product p1 = new Product();
p1.setName("Cheap Product");
p1.setPrice(new BigDecimal("10.00"));
productDao.save(p1);

Product p2 = new Product();
p2.setName("Medium Product");
p2.setPrice(new BigDecimal("50.00"));
productDao.save(p2);

Product p3 = new Product();
p3.setName("Expensive Product");
p3.setPrice(new BigDecimal("100.00"));
productDao.save(p3);

// Выполнение тестируемого метода
List<Product> products = productDao.findByPriceRange(
new BigDecimal("20.00"), 
new BigDecimal("80.00")
);

// Проверка результата
assertEquals(1, products.size());
assertEquals("Medium Product", products.get(0).getName());
}
}

При внедрении DAO в реальных проектах стоит помнить о балансе между абстракцией и практичностью. Слишком тонкий слой DAO может не обеспечить достаточной изоляции, а излишне абстрактный может привести к "плоскому" API, не отражающему семантику домена.

Сравнение паттернов DAO и Repository: когда что выбрать

Паттерны DAO и Repository часто путают из-за схожей роли в архитектуре приложений. Однако между ними существуют концептуальные различия, влияющие на выбор подхода в конкретном проекте. 🧩

Основные отличия между этими паттернами:

Характеристика DAO (Data Access Object) Repository
Происхождение J2EE-паттерн, ориентированный на доступ к данным DDD-паттерн (Domain-Driven Design), ориентированный на домен
Фокус Инкапсуляция механизма хранения и запросов Представление коллекции объектов домена
Уровень абстракции Ближе к источнику данных, CRUD-ориентированный Ближе к бизнес-логике, ориентирован на предметную область
Гранулярность Обычно один DAO на сущность Может агрегировать несколько сущностей, следуя границам агрегатов
Методы Чаще низкоуровневые (findById, save, update) Часто доменно-специфичные (findActiveSubscriptions, markAsDelivered)

В терминах кода, типичный Repository может выглядеть так:

Java
Скопировать код
public interface OrderRepository {
Order findById(Long id);
List<Order> findByCustomer(Customer customer);
List<Order> findPendingOrders();
void save(Order order);
void markAsShipped(Order order, ShippingInfo shippingInfo);
}

Тогда как DAO будет более ориентирован на операции с данными:

Java
Скопировать код
public interface OrderDao {
Order findById(Long id);
List<Order> findAll();
List<Order> findByCustomerId(Long customerId);
void save(Order order);
void update(Order order);
void delete(Long id);
}

Когда выбирать DAO:

  • В проектах с простой доменной моделью, где сущности тесно связаны с таблицами БД
  • Когда требуется максимальная инкапсуляция деталей хранения и запросов
  • В приложениях, где основной фокус на работе с данными, а не на сложной бизнес-логике
  • При необходимости поддержки нескольких механизмов хранения данных

Когда выбирать Repository:

  • В проектах с богатой доменной моделью в стиле DDD
  • Когда требуется более высокий уровень абстракции, ориентированный на домен
  • При наличии сложных бизнес-правил и логики домена
  • В случаях, когда операции с данными естественно выражаются в терминах предметной области

Гибридный подход

В крупных проектах часто встречается гибридный подход, где Repository использует DAO для фактического доступа к данным:

Java
Скопировать код
@Repository
public class JpaOrderRepository implements OrderRepository {
private final OrderDao orderDao;
private final CustomerDao customerDao;

@Autowired
public JpaOrderRepository(OrderDao orderDao, CustomerDao customerDao) {
this.orderDao = orderDao;
this.customerDao = customerDao;
}

@Override
public List<Order> findPendingOrders() {
return orderDao.findByStatus(OrderStatus.PENDING);
}

@Override
public void markAsShipped(Order order, ShippingInfo shippingInfo) {
order.setStatus(OrderStatus.SHIPPED);
order.setShippingInfo(shippingInfo);
order.setShippedAt(new Date());
orderDao.update(order);
}

// Другие методы
}

В этом случае Repository обогащает низкоуровневые операции DAO доменной семантикой и логикой. Такой подход позволяет сочетать преимущества обоих паттернов:

  1. DAO обеспечивает инкапсуляцию механизма хранения и запросов
  2. Repository добавляет семантический слой, специфичный для домена
  3. Сервисный слой взаимодействует с Repository, не зная о существовании DAO

Выбор между DAO и Repository зависит от сложности доменной модели и требований к приложению. В простых CRUD-приложениях DAO может быть достаточным, тогда как в системах с богатой бизнес-логикой Repository предоставит более элегантную абстракцию.

Независимо от выбора, ключевой принцип остается неизменным: изоляция бизнес-логики от деталей хранения и доступа к данным для создания гибкой, тестируемой и поддерживаемой архитектуры.

Внедрение паттерна DAO в Java-приложениях — это не просто следование абстрактным рекомендациям, а практический шаг к созданию более гибкой и поддерживаемой архитектуры. Отделяя бизнес-логику от механизмов хранения данных, мы получаем код, который проще тестировать, расширять и адаптировать к изменяющимся требованиям. Выбирая между DAO, Repository или их комбинацией, руководствуйтесь не модными тенденциями, а реальными потребностями проекта, помня, что любой паттерн — это инструмент, а не самоцель.

Загрузка...