Как работает подход Unit of Work?

Доброго времени всем, хотелось бы разобраться в принципе работы и устройства паттерна UoW. Если можно, объясните в коде, желательно на php в миниатюрном и очень примитивном примере
  • Вопрос задан
  • 24573 просмотра
Решения вопроса 2
@Flying
Unit of Work - это паттерн определяющий логическую транзакцию т.е. атомарную синхронизацию изменений в объектах, помещённых в объект UoW с хранилищем (базой данных).

Если обратиться к исходному описанию этого паттерна у Мартина Фаулера - то видно что объект, реализующий этот паттерн отвечает за накопление информации о том какие объекты входят в транзакцию и каковы их изменния относительно исходных значений в хранилище. Основная работа производится в методе commit() который отвечает за вычисление изменений в сохранённых в UoW объектах и синхронизацию этих изменений с хранилищем (базой данных).

Паттерн Unit of Work как правило не является полностью самостоятельным, он обычно тесно связан с паттерном Identity Map, задача которого - сохранение карты созданных объектов, взятых из хранилища с тем чтобы гарантировать что одна единица информации из хранилища представлена ровно одним экземпляром объекта данных в приложении. Это позволяет избежать конфликтов изменений т.к. не допускает ситуации когда два объекта, представляющих один и тот же элемент данных в хранилище, изменены по-разному. Информация из Identity Map используется в методе commit() паттерна Unit of Work для вычисления разницы между исходными данными и накопленными изменениями.

Поскольку для вычисления разницы (и, соответственно, определения того что и каким образом должно быть изменено в хранилище) необходимо знать какие данные и как именно хранятся в объектах - как правило необходима также реализация паттерна Metadata Mapping, описывающего связь между содержимым хранилища (к примеру таблицами и столбцами базы данных) и классами / свойствами объектов.

Также, если данные в хранилище не являются независимыми (к примеру связи между таблицами в базе данных) - может потребоваться реализации ряда паттернов, отвечающих за сохранение информации о связях между данными (это паттерны раздела Object-Relational Structural Patterns в каталоге паттернов).

Подводя итог: сам по себе Unit of Work довольно прост в своём внешнем интерфейсе, но реализация его корректной работы требует предоставления множества дополнительных данных, поэтому миниатюрных примеров привести не могу.

Если говорить о PHP - то лучшей реализацией этих паттернов на PHP безусловно является Doctrine ORM. В частности в разделе Working with Objects документации Doctrine можно найти хорошее описание и множество примеров использования паттернов, описанных выше.
Ответ написан
voronkovich
@voronkovich
Попробую привести очень примитивный пример. Допустим, мы делаем простое приложение для микроблоггинга. Каждая сущность будет иметь вид:

class Tweet
{
    private $id;
    private $content;

    public function __construct(int $id, string $content)
    {
        $this->id = $id;
        $this->content = $content;
    }

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

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

    public function setContent(string $content): void
    {
        $this->content = $content;
    }
}


Схема данных:

CREATE TABLE tweets (
    id INTEGER PRIMARY KEY,
    content VARCHAR(255) NOT NULL
)


Следующая реализация UnitOfWork будет иметь несколько ограничений:
  1. Она умеет работать только с Tweet;
  2. Она умеет только загружать сущности и сохранять произведённые в них изменения.
class UnitOfWork
{
    private $connection;
    private $identityMap;
    private $data;

    public function __construct(\PDO $connection)
    {
        $this->connection = $connection;
        $this->identityMap = [];
        $this->data = [];
    }

    public function find(int $id): Tweet
    {
        if (isset($this->identityMap[$id])) {
            return $this->identityMap[$id];
        }

        $query = $this->connection->prepare('SELECT * FROM tweets WHERE id = ?');
        $query->execute([ $id ]);

        if (false === $data = $query->fetch()) {
            throw new \Exception(\sprintf('Tweet with id "%d" not found.', $id));
        }

        $id = (int) $data['id'];

        // Исходные данные сохраняются для того, чтобы в дальнейшем вычислить изменения.
        $this->data[$id] = $data;

        $tweet = new Tweet($id, $data['content']);

        $this->identityMap[$id] = $tweet;

        return $tweet;
    }

    public function commit(): void
    {
        // Вообще говоря, лучше вычислить все изиенения, создать один "большой" запрос
        // и выполнить его внутри транзакции, но для простоты мы сделаем для каждого
        // изменения отдельный запрос
        $query = $this->connection->prepare('UPDATE tweets SET content = ? WHERE id = ?');
        foreach ($this->identityMap as $tweet) {
            if ($tweet->getContent() !== $this->data[$tweet->getId()]['content']) {
                $query->execute([ $tweet->getContent(), $tweet->getId() ]);
            }
        }
    }
}


Полный пример можете скачить и посмотреть тут: https://gist.github.com/voronkovich/d35cdcdf6eb09e...
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы