Unit of work



Паттерн Unit of work (единица работы) предназначен для того, чтобы следить за изменениями объектов и потом координировано их сохранять в базу данных.



Это позволяет:



• Ограничить время жизни транзакции

• Не выполнять обращение к БД сразу при выполнении изменений, а значит попытаться сделать это более эффективно

• Более удобно следить за изменениями в случае сложной иерархии или большого количества типов моделей.



Принцип использования Unit of Work состоит из двух этапов:



1. Сначала мы регистрируем в нем, что с нашими моделями были изменения (register_new, register_dirty, register_deleted).

2. Затем в какой-то момент сохраняем все эти изменения в БД (commit)



Изменения могут регистрировать как сами модели, так и прикладной код, использующий их. Таким образом, каждый раз, когда мы что-то делаем с моделями (добавляем, удаляем, изменяем), мы не отправляем сразу запрос в БД, а вместо этого добавляем эти изменения в UoW для последующего сохранения.



Хотя Unit of Work имеет метод для коммита изменений, он является более сложной вещью чем просто управление транзакциями. Суть его в том, чтобы накапливать изменения перед отправкой в базу данных. При этом он может выполнять оптимизации запросов, например, объединяя вставку данных в одну таблицу в один запрос. Также, в нем может быть реализована логика контроля целостности данных, например, с помощью оптимистических блокировок.



Сам Unit of work обращается в БД не напрямую, а через отдельные объекты, реализующие паттерн Data Mapper. Условно, в данном случае, каждый такой объект умеет отправлять в БД изменения (insert, update, delete) модели определенного типа и UoW знает в какой из мапперов обращаться для каждой из сохраненных моделей. Обратите внимание, что Unit of Work не используется для доступа к мапперам / шлюзам к БД, его задача другая. Более того, использование его в таком смысле будет нарушением принципа разделения интерфейсов.



С концепциями Unit of Work и Data Mapper тесно связан паттерн Identity Map, когда мы храним реестр загруженных экземпляров моделей для их идентификаторов. И, хотя оба из них могут использоваться независимо друг от друга, хорошей идеей будет реализация Unit of Work, использующего IdM.



Некоторые ORM, такие как SQLAlchemy, самостоятельно реализуют паттерн Unit of work: каждый экземпляр модели SQLAlchemy связана с объектом Session и её изменения записываются в базу данных в момент вызова session.flush()/session.commit().



Пример одной из возможных реализаций: https://github.com/Tishka17/python-uow-demo



Дополнительные материалы:

https://martinfowler.com/eaaCatalog/unitOfWork.html

https://martinfowler.com/eaaCatalog/dataMapper.html

https://techspot.zzzeek.org/2012/02/07/patterns-implemented-by-sqlalchemy/