ActiveRecord: плюсы и минусы

Воскресенье, 21е сентября, вечер, трезв…

Сегодня мы обсудим плюсы и минусы охуенной хуйни под названием ActiveRecord. Да‐да, это именно хуйня, но она охуенная, и в этом дальше мы убедимся сами.

На написание поста меня сподвигло то, что я увидел то, на что способны некоторые говнокодеры, а именно — 130+ запросов к СУБД для генерации главной страницы.

Для начала давайте разберемся с тем, что же такое ActiveRecord. AR — это один из паттернов проектирования для ОО доступа к сущностям в СУБД. Короче говоря — просто еще одна имплементация ORM. Впервые была мной замечена в Ruby On Rails, во второй раз я ее увидел в SimpleCMS в Ринамике (ринамика и пилит UFO CMF, aka SimpleCMS).

Для чего нужен AR, когда можно писать чистые SQL запросы?
Тут мы рассмотрим все плюсы использования AR.

Первое. Объектно‐ориентированный подход.
При использовании AR не нужно вносить в бизнес‐логику куски SQL‐запросов, с AR можно оперировать только объектами:

$post = Post::create();
if (!$title) {
$post->title = "No title";
} else {
$post->title = $title;
}
$post->save();

При использовании чистых SQL запросов все было бы менее красиво.

Второе. Независимость от СУБД.
Зачастую, реализации AR могут работать через различные драйверы для доступа к СУБД, а также генерировать специфичные для конкретной СУБД запросы, вынося все это с уровня бизнес‐логики. И в данном случае, программисту необходимо только описать что он хочет, а не описывать то, как он хочет.

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

class Post extends AR {
public function define_columns() {
$this->defineColumn('title', 'Заголовок')
->validation()
->maxLength(20)
->minLenght(2);
}
}
$post = Post::create();
$post->title = "a";
$post->save(); //выбросит исключение

Мелочь, а приятно. И в случае если у вас используются чистые SQL запросы, и создание поста может происходить более чем в одном месте, то и валидировать значения вам также нужно будет более чем в одном месте.

Четвертое. Связи.
По‐моему, все реализации AR поддерживают связи (has many, belongs to, has and belongs to many, has one), и это очень удобно! Например, когда вам необходимо вывести все категории и все посты из них, то вам необходимо будет написать что‐то вроде следующего:

class Post extends AR {
    public function define_columns() {
        $this->defineColumn('title', 'Заголовок')
            ->validation()
            ->maxLength(20)
            ->minLenght(2);
    }
}

class Category extends AR {
    public $has_many = [
        'posts' => [
            'class_name' => 'Post',
            'foreign_key' => 'category_id',
            'delete' => true
        ]
    ];
    public function define_columns() {
        $this->defineColumn('name', 'Имя');
        $this->defineMultiRelationColumn('posts', 'posts', 'Посты', '@title');
    }
}

foreach (Category::create()->find_all() as $c) {
    printf("Category %s", $c->name);
    foreach ($c->posts as $p) {
        printf("    Post %s", $p->title);
    }
}

Точно так же, можно и модифицировать все связи. А если у объекта категории вызвать метод ->delete (), то сначала будут удалены все посты из этой категории (в описании связи указан delete => true), и только после этого будет удалена сама категория. При использовании чистого SQL все было бы менее красиво и читаемо.

Плюсов у AR еще много, но все их расписывать не буду. Скажу только то, что совместно с реализацией паттерна Observer, разработка и расширение моделей, а так же обработка событий из разряда CRUD становится просто сказкой. Ну и в самих моделях можно реализовывать логику, например, при создании модели можно в заоверрайденном методе on_create разослать письма подписчикам.

А теперь минусы.

Вся эта гибкость это хорошо, удобно и очень ускоряет разработку, но за все нужно платить…

Первое. Скорость.
Добавление объектов добавляет еще и существенный оверхед на обработку этих объектов. При использовании AR не обойтись без «магии» и рефлексии, которые, как все мы знаем, медленные.

Второе. Память.
Объект занимает гораздо больше памяти, чем обычный массив, полученный с помощью mysqli_fetch_assoc. А еще необходимо помнить о том, что внутри самого AR хранится много объектов (как минимум, объекты описывающие колонки, объект валидатора, и прочее). Например, в UFO CMF, в базовом классе для AR имеется порядка 30 членов.

Третье. Неоптимальные SQL запросы.
Невозможно взять, и сгенерировать оптимальный запрос. К примеру, есть у вас в таблице posts 50 колонок, и в текущий момент вам нужны только две колонки — id и title (для вывода в списке), но AR сгенерирует запрос в котором выберет все колонки и присвоит из значения свойствам модели.

Также бывают случаи, когда может выполниться куча однотипных запросов, которые порождает каждая уникальная модель. Но отследить это невозможно, и оптимизировать также невозможно.

Четвертое. Лишние запросы.
Так как AR имеет встроенную валидацию, поэтому ей необходимо выполнять DESCRIBE запросы для каждой таблицы, должен же AR знать как и что валидировать «по‐умолчанию». Также, в зависимости от возможностей реализации AR, могут быть лишние запросы для различных внутренних нужд AR. Например, подгрузка связей, когда она не нужна. А еще, в UFO CMF можно в моделях описывать кастомные поля (которые не привязаны к самой таблице этой модели, например, подсчет количества постов в категории), и все эти поля делаются подзапросами, и даже не смотря на то, что они не нужны в 99% случаев — AR сгенерирует запрос для них в любом случае.

Помимо этого, в UFO CMF из моделей можно генерировать списки и формы, а для них также необходимы посторонние данные, точнее, данные о связях, что тоже приводит к генерации лишних подзапросов/джойнов.

Также, в реализации AR для UFO CMF имеются т.н. deffered bindings, позволяющие связывать еще не созданные объекты. Необходимые, когда к примеру, вы создаете новость, цепляете к ней фотографию. Но т.к. фотографию можно сохранить в таблице db_files, но т.к. в news_posts еще нет самой новости, то связать фотографию с постом невозможно. И эта полезная штука также генерирует свои запросы.

Выводы

Использовать AR полезно и нужно! С помощью AR можно убить много зайцев, и реализовать проект в стопицот раз быстрее, не задумываясь о низких уровнях, вроде SQL запросах. Но, использовать AR нужно с умом, и там, где более быстрее будет выполнить обычный SQL запрос (как говорил выше, для выборки только пары колонок) — лучше выполнить его. Ну и конечно же, необходимо профилировать проекты, анализировать генерируемые запросы, и в случае если они работают медленно — либо добавлять индексы, либо переписывать такие участки на чистый SQL.

comments powered by Disqus