Что такое паттерн Unit of Work?

Ответ

Unit of Work (Единица работы) — это поведенческий паттерн проектирования, который отслеживает все изменения, сделанные с бизнес-объектами в ходе транзакции, и координирует запись этих изменений в источник данных (например, базу данных). Его основная цель — обеспечить целостность данных и минимизировать количество обращений к БД, группируя несколько операций в одну команду.

Аналогия: Представьте, что вы делаете покупки в супермаркете. Unit of Work — это ваша корзина. Вы кладете в нее товары (новые объекты), меняете их (изменяете объекты), убираете обратно на полку (удаляете объекты). Оплата на кассе (Commit) — это момент, когда все изменения применяются разом.

Ключевые обязанности Unit of Work:

  1. Отслеживание новых объектов, которые должны быть вставлены.
  2. Отслеживание измененных ("грязных") объектов, которые должны быть обновлены.
  3. Отслеживание удаленных объектов.
  4. Управление Identity Map — кэшем, гарантирующим, что один бизнес-объект будет представлен в памяти только одним экземпляром.
  5. Выполнение всех накопленных операций в правильном порядке (вставки, обновления, удаления) при вызове commit().

Практическая реализация в .NET с Entity Framework Core:

// DbContext в EF Core является реализацией Unit of Work.
using (var context = new AppDbContext()) // Начало Unit of Work
{
    // 1. Отслеживание новой сущности (будет INSERT)
    var newAuthor = new Author { Name = "Джон Смит" };
    context.Authors.Add(newAuthor);

    // 2. Отслеживание изменений сущности (будет UPDATE)
    var existingBook = context.Books.First(b => b.Id == 1);
    existingBook.Price = 29.99m; // EF Core отслеживает это изменение

    // 3. Отслеживание удаления сущности (будет DELETE)
    var bookToDelete = context.Books.Find(5);
    context.Books.Remove(bookToDelete);

    // 4. Все изменения (INSERT, UPDATE, DELETE) выполняются одной транзакцией
    // при вызове SaveChanges().
    context.SaveChanges(); // Commit Unit of Work
}

Преимущества использования:

  • Производительность: Группировка запросов (batch operations) снижает нагрузку на БД.
  • Целостность: Все изменения либо применяются вместе (Commit), либо откатываются (Rollback), что поддерживает согласованность данных.
  • Упрощение кода: Разработчику не нужно вручную отслеживать, какие объекты изменились и когда их сохранять.
  • Снижение риска Deadlock: Правильный порядок операций (например, вставки перед обновлениями) помогает избежать взаимных блокировок.

Паттерн является фундаментальным для большинства ORM, таких как Entity Framework, Hibernate, Doctrine.

Ответ 18+ 🔞

А, слушай, вот про эту самую Unit of Work! Ну, паттерн, который за тобой как нянька — следит, что ты там натворил с объектами, и потом разом всё в базу вываливает. Представь, ты в магазине: набрал в корзину, что-то обратно на полку сунул, что-то в руках помял. А потом подходишь к кассе — вот этот момент оплаты и есть Commit. До него ты можешь делать что угодно, а база данных даже не подозревает, что ты там вытворяешь. Доверия ебать ноль у неё к тебе, пока не скажешь "сохранить".

Суть, если по-простому: Это такой менеджер изменений. Он знает:

  1. Что ты добавил нового (INSERT).
  2. Что ты испортил (изменил) в старом (UPDATE).
  3. Что ты выкинул нахуй (DELETE).
  4. И главное — следит, чтобы один и тот же объект в памяти не размножался как таракан, а был в одном экземпляре (это Identity Map, хитрая жопа, но полезная).
  5. А когда ты говоришь "всё, я закончил" (commit()), он берёт всю эту кучу изменений и аккуратно, в правильном порядке, отправляет в базу. Не по одному, а пачкой! Овердохуища запросов экономит.

Смотри, как это в .NET на EF Core выглядит, ёпта:

// DbContext в EF — это и есть готовый Unit of Work, блядь. Бери и пользуйся.
using (var context = new AppDbContext()) // Начинаем сеанс работы
{
    // 1. Новый автор — сунули в корзину (будет INSERT)
    var newAuthor = new Author { Name = "Джон Смит" };
    context.Authors.Add(newAuthor); // Контекст это запомнил

    // 2. Нашли старую книжку и цену поменяли (будет UPDATE)
    var existingBook = context.Books.First(b => b.Id == 1);
    existingBook.Price = 29.99m; // Контекст это тоже увидел и зафиксировал

    // 3. Нашли какую-то пятую книжку и решили, что она не нужна (будет DELETE)
    var bookToDelete = context.Books.Find(5);
    context.Books.Remove(bookToDelete); // И это записал

    // 4. ВСЁ! Магия! Вместо трёх отдельных походов в базу — один жирный запрос.
    // Все три операции (добавить, обновить, удалить) упакуются и выполнятся в одной транзакции.
    context.SaveChanges(); // Вот он, наш Commit! Касса!
}

И зачем этот цирк, спросишь? А вот зачем:

  • Скорость: Не дёргаешь базу по мелочам. Один раз впендюрил все изменения — и свободен. Бензина (ресурсов) жрёт в разы меньше.
  • Целостность, блядь: Либо всё сохранится как надо, либо, если что-то пошло не так, нихуя не сохранится (Rollback). Не будет такого, что автора добавили, а книжку его — нет. Волнение ебать отпадает.
  • Голова не болит: Не надо самому париться, что там изменилось, что удалилось. Сделал дело — вызвал SaveChanges. Терпения ноль ебать на отслеживание этого вручную.
  • Меньше мороки с блокировками: Сам выстраивает операции в безопасном порядке, чтобы не получить deadlock и не охуеть посреди транзакции.

Короче, ты веришь, чувак, этот паттерн — основа всех нормальных ORM вроде Entity Framework или Hibernate. Без него был бы пиздец и ручное управление каждым чихом в базе.