Как на самом деле должен обрабатываться шаблон UnitOfWork - PullRequest
0 голосов
/ 29 октября 2018

Что должен действительно обрабатывать UoW?

На мой взгляд, UoW не должен обрабатывать коммиты и откаты. Он должен просто предоставить средства для этого и должен отвечать только за отслеживание изменений в объектах, которые должны быть зафиксированы таким образом, чтобы, если они находятся в каком-либо несовместимом состоянии или что-то изменилось, транзакция не состоялась?

Таким образом, я вижу UoW примерно так.

public interface IUnitOfWork : IDisposable
{
    IAtomicTransaction BeginTransaction();

    Task<object> SaveAsync<TEntity>(TEntity entity);

    Task UpdateAsync<TEntity>(TEntity entity);

    Task RemoveAsync<TEntity>(TEntity entity);
}

Передача должна быть примерно такой:

interface IAtomicTransaction : IDisposable 
{
    void Commit();

    void Rolleback();
}

Взять, к примеру, этот пост (и не только этот, но и многие подобные),

http://jasonwatmore.com/post/2015/01/28/unit-of-work-repository-pattern-in-mvc5-and-web-api-2-with-fluent-nhibernate-and-ninject

Если вы посмотрите, то увидите, что он использует ISession в репозитории, что, по моему мнению, является ошибкой, поскольку оно напрямую связывает репозиторий (ваш бизнес) с ISession NHibernate. Не должен ли UoW взять на себя эту ответственность? Должны ли вы начать менять реализацию своего бизнеса, потому что вы меняете структуру? Разве UoW не должен действовать как Адаптер, что-то вроде антикоррупционного слоя?

Ответы [ 2 ]

0 голосов
/ 31 октября 2018

Как сейчас вы отредактировали свой вопрос, я должен изменить способ ответа; отсюда второй ответ. Мой первый ответ по-прежнему ценен (надеюсь;)).

UoW должен автоматически выяснить, какие (если они есть) изменения необходимо сбросить.

IAtomicTransaction BeginTransaction();
void Commit();
void Rolleback();

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

Если вы хотите обойти этот недостаток, см. Альтернативу в моем первом ответе.

Task<object> SaveAsync<TEntity>(TEntity entity);
Task UpdateAsync<TEntity>(TEntity entity);
Task RemoveAsync<TEntity>(TEntity entity);

Вышеуказанные методы являются частью репозитория. Это не может быть частью UoW. UoW должен автоматически выяснить, что делать на основе отслеживания изменений. Если мы ограничим наше обсуждение только транзакцией с базой данных, то DbTransaction обрабатывает это правильно. Более подробные способы отслеживания изменений см. В моем первом ответе.

Ниже приведена реализация на основе NHibernate. Обратите внимание, что это не рекомендуется . Если вы используете полную ORM, вам следует избегать такого типа реализаций, так как это мало добавляет ценности вашему дизайну. Если вы просто замените ISession на IDbConnection, это может быть реализовано и в ADO.NET

public interface IUnitOfWork : IDisposable
{
    void Flush();
}

public sealed class UnitOfWork : IUnitOfWork
{
    public UnitOfWork()
    {
        session = SessionFactroyHelper.CreateSession();
        transaction = session.BeginTransaction();
    }

    ISession session = null;
    ITransaction transaction = null;

    void IUoWSession.Flush()
    {
        transaction.Commit();
    }

    void IDisposable.Dispose()
    {
        transaction.Dispose();
        transaction = null;
        session.Dispose();
        session.Dispose();
    }
}

Кстати, сама эта тема основана на мнениях. Как реализовать UoW и репозитории - это индивидуальное дизайнерское решение. Если вы действительно заинтересованы в реализации правильного (?) UoW, рассмотрите возможность использования некоторого расширенного ORM непосредственно в коде в обход оболочки UoW.

0 голосов
/ 30 октября 2018

UoW на больше , чем просто транзакция. Ниже приводится цитата Мартина Фаулера :

Единица работы отслеживает все, что вы делаете во время бизнес-транзакции, которая может повлиять на базу данных. Когда вы закончите, он выяснит все, что нужно сделать, чтобы изменить базу данных в результате вашей работы.

Стоит:

  1. Укажите контекст для ваших репозиториев
    Либо разрешите создавать хранилища из этого экземпляра UoW, либо разрешите внедрить этот экземпляр UoW в хранилище. Я предпочитаю первый подход. Все репозитории с конструктором internal и некоторыми фабричными методами в UoW создают репозитории.
  2. Отслеживать изменения в этом контексте
    Это сложная тема, но для простоты ограничимся транзакцией базы данных. Многие ОРМ справляются с этим лучше. Отслеживание состояния объектов Entity может быть выполнено несколькими способами. Вести список изменений, поддерживать состояние (грязное или нет) объекта или сохранять исходную копию объекта и сравнивать его с окончательной копией в конце и т. Д.
  3. Flush / Don't-Flush изменения, сделанные в этом контексте
    Это связано с вышеуказанным пунктом. На основе отслеживания решите, какие изменения необходимо перенести в хранилище. Некоторые реализации UoW также поддерживают автоматическую очистку, когда UoW автоматически решает , когда , чтобы сбросить изменения. Изменения сбрасываются, если все было в порядке; не покраснел, если была проблема. Опять же, давайте для простоты ограничим это транзакцией базы данных. Для подробных реализаций лучше использовать ORM.
  4. Создание и очистка ресурсов
    Экземпляр UoW должен создавать (автоматически или вручную) ресурсы, необходимые для выполнения действий, и очищать их, когда они больше не нужны.

interface IUnitOfWork : IDisposable
{
    IDbConnection Connection { get; }
    IDbTransaction Transaction { get; }
    void Begin();
    void Commit();
    void Rollback();

    IRepository CreateRepository(....);
}

Метод CreateRepository создает экземпляр хранилища под этим UoW. Таким образом, вы можете использовать один и тот же UoW в нескольких репозиториях. Таким образом, одна транзакция БД может быть распределена по нескольким репозиториям. Другой альтернативой является внедрение UoW в хранилище, как показано здесь .

Проблема с этим подходом состоит в том, что он не заставляет UoW. Вызывающая сторона может (или должна) начать транзакцию.


Другая мини-версия UoW (которая заставляет UoW), которую я могу себе представить, выглядит примерно так:

public sealed class UoWSession
{
    public UoWSession()
    {
        //Open connection here
        //Begin transaction here
    }

    IRepository CreateRepository(....)
    {
        //Create and return the requested repository instance here
    }

    void Commit()
    {
        transaction.Commit();
    }

    void Dispose()
    {
        //If transaction is not commited, rollback it here.
        //Cleanup resources here.
    }
}

Без использования ORM вы должны выставить что-то , которое говорит вам, что все было хорошо. Выше реализация использует Commit метод.
Может быть простое свойство, скажем, IsAllWell, которое по умолчанию равно false, и вызывающий код устанавливает его явно. Затем ваш метод Dispose фиксирует или откатывает транзакцию на основе свойства. В этом случае вам не нужно показывать метод Commit, поскольку вы обрабатываете его внутри флага.

...