Active Record, Repository, DAO

Три enterprise-паттерна слоя доступа к данным. Не из GoF, но критичны для backend. Каждый — другой trade-off между простотой и разделением ответственности.

Active Record — простой, но грязный

Идея: один класс хранит и данные, и логику работы с БД.

class User {
  id; name;
  static async find(query) { /* SELECT */ }   // статический
  async save { /* INSERT/UPDATE this */ }   // динамический
  async delete { /* DELETE this */ }
}

const user = await User.find({ name: 'Marcus' });
user.name = 'Mark';
await user.save;

Проблемы:

  • Нарушает SRP/SOLID — класс отвечает и за state, и за persistence
  • Нарушает separation of concerns — domain logic перемешан с CRUD
  • Невозможно нормально разделить — только префиксами

Когда оправдан: анемичная модель без доменной логики, мелкие CRUD-приложения. Типичен для PHP, Ruby on Rails.

DAO (Data Access Object) — разделение state/persistence

Идея: данные в одном классе (плоский DTO), доступ к БД — в другом (DAO).

// State — только данные
class User {
  id; name;
}

// DAO — только работа с БД
class UserDao {
  async find(query) { /* ... */ }
  async save(user) { /* ... */ }
  async delete(user) { /* ... */ }
}
  • Уже разделена ответственность
  • DAO работает с конкретной таблицей
  • Близко к mapper-паттерну

Repository — коллекция, не таблица

Идея: репозиторий — как коллекция доменных объектов. Бизнес-логика не знает, что под капотом БД, файл, или сеть.

class UserRepository {
  async findById(id) { /* ... */ }
  async findByEmail(email) { /* ... */ }
  async save(user) { /* ... */ }
  async list({ filter, page }) { /* ... */ }
}
  • Репозиторий = коллекция в памяти + кеш + БД
  • Семантика — доменная, не таблиц/строк
  • Хорошо ложится на DDD (Aggregate Root и его репозиторий)
  • Можно подменить на in-memory реализацию для тестов

ORM — тоже про data access, но антипаттерн (по автору)

автор критически относится к ORM:

  • ORM скрывает SQL → программист не понимает производительность запросов
  • ORM плохо ложится на сложные join'ы и денормализованные view
  • Query Builder — золотая середина: ручное управление SQL + автоматизация типизации

Главные тезисы автора

  • Active Record нарушает SOLID/GRASP/SoC, но простой — для CRUD без логики оправдан.
  • DAO разделяет state и persistence — лучше для нагруженной доменной логики.
  • Repository — это коллекция доменных объектов, не таблица.
  • Query Builder — альтернатива ORM, ручной контроль над SQL.
  • В Ruby/PHP Active Record норма, в Java/.NET DAO/Repository доминируют.
  • В JS — все три встречаются, выбор зависит от сложности домена.
  • «ORM не нужен» — повторяющийся тезис автора.

🎓 Источники

См. также