- PVSM.RU - https://www.pvsm.ru -

О паттернах проектирования для работы с РСУБД

Введение

Работа с РСУБД является одной из важнейших частей разработки веб-приложений. Дискусcии о том, как правильно представить данные из БД в приложении ведутся давно. Существует два основных паттерна для работы с БД: ActiveRecord и DataMapper. ActiveRecord считается многими программистами антипаттерном. Утверждается, что объекты ActiveRecord нарушают принцип единственной обязанности (SRP). DataMapper считается единственно верным подходом к обеспечению персистентности в ООП. Первая часть статьи посвящена тому, что DataMapper далеко не идеален как концептуально, так и на практике. Вторая часть статьи показывает, как можно улучшить свой код используя существующие реализации ActiveRecord и несколько простых правил. Представленный материал относится главным образом к РСУБД, поддерживающим транзакции.

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

Апологеты DataMapper отмечают, что этот паттерн предоставляет возможность абстрагироваться от БД и программировать в "терминах бизнес-объектов". Предписывается создавать абстрактные хранилища, в которых сохраняются т.н. "сущности" — обьекты, содержащие персистентные данные приложения. Фактически предлагается эмулировать БД в приложении, создав над РСУБД объектное хранилище. Якобы это должно позволить достичь отделения бизнес логики от БД. Однако в любом серьезном приложении требуются операции на множестве записей. Их реализация в виде работы с объектами как правило гораздо менее эффективна, чем SQL-запросы. В качестве решения этой проблемы предлагается часть кода делать в терминах объектов, а где это не удается, использовать SQL или какой-нибудь собственный язык запросов, транслируемый в SQL (HQL, DQL). Идея не работает в полной мере, поэтому и предлагается фактически возвращаться к SQL.

Сущности, несмотря на отсутствие внутри SQL-кода, все равно зависят от БД. Особенно это проявляется при программировании связей (одна сущность объявляется главной, другая подчиненной). Реляционные отношения так или иначе протекают в объектную структуру. На самом деле сущности это никакие не "бизнес-объекты", а "пассивные записи". Более того, это вообще не объекты, а структуры данных, которые должны обрабатываться специальным объектом-преобразователем для сохранения и извлечения из БД. Особенно хорошо это заметно в CRUD-приложениях. Сущности в таких приложениях вырождаются в контейнеры для данных без какой-либо функциональности и известны как анемичные сущности. В качестве решения предлагается помещать в сущности бизнес-логику. Это утверждение также вызывает сомнения. Во-первых, сущности в CRUD-приложениях так и останутся анемичными. Негде взять бизнес-логику, чтобы заполнить пустые классы. DataMapper не работает в CRUD-приложениях. Во-вторых, для бизнес-логики почти всегда нужны зависимости. Редко какая настоящая бизнес-логика будет работать только на данных самой сущности. Правильный способ получения зависимостей — внедрение через конструктор. Однако, большинство реализаций DataMapper ограничивают конструирование, делая недоступным внедрение конструктора. Использования внедрения метода в качестве замены внедрению конструктора делает объект неполноценным. Такой объект ничего не может делать сам, ему всегда нужно передавать все необходимые зависимости. Как следствие, происходит загрязнение клиентского кода повсеместной передачей зависимостей.

Самая известная реализация DataMapper в PHP — Doctrine ORM. Чтобы использовать эту библиотеку нужны либо аннотации, либо дополнительные файлы, задающие отображение. Первый способ хорошо показывает связь сущности и БД, пусть и неявную. Само преобразование основано на использовании интерфейса отражения (Reflection API). Данные помещаются и извлекаются без какого-либо участия самого объекта — работа с объектом ведется как со структурой данных. Очевидно, что это нарушает инкапсуляцию — один из базовых принципов ООП. API Doctrine ORM довольно объемный, подводных камней достаточно много. На обучение эффективному использованию этой библиотеки требуется время. Все вышесказанное в разной степени относится и к другим реализациям DataMapper. Учитывая приведенные аргументы, DataMapper представляется избыточным, тем более, что от знания SQL и РСУБД он все равно не избавляет, никакой реальной независимости от БД не дает. Код, использующий Doctrine ORM как правило навсегда останется привязанным к ней.

Использование ActiveRecord

Практически каждый из популярных PHP-фреймворков предлагает собственный способ работы с БД. Большинство используют собственную реализацию ActiveRecord. Как правило, ради скорости разработки типовых приложений на ActiveRecord возлагаются не только обязанности по взааимодействию с БД, но и роль бизнес-объекта, валидатора, а иногда и формы. Проблемы такого использования ActiveRecord известны и хорошо описаны во многих статьях. В качестве решения как правило предлагается переписать весь код используя DataMapper. В данной статье предлагается использовать ActiveRecord с которого снимается часть обязанностей путем соблюдения нескольких простых правил.

Решение описывается далее с примерами псевдокода. Некоторые вызовы могут быть составлены некорректно, цель статьи — показать идею, а не конкретную рабочую реализацию. Конструкторы и некоторые очевидные методы, а также часть проверок опущены для краткости. В качестве реализации AR используется Yii. Данный фреймворк выбран для примеров потому, что на нем написано немало проектов, которые надо рефакторить, поддерживать, с ними нужно считаться.

Подход применим и для других фреймворков и независимых реализаций ActiveRecord. Сначала будет показан код, применимый в проектах, полностью зависимых от Yii. Он довольно прост. Далее будут показаны примеры с внедрением зависимостей и использованием Yii как библиотеки для реализации интерфейсов объектов взаимодействующих с БД.

Вставка и модификация данных

Для начала необходимо создать класс, описывающий объект шлюза к одной или группе связанных таблиц БД. В данной статье такие классы называются репозиториями, но они работают не так как репозитории в DataMapper. Объект, представляющий запись или записи в БД, не должен использоваться при отсутствии данных в самой БД. Для вставки и создания должен использоваться репозиторий.

class YiiARUserRepository
{
    public function add(string $email, string $name, array $phones, DateTimeInterface $created_at)
    {
        return $this->transaction->call(function () use($email, $name, $phones, $created_at) {

            //в БД есть уникальный индекс на email, проверка для UI проверка произвводится в форме с помощью обращения к внедренному репозиторию
            $ar = new YiiARUser([
                'email'      => $email,
                'name'       => $name,
                'created_at' => $created_at->format('Y-m-d H:i:s')
            ]);
            $ar->insert();
            foreach ($phones as $phone) {
                $ar->addPhone($phone['phone'], $phone['description']);
            }

            return $ar;
        });

    }

}

class YiiDbTransaction
{

    public function call(callable $callable)
    {
        $txn = Yii::$app->db->beginTransaction();
        try {

            $result = $callable();

            $txn->commit();

            return $result;

        } catch (Exception $e) {
            $txn->rollback();
            throw $e;
        }
    }

}

class YiiARUser extends yiidbActiveRecord
{
    //...
    public function addPhone(string $phone, string $description)
    {
        $ar = new YiiARPhone([
            'user_id'     => $this->id,
            'phone'       => $phone,
            'description' => $description
        ]);
        $ar->insert();

        return $ar;
    }

}

Желательно, чтобы в методах добавления не было никакого кода, кроме присваивания и вставки в БД. Все необходимое нужно вычислить в клиентском коде. Никаких beforeSave() или других вызовов жизненного цикла быть не должно. Сразу можно вставлять в базу только обязательные поля, остальное можно добавить в других методах в рамках транзакции. В качестве бонуса — нет никаких проблем с использованием автоинкрементных ключей. В статьях и докладах по Symfony, Doctrine и DDD все чаще можно встретить тирады про недостатки автоникрементных ключей БД, про то, что сущность при создании без ключа — не сущность и надо использовать генераторы UUID. Это еще один шаг к эмуляции функционала БД в приложении — то, чего в данной статье предлагается избегать.

class YiiARUser extends yiidbActiveRecord
{
    //...
    public function changePassword(string $password)
    {
        $this->updateAttributes([
            'password' => md5($password)
        ]);
    }

    public function rename(string $name)
    {
        $this->updateAttributes([
            'name' => $name
        ]);
    }

}

class RegisterForm
{
    public function register(DateTimeInterface $created_at): YiiARUser
    {

        if ( ! $this->validate()) {
            throw new DomainException($this->errorMessage());
        }

        return $this->transaction->call(function () use($created_at) {
            $user = $this->user_repo->add($this->email, $this->name, [], $created_at);
            $user->changePassword($this->password);
            $user->changeSomething($some_data);
            foreach ($this->phone_forms as $form) {
                $user->addPhone($form->phone, $form->description);
            }

            return $user;
        });

    }
}

Основная идея заключается в том, что мы не накапливаем изменения в единице работы на стороне приложения, а производим их с помощью единицы работы на стороне БД. Транзакция БД это единица работы. Многие известные проблемы AR как раз вызваны тем, что разработчики пытаются предварительно собрать граф из AR в памяти и при вызове save() сохранить все их сразу. Для этого в Yii даже существует расширение WithRelatedBehavior. Все это заблуждение. Слово "Active" в ActiveRecord как раз и предназначено для того, чтобы показать, что объекты могут выполнять запросы при обращении к их методам. Какое-либо разделение на "до сохранения" и "после сохранения" недопустимо.

В приведенных примерах нетрудно видеть, что валидация производится вне AR. Строки в базе должны содержать только корректные данные, соответственно объект, представляющий строку в приложении также должен обладать этим свойством.

Выполнение нескольких запросов вместо одного при вставке или обновлении данных в БД не должны вызвать проблем с производительностью. СУБД хорошо оптимизирует последовательные INSERT и UPDATE на одну и ту же строку в одной транзакции. Однако, последнего все равно лучше не допускать, и если производится массовое обновление данных строки, лучше создавать соответствующие методы, типа YiiARUser::changeInfo($phones, $addresses, $name, $email).

Выборка

Код запросов на выборку необходимо помещать в класс репозитория. Использовать статические методы для этой цели не стоит. Статические методы Yii в примере используются в качестве реализации просто потому что по-другому в Yii трудно или невозможно. В идеале нужно внедрять соединение с БД в репозиторий через конструктор, а затем и во все создаваемые объекты AR. От статики лучше держаться подальше — она вредит проектам с большим объемом кода или длительным жизненным циклом.

class YiiARUserRepository
{
    //...
    public function findOne($id)
    {
        return YiiARUser::findOne($id);
    }

    public function findUsersWithGroups($limit)
    {
        return YiiARUser::find()->with('groups')->all();
    }

    //можно вернуть и DataProvider, если клиентский код тоже зависит от Yii
    public function findAll(): DataProviderIterface
    {
        //...
    }

    //если нужно много пользователей
    public function findAll(): Iterator
    {
        //...
        return new class($reader) implements Iterator
        {
            //...
            public function current()
            {
                $data = $this->reader->current();

                return YiiARUser::instantiate($data);
            }
        }
    }

}

Как видно из примеров, можно сохранить DataProvider и data widgets (RAD-инструменты Yii). Это возможно только в том случае, если проект можно сделать полностью зависимым от Yii.

Модификация данных в связанной таблице

Допустим, необходимо изменить один из номеров телефонов пользователя. Номера хранятся в отдельной таблице, связанной с таблицей пользователей внешним ключом. Традиционный подход основывается на изменении данных в памяти и последующем сохранении.

//поведение осуществляет загрузку данных в основную и связанные записи
$user->with_related_behavior->setAttributes($request->post());

//путем array_diff(), AR::isNewRecord() определяются новые и измененные записи, генерируются соответсвующие запросы
//очевидно, что между методами существует temporal coupling
$user->with_related_behavior->save();

Разбор всех недостатков представленного подхода выходит за пределы статьи. В рамках данной темы стоит отметить лишь то, что при вызове setAttributes() фактически теряется информация о том, какие записи были добавлены или обновлены, а при вызове save() эта информация восстанавливается. К тому же, данные вызовы тесно связаны. В качестве альтернативы предлагается следующее. Обязанности по определению того, что было добавлено, обновлено или удалено необходимо возложить на форму. Лучше всего строить UI, которые генерирует HTTP запросы на уделение связанных записей по конретным идентификаторам.

class UserUpdateForm
{

    public function update(YiiARUser $user)
    {

        $this->transaction->call(function () use ($user) {

            //...
            foreach ($this->changedPhones() as $phone)
                $user->changePhone($phone['id'], $phone['phone'], $phone['description'])

            $user->addPhones($this->addedPhones());

        });

    }

}

class YiiARUser extends yiidbActiveRecord
{

    //...

    public function changePhone(int $phone_id, $phone, $description)
    {
        $phone = YiiARPhone::findOne(['id' => $phone_id, 'user_id' => $this->id]);
        if ($phone == null) {
            throw new DomainException('Телефон не найден.');
        }
        $phone->updateAttributes([
            'phone'       => $phone,
            'description' => $description
        ]);
    }

    public function addPhones($phones)
    {
        YiiARUser::$db->createCommand()->barchInsert('phones', ['phone', 'description'], $phones)->execute();
    }

}

При изменении связанных записей необходимо либо сбросить, либо перезагрузить ранее загруженные связанные записи. Жаль, что в Yii нет публичного метода типа resetRelation($name). Остается либо перезагрузить связанные даннные, либо убедиться в том, что загруженных данных нет или они нигде не используются (плохой код), ну или ответвиться.

Подход, основанный на непосредственных вызовах к РСУБД имеет еще одно преимущество в плане работы со связанными данными — простота обеспечения констант. Например, имеется ограничение — пользователь не должен иметь более пяти номеров телефонов. Простая проверка при добавлении не обеспечивает соблюдения этой константы в условиях одновременного использования ресурса несколькими клиентами. Необходимы блокировки. Работая с БД напрямую можно легко использовать механизмы блокировок движка БД.

public function addPhones(array $phones)
{

    $this->transaction->call(function () {

        $id = YiiARUser::$db->query('SELECT id FROM users FOR UPDATE;')->queryScalar();

        if ($id === null) {
            throw new DomainException('Пользователь не найден.');
        }

        if ($this->phoneCount() + count($phones) > 5) {
            throw new DomainException('Телефонов слишком много!');
        }

        YiiARUser::$db->createCommand()->batchInsert('phones', ['phone', 'description'], $phones);

    });

}

При использовании Doctrine ORM реализовать подобное будет сложнее, если не блокировать всю запись сразу при выборке.

Удаление

Удаление осуществляется через объект, представляющий строку.

$user->delete();

class YiiARUser extends yiidbActiveRecord
{

    public function delete()
    {
        self::$db->createCommand()->delete('phones', ['user_id' => $this->id]);
        $this->delete();
        //если нужно событие на удаление, здесь можно добавить обращение к шине событий (о внедрении далее)
    }

}

При использовании DataMapper часто приходится делать такие связанные вызовы:

//чтобы сущность отправила событие в шину:
$user->delete();

//удаление записи:
$em->delete($user);

Это один из примеров, показывающий противоречивость DataMapper — даже удалить одним вызовом не получается. Сущность несамостоятельна — работа с ней тесно связана с использованием преобразователя (менеджера сущностей).

Внедрение зависимостей и обеспечение независимости клиентского кода от кода, взаимодействующего с БД

Как известно, приложения гораздо легче обновлять/переписывать по частям, чем полностью. Старое монолитное приложение лучше сначала разделить на слои. Данный подход позволяет как это сделать даже в монолитном коде Yii-приложений.
Бизнес-логика, которая не нуждается в непосредственном выполнении SQL-запросов, может быть реализована без явной зависиости от классов, взаимодействующих с БД. Этого можно достичь благодаря тому, что репозитории являются фабриками AR. Код, приведенный выше, можно модифицировать, добавив интерфейсы и реализации. Это позволяет отделить код, взаимодействующий с БД от остального, а затем, если возникнет такая необходимость, переписать его используя другую библиотеку для работы с БД. Таким образом, legacy-проект можно обновлять частями без полного переписывания.

interface UserRepository
{

    public function add(string $name, string $email, array $phones, DateTimeInterface $created_at): User;

    public function findOne($id);

}

interface User
{

    public function addPhones($phones);

    public function rename($name);

    public function changePassword($pwd);

}

class YiiDbUserRepository
{

    public function add(string $name, string $email, array $phones, DateTimeInterface $created_at): User
    {
        $ar = $this->transaction->call(function () use($name, $email, $phones, $created_at) {

            $ar = new YiiARUser([
                'name'       => $name,
                'email'      => $email,
                'created_at' => $created_at->format('Y-m-d H:i:s')
            ]);
            $ar->addPhones($phones);

            return $ar;

        });

        return new YiiDbUser($ar);

    }

    public function findOne($id)
    {
        $ar = YiiARUser::findOne($id);
        if ($ar === null) {
            return null;
        }

        return new YiiDbUser($ar);

    }

}

class YiiDbUser implements User
{

    private $ar;

    public function addPhones(array $phones)
    {
        //multiple insert command
    }

    public function rename(string $name)
    {
        //запрос только при необходимости
        if ($this->ar->name !== $name) {
            $this->ar->updateAttributes(['name' => $name]);
        }
    }

    public function changePassword(string $pwd)
    {
        $this->ar->updateAttributes(['password' => md5($pwd)]);
    }

    public function phones(): Iterator
    {
        //в YiiARUser объявлена Yii-реляция на YiiARPhone
        $phone_ars = $this->ar->phones;

        $iterator = new ArrayIterator($phone_ars);

        return new class($iterator, $this->dependency) implements Iterator
        {

            //...
            public function current()
            {
                $ar = $this->iterator->current();

                //объект YiiDbPhone инкапсулируeт объект YiiARPhone
                return new YiiDbPhone($ar, $this->dependency);
            }

        }

    }

}

Если не нужно ничего внедрять, можно реализовывать интерфейс непосредственно в классе AR, не прибегая к композиции. Это даст некоторое упрощение, но снизит гибкость.

class YiiARUser extends yiidbActiveRecord implements User
{
  //...
}

Внедряя репозитории в клиентский код с помощью интерфейсов, а также не используя в возвращаемых значениях методов зависимые от БД форматы, можно разорвать явную зависимость между классами, работающими с БД и клиентским кодом. Это даст возможность менять реализации в будущем и повысит возможность повторного использования. Также становится возможным разного рода декорирование. Если в клиентском коде понадобится транзакция — ее также можно внедрить воспользовавшись интерфейсом. Данный метод дает возможность внедрять зависимости в репозитории и объекты, работающие со строками. Без инкапсуляции объекта-записи или использования синглтона в Yii этого пока что сделать нельзя [1]. Очевидно, при использовании композиции объекта AR теряется возможность использовать DataProvider, RAD-возможности снижаются. Однако, использование интерфейсов и композиции снизит вероятность ляпов новичков или злоупотреблений, связанных с открытостью интерфейса Yii AR. Стоит отметить, что в последнем утверждении гибкость и открытость рассматриваются как преимущество, позволяющее использовать реализацию AR Yii в своих целях. Если необходимо что-то ограничить — можно использовать композицию.

Работа с межмодульными связями

В больших приложениях, разбитых на нескольок модулей, часто возникает задача организации связей между объектами. Рассмотрим следующий пример. Есть модуль пользователей, рассмотренный ранее. Есть модуль блогов. Необходимо обесчечить использование пользователей в качестве авторов статей в блогах.

Сначала в модуле блогов необходимо объявить интерфейсы для статей, авторов и их репозиториев. Для своей работы модуль будет требовать реализации интерфейсов Author и AuthorRepository.
Весь остальной код содержится в самом модуле.

interface Post
{

    public function id(): int;

    public function title(): string;

    public function author(): Author;

    public function authorId(): int;

}

interface PostRepository
{
    public function findAllWithAuthors(int $limit): array;
}

class YiiARPost extends yiidbActiveRecord
{
  //...  
}

class YiiDbPostRepository implements PostRepository
{

    private $author_repository;

    public function findAllWithAuthors(int $limit): Iterator
    {
        $ars = YiiARPost::findAll(['limit' => $limit]);

        $iterator = new ArrayIterator($ars);

        $ids = [];

        foreach ($ars as $ar) {

            $ids[] = $ar->id;

        }

        $authors = $this->author_repository->findAll($ids);

        return new class($iterator, $this->author_repository, $authors) implements iterator
        {

            private $iterator;

            private $author_repository;

            private $authors;

            //...
            public function current()
            {
                $ar = $this->iterator->current();

                return new AuthoredPost(
                    new YiiDbPost($ar, $this->author_repository),
                    $this->authors
                );
            }

        }

  }

}

class YiiDbPost implements Post
{

    private $ar;

    private $author_repository;

    public function title(): string
    {
        return $this->ar->title();
    }

    public function content(): string
    {
        return $this->ar->content();
    }

    public function author(): Author
    {
        return $this->author_repository->findOne($this->ar->author_id);
    }

    public function authorId(): int
    {
        return $this->ar->id;
    }

}

class AuthoredPost implements Post
{

    private $post;

    private $authors;

    public function title(): string
    {
        return $this->post->title();
    }

    public function content(): string
    {
        return $this->post->content();
    }

    public function author(): Author
    {

        foreach ($this->authors as $author) {
            if ($author->id() == $this->post->authorId()) {
                return $author;
            }
        }
        throw new DomainException('Статья без автора! Нарушена целостность БД!');

    }

}

interface Author
{

    public function id(): int;

    public function name(): string;

}

interface AuthorRepository
{

    public function fundOne(int $id);

    public function findAll(array $ids): array;

}

Класс AuthoredPost необходим для оптимизации — предзагрузки авторов для списка статей. Реализации интерфейсов находятся в корне компоновки — самом приложении. Только приложению известно какие модули у него есть и как они работают вместе. Модулям друг о друге ничего не известно.

class UserAuthor implements Author
{

    private $user;

    public function id(): int
    {
        return $this->user->id();
    }

    public function name(): string
    {
        return $this->user->name();
    }

}

class UserAuthorRepository implements AuthorRepository
{

    private $repository;

    public function findOne(int $id)
    {

        $user = $this->repository->findOne($id);

        if ($user === null) {
            return null;
        }

        return new UserAuthor($user);

    }

    public function findAll(array $ids): Iterator
    {
        $users = $this->repository->findAll($ids);

        return new class($users) implements Iterator
        {

            //..
            public
            function current()
            {
                $user = $this->iterator->current();

                return new UserAuthor($user);
            }

        };
    }

}

На конференциях по PHP слышал вопросы — как в Yii организовать связь между моделями в разных модулях. Приведенный пример кода является вариантом ответа. В представленном случае статьи и авторы могут храниться в разных БД. Безусловно абстракция имеет свою цену. Если у вас маленький одноразовый проект — лучше использовать первый вариант без интерфейсов и разбиения на модули. В этом случае у вас не возникнет необходимости в межмодульных связях и проще будет работать с реляциями.

Если все хранится в одной БД и возникает необходимость в межмодульном JOIN или межмодульной транзакции, метод с абстракцией не будет работать. В этом случае можно порекомендовать пересмотреть само разделение на модули. Возникновение этой проблемы является индикатором того, что разделение на модули неоправдано или выполнено некорректно. Есть такое правило "одна БД — один контекст", другими словами не рекомендуется создавать более одного модуля на одной БД. Для взаимодействия между контекстами существуют свои инструменты интеграции. Второй вариант — обращаться к таблицам из другого модуля напрямую через подключение к БД, однако в этом случае возможно появление дублирования одного и того же кода в разных модулях. Выполнять разделение на модули следует очень внимательно, во многих случаях можно обойтись и без него.

Бизнес-логика

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

class SomeLogicUser
{

    private $user;

    //...

    public function doSomething()
    {

        $name = $this->calculateName();

        //этого лучше избегать
        $this->transaction->call(function () {
            $user->rename($name);
            $user->changeSomething($data);
        });

        //проектировать методы по требованиям бизнес-логики, не придется внедрять транзакцию - она будет внутри
        $user->changeEverythingRequiredUsingOneMethod($name, $data);

    }

}

Отличия между предложенным подходом и популярными реализациями DataMapper

Главное отличие заключается в том, где находится единица работы (Unit of Work). В DataMapper объекты, представляющие данные из БД играют роль структур данных, обрабатываемых объектом-преобразователем. Изменения данных отслеживаются с помощью прокси-объектов (которые, к тому же делают невозможным использование final). В предложенном подходе запросы составляются и выполняются сразу. Нет никакого отслеживания изменений на стороне приложения, прокси, использования Reflection API. Все устроено проще. Можно делать классы final, можно внедрять зависимости через конструктор.

Одним из недостатков является невозможность тестирования без БД объектов, генерирующих SQL-запросы. Тем не менее код, который призван взаимодействовать с БД лучше тестировать вместе с БД. Это позволит избежать получения ложноположительных результатов.

На использование данного подхода натолкнуло определение транзакции в глоссарии MySQL, а также усмотренное сходство между задачами, которые решают программный Unit of Work Doctrine ORM и нативные транзакции БД. Это одно и то же — накопление и управляемое применение/откат изменений.

Заключение

Производительность разработчика тесно связана с простотой и мощью используемых им инструментов. РСУБД и их интерфейсы/драйверы/библиотеки, язык SQL, сами по себе являются фреймворками, позволяющими решать широкий спектр задач. Конкретный движок РСУБД является важнейшей частью проекта. Он должен тщательно подбираться на стадии проектирования. Библиотеки, использующие DataMapper и программный Unit of Work фактически дублируют имеющиеся в БД функции, являются громоздкими и требуют тщательного изучения не только теории РБД, SQL и движка РСУБД, но и собственного API, и, что очень нередко, особенностей своей внутрнней реализации. При этом пользы от них немного. Обеспечение переносимости между БД это очень дорогая задача, которая редко когда требуется и решается до конца. Рассмотренный подход предлагает отказаться от избыточного функционала, изучения оверинжиниринговых технологий и рекомендует использовать всем знакомые простые инструменты и библиотеки.

Автор: Anton_Zh

Источник [2]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/php-2/265113

Ссылки в тексте:

[1] нельзя: https://github.com/yiisoft/yii2/issues/5786

[2] Источник: https://habrahabr.ru/post/339582/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best