> Почему Active Record считается антипаттерном (PHP)

Уровень: senior · Роль: backend · Язык: PHP · Категория: Технические вопросы

Компании: vanta

Стек: PHP

> Пример ответа

Короткий ответ

Active Record считают антипаттерном в контексте сложных доменных моделей, потому что он нарушает принцип единственной ответственности: сущность одновременно отвечает и за бизнес-логику, и за персистентность. Это приводит к тесной связанности с БД, усложняет тестирование и масштабирование. Однако для простых CRUD-приложений он остаётся прагматичным выбором - проблема не в паттерне самом по себе, а в его применении вне зоны адекватности.

Подробное объяснение

Active Record - это паттерн, где объект домена напрямую оборачивает строку таблицы БД и содержит методы для CRUD (save(), delete(), find()). Критика возникает, когда этот подход применяют к сложным доменным моделям.

Основные претензии:

  1. Нарушение SRP - класс совмещает бизнес-правила и механику работы с БД. Изменение схемы БД затрагивает доменную логику, и наоборот.

  2. Тесная связанность с инфраструктурой - домен зависит от конкретной реализации БД. Переход с MySQL на PostgreSQL или на внешний API становится нетривиальной задачей.

  3. Проблемы с тестированием - unit-тесты требуют реальной БД или сложных моков. Бизнес-логику нельзя проверить изолированно.

  4. Нарушение инкапсуляции - публичные геттеры и сеттеры позволяют менять состояние объекта без контроля инвариантов. Любой сервис может установить $user->status = 'active', минуя валидацию.

  5. Трудности с наследованием и полиморфизмом - таблицы БД плохо ложатся на иерархии классов. Приходится использовать single table inheritance или table per type, что усложняет схему.

  6. Разрастание классов - в Active Record классы часто становятся god-object'ами: содержат и валидацию, и скоупы, и бизнес-методы, и связи.

Альтернатива - паттерн Data Mapper (например, Doctrine 2), где сущности - это чистые PHP-объекты, а репозитории отвечают за персистентность. Это даёт гибкость, но требует больше кода и явного управления жизненным циклом объектов.

На практике

В PHP-экосистеме Active Record - стандарт для Laravel (Eloquent) и Yii2. В Doctrine (Symfony) используется Data Mapper. Выбор зависит от контекста:

  • Когда Active Record оправдан: CRUD-приложения, админки, прототипы, проекты с простой доменной логикой. Eloquent сильно ускоряет разработку, а его магические методы и query builder покрывают большинство задач.

  • Когда лучше Data Mapper: сложные бизнес-правила, множество инвариантов, длинные транзакции, необходимость тестировать логику без БД, требования к масштабируемости.

На практике в Laravel часто используют гибридный подход: Eloquent-модели остаются тонкими (только поля и связи), а бизнес-логика выносится в сервисы или action-классы. Это смягчает недостатки паттерна, сохраняя его скорость.

Пример кода

Плохой пример - бизнес-логика внутри модели:

PHP
class Order extends Model
{
public function applyDiscount(float $percent): void
{
// Бизнес-правило: скидка не более 30%
if ($percent > 30) {
throw new \DomainException('Discount too large');
}
// Смешиваем бизнес-логику и персистентность
$this->discount = $percent;
$this->total = $this->calculateTotal();
$this->save(); // <-- модель сама сохраняет себя
}
}

Лучше - тонкая модель + сервис:

PHP
class Order extends Model
{
protected $fillable = ['total', 'discount'];
}
class OrderService
{
public function applyDiscount(Order $order, float $percent): void
{
if ($percent > 30) {
throw new \DomainException('Discount too large');
}
$order->discount = $percent;
$order->total = $this->calculateTotal($order);
$order->save();
}
}

Как отвечать на собеседовании

Начните с определения паттерна, затем перечислите ключевые проблемы. Важно показать, что вы понимаете контекст: не говорите "Active Record - зло", а объясните, когда он становится проблемой. Упомяните альтернативу (Data Mapper) и приведите пример из практики - например, как вы выносили логику из Eloquent-моделей в сервисы.

Если спросят про тестирование - подчеркните, что с Active Record сложно писать изолированные unit-тесты, приходится использовать DatabaseTransactions или фабрики. Если спросят про производительность - отметьте, что паттерн сам по себе не влияет на скорость, но может привести к N+1 запросам, если не использовать жадную загрузку.

Что проверяет интервьюер

  • Понимание принципов SOLID, особенно SRP.
  • Умение видеть trade-off между скоростью разработки и архитектурной чистотой.
  • Знание альтернативных паттернов (Data Mapper, Repository).
  • Практический опыт: как кандидат решал проблемы, вызванные Active Record.
  • Способность аргументировать позицию, а не просто заучивать "антипаттерн = плохо".

Типичные ошибки

  • Категоричность: "Active Record - это всегда плохо". Это не так, для простых CRUD он оптимален.
  • Путаница с Repository: Active Record - это не то же самое, что Repository. Repository абстрагирует доступ к данным, а Active Record - это конкретная реализация сущности.
  • Игнорирование контекста: не упоминают, что в Laravel Eloquent - это осознанный выбор фреймворка, и бороться с ним не нужно.
  • Отсутствие примеров: говорят абстрактно, без кода и реальных ситуаций.
  • Смешение с ORM: Active Record - это паттерн, а ORM (Doctrine, Eloquent) - инструмент. Eloquent реализует Active Record, но не каждый ORM - это Active Record.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью