Использование как операции с базой данных, так и внешнего метода в единице работы

О шаблоне единицы работы:

Шаблон проектирования Unit of Work выполняет две важные функции: во-первых, он поддерживает обновления в памяти, а во-вторых, отправляет эти обновления в памяти как одну транзакцию в базу данных.

Предположим, что мне нужно использовать веб-службу и обновить таблицу БД в той же единице работы. Традиционно я выполняю следующие шаги:

  1. Откройте соединение.
  2. Вызвать метод БД Insert (риска нет, если он выполняется, но в этот момент в UOW не выполняется FLUSHED).
  3. Вызов внешней веб-службы (любой службы другой компании).
  4. Если результаты веб-службы равны 200 (ОК), вызовите фиксацию, иначе выполните откат.

    using (var unitOfWork = _unitOfWorkManager.Begin())
    {
        _personRepository.Insert(person);
        externalWebService.IncrementPeopleCount();
    
        unitOfWork.Complete();
    }
    

unitOfWork имеет один метод: Complete(). Если веб-сервис получает ошибку, это не проблема. Поскольку я могу генерировать исключение, поэтому метод complete() не выполняется. Но я получаю сообщение об ошибке в Complete () Я должен отменить веб-службу?

Это будет работать нормально, если транзакционный метод Insert () выполняется в БД (еще не фиксируется).

Как я могу выполнить этот сценарий в шаблоне Unit of Work? Или обязательно иметь обратный веб-метод DecrementPeopleCount?


person Adem Aygun    schedule 13.04.2018    source источник


Ответы (3)


В Unit of Work вы управляете transactional операциями. В области Unit of Work вы запускаете некоторую бизнес-логику, и когда все операции готовы, вызываются операции Complete или SaveChanges. Эта операция дает вам шанс, что все ваши операции завершатся успешно или все ваши операции будут отменены. В этом случае код не работает как единое целое. Есть две отдельные операции: операции вставки и операции увеличения веб-службы.

DecrementPeopleCount операций не является решением. Представьте что-то вроде этого: операция IncrementPeopleCount возвращается успешно, однако операция Complete возвращает ошибку. После этого вы пытаетесь вызвать DecrementPeopleCount, но веб-служба слишком занята или возникают проблемы с сетью. Таким образом, ваш код все еще не работает как единое целое.

В качестве решения вы рассматриваете изменение своего подхода.

1) Операция вызова WebService может быть обернута и преобразована как транзакционная операция. Я предлагаю инструмент, который называется Hangfire. Он сохраняет имя операции и параметры в БД, а транзакция завершает чтение БД и запускает зарегистрированные функции. Вы можете сохранить entity и call webservice operation execution в БД как одну операцию.

2) Вы можете сохранить entity и опубликовать user-created event или increment-user-count command. Ваш наблюдатель/потребитель воспользовался этим событием/командой и выполнил вызов веб-API.

Оба решения в конечном итоге дают согласованность.

person Adem Catamak    schedule 13.04.2018

Из документации по Unit Of Работа:

Если метод выдает исключение, транзакция откатывается и соединение уничтожается. Таким образом, метод единицы работы является атомарным (единица работы).

Вам не нужно ничего делать, но иногда вам может понадобиться сохранить изменения в базе данных в середине операции единицы работы.

Вы можете использовать метод SaveChanges или SaveChangesAsync для текущей единицы работы.

Обратите внимание, что если текущая единица работы является транзакционной, все изменения в транзакции откатываются в случае возникновения исключения. Даже сохраненные изменения!

Итак, сгенерируйте исключение перед вызовом Complete для отката:

try
{
    using (var unitOfWork = _unitOfWorkManager.Begin(TransactionScopeOption.RequiresNew))
    {
        _personRepository.Insert(person);

        // Save changes
        _unitOfWorkManager.Current.SaveChanges();

        var result = externalWebService.IncrementPeopleCount();
        if (result != 200)
        {
            // Rollback
            throw new MyExternalWebServiceException("Unable to increment people count!");
        }

        // Commit
        unitOfWork.Complete();
    }
}
catch (MyExternalWebServiceException)
{
    // Transaction rolled back, propagate exception?
    throw;
}

MyExternalWebServiceException может наследовать UserFriendlyException для отображения пользователю:

public class MyExternalWebServiceException : UserFriendlyException
{
    public MyExternalWebServiceException(string message)
        : base(message)
    {
    }
}
person aaron    schedule 14.04.2018
comment
Я нашел ответ Адема Чатамака более полезным, а также дал вам репутацию. Спасибо вам обоим - person Adem Aygun; 17.04.2018

Что делает вызов externalWebService? Синхронизируется ли это с другим репозиторием данных? Или это обновление элемента пользовательского интерфейса?

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

Я мог бы привести аналогичный аргумент в отношении синхронизации двух наборов данных.

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

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

person user7396598    schedule 13.04.2018
comment
@user7395698 user7395698 это совершенно другой веб-сервис от сторонней компании, метод IncrementPeopleCount просто пример - person Adem Aygun; 14.04.2018