Короткий ответ
- Абстрагируйте бизнес‑операции (репозиторий как набор доменных методов), но не пытайтесь полностью скрыть БД под универсальным слоем, который «работает с любой базой» и гасит её особенности.
- Пул подключений — инфраструктурная деталь: её конфигурация, жизненный цикл и поведение (таймауты, retry, max open) лучше держать в слое инфраструктуры и не делать предметом доменных абстракций. Экспортируйте только то, что реально нужно: контекст транзакции, интерфейс получения сессии/канала и семантику ошибок.
Почему это важно (плюсы/минусы полной абстракции)
- Плюсы абстракции:
- Изоляция доменной логики от реализации БД → легче тестировать, менять реализацию.
- Чистые интерфейсы для контрактов (инверсия зависимостей).
- Возможность подменять реализацию в интеграционном тесте.
- Минусы «глубокой» абстракции:
- Ликвидность (leaky abstractions) — низкоуровневые свойства БД всё равно просачиваются, и вы получаете сложный универсальный слой.
- Потеря производительности и возможностей, если вы вынуждены работать с НОСД (наименьшим общим делителем) функций разных СУБД.
- Избыточная сложность и поддержка множества адаптеров редко окупаются, если реально не нужна мульти‑СУБД поддержка.
Практические рекомендации
1. Абстрагируйте дело (что), а не способ (как).
- Репозиторий должен предоставлять методы, нужные домену: getById, save, findBy..., списки/агрегаты. Не пытайтесь сделать generic API, которое генерирует SQL за вас.
2. Слой инфраструктуры реализует детали:
- Конфигурация пула, драйвер, миграции, репликация — всё в infra. Доменный код не должен знать про maxConnections.
3. Экспортируйте минимально необходимую семантику:
- Контекст транзакции, rollback/commit, таймауты, опционально retry. Например: withTransaction(ctx, func(tx Tx) error).
4. Не прячьте критические фичи СУБД, если они нужны:
- JSONB, полнотекстовый поиск, window functions, upsert — если бизнес их использует, отражайте это в API.
5. Для тестирования:
- Юнит‑тесты: мокируйте репозитории (интерфейсы). Интеграционные — прогоняйте на реальной СУБД (или на контейнере), т.к. in‑memory базы часто ведут себя иначе.
6. Если вы библиотека/фреймворк:
- В таком случае абстракция нужна: библиотека должна поддерживать несколько реализаций через интерфейсы и адаптеры.
7. Если вы приложение:
- Чаще всего проще выбрать одну СУБД и использовать её фичи; делать слои адаптации имеет смысл только при реальной необходимости смены движка.
Конкретная структура (пример архитектуры)
- domain/
- model, service, repository interface (например UserRepository { Get(id) Save(user) FindByEmail(email) })
- infra/
- db/pool — конфигурация и инициализация пула (параметры в конфигурации)
- db/postgres_user_repository — реализация UserRepository, использующая SQL/ORM и пул
- migrations, healthchecks, metrics
- app/
- собирает зависимости (внедряет infra реализацию в доменные сервисы)
Про пул БД
- Сделайте пул единственным местом инициализации (singleton/di). Коды в приложении получают либо:
- явный объект Session/Connection/Tx от пула, либо
- контекст (ctx) в котором middleware/handler инжектирует сессию.
- Не пытайтесь «абстрагировать пул» в общем виде, чтобы он поддерживал любые драйверы. Лучше иметь адаптеры: PooledDB → PostgresPool / MySQLPool, и фабрику конфигурации.
- Экспонируйте поведение, которое потребуется приложению: BeginTransaction(ctx, opts), GetConn(ctx), Close() и параметры конфигурирования.
Поведение ошибок и семантика
- Не переворачивайте все ошибки в абстрактные. Часто нужно различать сетевую/таймаут/конфликт (unique violation) — переводите только те ошибки, которые важны домену (например, DuplicateEntityError), остальные оставляйте как транспортные или оборачивайте с сохранением кода.
Когда стоит сделать полную абстракцию
- Нужна реальная поддержка нескольких СУБД или ваша команда/продукт будет мигрировать между ними.
- Вы пишете публичную библиотеку/фреймворк.
- У вас очень жесткие требования к тестируемости, и вам важна возможность лёгкой подмены реализации во всех сценариях (но обычно достаточно моков на уровне репозитория).
Когда не стоит
- Вы разрабатываете приложение с одной СУБД, хотите её фичи и максимальную производительность.
- Абстракция будет стоить больше усилий, чем потенциальный выигрыш.
Короткий чек‑лист перед реализацией
- Что именно вы хотите скрыть и зачем? (производительность, тестирование, геморрой при смене СУБД)
- Нужны ли вам функции конкретной СУБД? Если да — не абстрагируйте их.
- Как будет выглядеть тестирование? Нужно ли реальное поведение транзакций/DDL?
- Будете ли вы поддерживать несколько реализаций «вечно»? Если нет — упрощайте.
Итог
Абстрагируйте интерфейсы на уровне домена и держите пул/конфигурацию/миграции в инфраструктурном слое. Не создавайте универсальную «сверх‑абстракцию», которая пытается скрыть всю семантику разных СУБД — это дорого и часто ненадёжно. Делайте адаптеры и ясные границы слоёв: домен пользуется понятным API, инфраструктура — конкретной реализацией.
Если хочешь, могу:
- привести примеры интерфейсов и адаптеров на твоём стеке (Go/Java/Node/Python), или
- помочь спроектировать конкретный API пула/транзакций для твоего проекта.