Unit Of Work

Ця стаття має кілька недоліків. Будь ласка, допоможіть удосконалити її або обговоріть ці проблеми на сторінці обговорення.
Ця стаття містить перелік посилань, але походження тверджень у ній залишається незрозумілим через практично повну відсутність внутрішньотекстових джерел-виносок. Будь ласка, допоможіть поліпшити цю статтю, перетворивши джерела з переліку посилань на джерела-виноски у самому тексті статті. (15 червня 2019)
Ця стаття не має інтервікі-посилань. Ви можете допомогти проєкту, знайшовши та додавши їх до відповідного елементу Вікіданих. (15 червня 2019)
Вступний розділ цієї статті, ймовірно, несповна підсумовує ключові тези її вмісту. Будь ласка, допоможіть розширити вступ, додавши стислий огляд найважливіших аспектів статті. (15 червня 2019)

Unit Of Work — патерн об'єктно-реляційної поведінки, мета якого полягає у відстежуванні зміни об'єктів під час транзакції. Часто використовується із патерном Repository.

Під час роботи із базою даних важливо відстежувати зміни в об'єктах, в іншому випадку дані не будуть оновлені. Це також вірно для операцій додавання та видалення.

Можна змінювати дані в сховищі при кожній взаємодії з об'єктом, але це призведе до багатьох викликів у базу даних. Очевидно це потребує підтримування транзакції відкритою, що впливає на продуктивність роботи.

Даний шаблон пропонує відстежувати всі зміни над об'єктами та вносити їх у вигляді єдиної транзакції.

Переваги та недоліки

Цей розділ має вигляд переліку, який краще подати прозою. Ви можете допомогти викласти список прозою, де це доречно. Ознайомтеся з довідкою з редагування. (15 червня 2019)

Переваги

  • UnitOfWork покликаний відстежувати всі зміни даних, які ми здійснюємо з доменною моделлю в рамках бізнес-транзакції. Після того, як бізнес-транзакція закривається, всі зміни потрапляють в БД у вигляді єдиної транзакції.
  • Фабрика для репозиторіїв
  • Лінива ініціалізація репозиторіїв (залежно від реалізації)
  • Забезпечує використання одного з'єднання до БД усіма репозиторіями

Недоліки

  • Зростає кількість класів

Опис мовою C#

Розглянемо спочатку реалізацію шаблону запропоновану Мартіном Фаулером. Вона передбачає методи для відстеження змін в об'єктах та здійснення транзакції.

public class UnitOfWork
{
    // методи для відстеження стану об'єктів
    public void RegisterAdd(object newObject)
    {
        ...
    }
    public void RegisterUpdate(object newObject)
    {
        ...
    }
    public void RegisterDelete(object newObject)
    {
        ...
    }

    // методи для здійснення транзакції
    public void Commit()
    {
        ...
    }
    public void Rollback()
    {
        ...
    }
}

Даний шаблон можна адаптувати, до мови програмування. Таким чином використаємо Entity Framework. Нехай дано деякі класи сутностей.

public class User { ... }
public class Apartment { ... }
public class Bill { ... }

А також припустимо, що патерн Repository для цих сутностей вже реалізований.

Запишемо інтерфейс до Unit Of Work та його реалізацію. Нам більше не потрібно відстежувати зміни у сутностях через те, що ця поведінка реалізована у Entity Framework. Але також це означає, що у нас виникнуть складнощі при зміні фреймворку.

public interface IUnitOfWork
{
    IUserRepository UserRepository { get; }
    IApartmentRepository ApartmentRepository { get; }
    IBillRepository BillRepository { get; }

    void Update<TEntity>(TEntity entityToUpdate) where TEntity : class;
    int Save();
}

Найпростіша реалізація матиме наступний вигляд:

public class UnitOfWork : IUnitOfWork
{
    // поля
    private readonly DbContext dataBaseContext;

    private readonly IUserRepository userRepository;
    private readonly IApartmentRepository apartmentRepository;
    private readonly IBillRepository billRepository;

    // конструктори
    public UnitOfWork(DbContext dataBaseContext)
    {
        this.dataBaseContext = dataBaseContext;

        this.userRepository = new UserRepository(dataBaseContext);
        this.apartmentRepository = new ApartmentRepository(dataBaseContext);
        this.billRepository = new BillRepository(dataBaseContext);
    }

    // властивості
    public IUserRepository UserRepository => userRepository;
    public IApartmentRepository ApartmentRepository => apartmentRepository;
    public IBillRepository BillRepository => billRepository;

    // методи
    public int Save()
    {
        return dataBaseContext.SaveChanges();
    }

    public void Update<TEntity>(TEntity entityToUpdate) where TEntity : class
    {
        if (dataBaseContext.Entry(entityToUpdate).State == EntityState.Detached)
        {
            dataBaseContext.Set<TEntity>().Attach(entityToUpdate);
        }
        dataBaseContext.Entry(entityToUpdate).State = EntityState.Modified;
    }
}

Недолік полягає у створенні всіх репозиторіїв при ініціалізації Unit Of Work. Забезпечимо їх ліниву ініціалізацію. Перепишемо властивості наступним чином:

...
    public IUserRepository UserRepository
    {
        get
        {
            if (userRepository == null) userRepository = new UserRepository(dataBaseContext);
            return userRepository;
        }
    }
...

Якщо з'являється необхідність додати нову сутність — доведеться кожний раз редагувати клас та інтерфейс, що вміщюють логіку Unit Of Work.

Також актуалізується проблема неможливості вибору специфічної реалізації репозиторію (наприклад, покращеної версії). Вирішити такі проблеми можна, наприклад, наступним чином:

public interface IUnitOfWork
{
    TRepository GetRepository<TEntity, TRepository>()
        where TEntity : class
        where TRepository : IRepository<TEntity>, new();

    void Update<TEntity>(TEntity entityToUpdate) where TEntity : class;
    int Save();
}

public class UnitOfWork : IUnitOfWork
{
    // поля
    private readonly DbContext dataBaseContext;
    private readonly IDictionary<Type, object> repositoriesFactory;

    // конструктори
    public UnitOfWork(DbContext dataBaseContext)
    {
        this.dataBaseContext = dataBaseContext;
        this.repositoriesFactory = new Dictionary<Type, object>();
    }
    
    // методи
    public TRepository GetRepository<TEntity, TRepository>()
            where TEntity : class
            where TRepository : IRepository<TEntity>, new()
    {
        Type key = typeof(TEntity);

        // add repo, lazy loading
        if (!repositoriesFactory.ContainsKey(key))
        {
            TRepository repository = new TRepository();
            repository.SetDbContext(dataBaseContext);

            repositoriesFactory.Add(key, repository);
        }

        // return repository
        return (TRepository)repositoriesFactory[key];
    }
...
}

У нинішніх реаліях важливо контролювати доступ багатьох потоків до репозиторію. Поряд із стандартними блокуваннями можна використати вбудовані можливості С#, зокрема клас ConcurrentDictionary: Реалізація може виглядати так:

public class UnitOfWork : IUnitOfWork
{
    // поля
    private readonly DbContext dataBaseContext;
    private readonly ConcurrentDictionary<Type, object> repositoriesFactory;

    // конструктори
    public UnitOfWork(DbContext dataBaseContext)
    {
        this.dataBaseContext = dataBaseContext;
        this.repositoriesFactory = new ConcurrentDictionary<Type, object>();
    }
    
    // методи
    public TRepository GetRepository<TEntity, TRepository>()
            where TEntity : class
            where TRepository : IRepository<TEntity>, new()
    {
        Type key = typeof(TEntity);

        // add repo, lazy loading
        return (TRepository)repositoriesFactory.GetOrAdd(key, factory =>
        {
            TRepository repository = new TRepository();
            repository.SetDbContext(dataBaseContext);

            return repository;
        });
    }
...
}

Залишилось вирішити, те що метод GetRepository працює із реалізацією, а не абстракцією. Скористаємось фабрикою

public class UnitOfWork : IUnitOfWork
{
    // поля
    private readonly DbContext dataBaseContext;
    private readonly ConcurrentDictionary<Type, object> repositories;
    private readonly IDictionary<Type, Func<object>> repositoriesFactory;

    // конструктори
    public UnitOfWork(DbContext dataBaseContext)
    {
        this.dataBaseContext = dataBaseContext;
        
        this.repositoriesFactory = InitializeRepositoriesFactory();
        this.repositories = new ConcurrentDictionary<Type, object>();
    }
    
    private IDictionary<Type, Func<object>> InitializeRepositoriesFactory()
    {
        return new Dictionary<Type, Func<object>>()
        {
            [typeof(IUserRepository)] = () => new UserRepository(dataBaseContext),
                                     .    .    .
        };
    }

    public void RegisterRepositoriesFromAssembly(Assembly assembly)
    {
                                     .    .    .
    }

    private void RegisterRepository(Type key, IRepository repository)
    {
                                     .    .    .
    }

    // методи
    public TRepositoryInterface GetRepository<TRepositoryInterface>()
    {
        Type key = typeof(TRepositoryInterface);
            
        return (TRepositoryInterface)repositories.GetOrAdd(key, repositoriesFactory[key].Invoke());
    }
...
}

Зв'язок з іншими патернами

  • Unit Of Work та Repository часто[джерело?] використовують в парі.

Див. також

Джерела

  • Implementing the repository and unit of work patterns [Архівовано 18 червня 2019 у Wayback Machine.]
  • Repository and unit of work pattern [Архівовано 10 серпня 2020 у Wayback Machine.]
  • Common mistakes with the repository pattern [Архівовано 13 червня 2019 у Wayback Machine.]
  • п
  • о
  • р
Основні шаблони
Абстрагування (програмування) • Делегування (Delegation) • Інтерфейс (Interface) • Інтерфейс-маркер (Marker Interface) • Незмінний інтерфейс (Immutable Interface) • Незмінний об'єкт (Immutable Object) • Функціональний дизайн (Functional Design) • Контейнер властивостей (Property Container) • Канал подій (Event Channel)
Твірні шаблони
Абстрактна фабрика (Abstract Factory) • Будівник (Builder) • Одинак (Singleton) • Прототип (Prototype) • Фабричний метод (Factory Method) • Пул об'єктів • Fluent builder • Мультитон • Лінива ініціалізація Отримання ресурсу, як ініціалізація (Resource Acquisition Is Initialization)
Структурні шаблони
Адаптер (Adapter) • Декоратор (Decorator) • Замісник (Proxy) • Компонувальник (Composite) • Міст (Bridge) • Легковаговик (Flyweight) • Фасад (Facade) • Модуль • Виділення приватного класу даних • Близнюки
Шаблони поведінки
Відвідувач (Visitor) • Інтерпретатор (Interpreter) • Ітератор (Iterator) • Команда (Command) • Ланцюжок відповідальностей (Chain of Responsibility) • Посередник (Mediator) • Спостерігач (Observer) • Стан (State) • Стратегія (Strategy) • Знімок (Memento) • Шаблонний метод (Template Method) • Одноразовий відвідувач • Null object • Специфікація • Feature toggleМультиметод • Перехоплювач (Interceptor) • Накопичувач (Collecting Parameter) • Слуга (Servant)
Функційні
Функтор • Генератор • Замикання • Монади • Каррінг • Функція зворотного виклику • Функція вищого порядкуВкладена функція • Результат (Result)
Патерни
конкурентного
програмування
Блокування • Модель акторів • Бар'єр • Монітор • Семафор • М'ютексПланувальник операційної системиЛокальна пам'ять ниток • Оптимістичне блокування (Optimistic Offline Lock) • Песимістичне блокування (Pessimistic Offline Lock) • Активний об'єкт (Active Object)
Кешування
Архітектурні
Базові шаблони
Клієнт-серверна архітектураFront end та back endТриярусна архітектура • Гексагональна архітектура (Архітектура портів та адаптерів) • Відокремлений інтерфейс (Separated Interface) • Сервісно-орієнтована архітектураМікросервісиPush/Pull модель
Шаблони об'єктного структурування
Шаблони представлення
MVCPureMVCHMVCMVPMVVMPost/Redirect/Get
Шаблони предметно-орієнтованого проєктування
Rich/Anemic модельDDD • Інваріант • EntityValue ObjectAggregate RootDTORepositoryПатерн сервісного рівня (Service Layer) • Фабричний метод (Factory Method) • Специфікація
Шаблони сервісно-орієнтованої архітектури
Архітектура
корпоративних
програмних
додатків
Базові шаблони
Об'єкт-значення (Value Object) • Гроші (Money) • Особливий випадок (Special Case) • Супертип рівня (Layer Supertype) • Відокремлений інтерфейс (Separated Interface) • Шлюз (Gateway) • Розподільник (Mapper) • Реєстр (Registry) • Плагін (Plugin) • Набір записів (Record Set) • Заглушка сервісу (Service Stub)
Шаблони логіки домену
Сценарій транзакції (Transaction script) • Модель предметної області (Domain model) • Обробник таблиці (Table Module) • Патерн сервісного рівня (Service Layer)
Шаблони сховища даних
Активний запис (Active Record) • Шлюз до даних таблиці (Table Data Gateway) • Шлюз до даних запису (Row Data Gateway) • Відображення даних (Data Mapper)
Шаблони об'єктно-реляційної поведінки
Одиниця роботи (Unit Of Work) • Мапа відповідності (Identity Map) • Ліниве завантажування (Lazy Load)
Шаблони об'єктно-реляційного структурування
Поле первинного ключа (Identity Field) • Розмітка зовнішніх ключів (Foreign Key Mapping) • Розмітка зв'язків таблиць (Association Table Mapping) • Відображення залежних об'єктів (Dependent Mapping) • Об'єднане значення (Embedded Value) • Серіалізований великий об'єкт (Serialized LOB) • Наслідування з однією таблицею (Single Table Inheritance) • Наслідування з таблицею для кожного класу (Class Table Inheritance) • Наслідування з таблицею для кожного конкретного класу (Concrete Table Inheritance) • Відображення із наслідуванням (Inheritance Mappers) • База даних звітності
Шаблони обробки об'єктно-реляційних метаданих
Відображення на основі метаданих (Metadata Mapping) • Об'єкт-запит (Query Object) • Сховище (Repository)
Шаблони вебпредставлення
Модель-вид-контролер (Model View Controller) • Контролер сторінки (Page Controller) • Єдина точка входу (Front controller) • Контролер аплікації (Application Controller) • Шаблонізатор (Template View) • Перетворювач (Transform View) • Двокрокова шаблонізація (Two Step View)
Шаблони розподіленої обробки даних
Шаблони локального конкурентного програмування
Оптимістичне блокування (Optimistic Offline Lock) • Песимістичне блокування (Pessimistic Offline Lock) • Блокування із низьким рівнем деталізації (Coarse Grained Lock) • Неявне блокування (Implicit Lock)
Шаблони збереження стану сеансу
Збереження стану сеансу на стороні клієнта (Client Session State) • Збереження стану сеансу на стороні сервера (Server Session State) • Збереження стану сеансу в базі даних (Database Session State)
Тестування
PageObjectМакет об'єкта (Mock Object) • Заглушка сервісу (Service Stub) • Скромний об'єкт (Humble Object)
Інші
Впровадження залежностейIoC контейнер • Локатор служб (Service Locator) • М'яке видалення (Soft Delete) • Auditable Entity • Entity Component System (ECS)Extract, Transform, Load (ETL)
Див. також
Design Patterns (книга) • Бізнес-логіка • Інваріант • Зв'язність (Coupling) • Пов'язаність (Cohesion) • Закон ДеметриKISSDRYYAGNITell Don't Ask • SOLID • CQRSGRASPІдемпотентністьМартін ФаулерАнтипатерн