1

About Unit Of Work Pattern :

Unit of Work design pattern does two important things: first it maintains in-memory updates and second it sends these in-memory updates as one transaction to the database

Assume that I need to use a web service and update a DB table in same unit of work. Conventionally, I perform these steps:

  1. Open a connection.
  2. Call DB Insert method (there is no risk if it is performed, but it is not FLUSHED at that moment in UOW ).
  3. Call an external web service (an any service from other company).
  4. If web service results is 200 (OK) then call commit, else rollback.

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

unitOfWork has one method: Complete().If web service gets error, it's not problem.Because I can throw exception, so complete() method doesn't execute. But I get error in complete() I must reverse web service?

It would work fine if Insert() method transactional is performed in DB (not commit yet).

How can I do this scenario in Unit of Work pattern? Or is it absolutely necessary to have a reverse web method DecrementPeopleCount?

Adem Aygun
  • 550
  • 2
  • 6
  • 25

3 Answers3

2

In Unit of Work you manage transactional operations. In Unit of Work scope, you run some business logic and when all operation is ready, Complete or SaveChanges operations is called. This operations gives you a chance, all your operation finish successfully or all of your operations canceled. In this case, code does not works as a unit. There are two separete operations, insert operations and web service increment operations.

DecrementPeopleCount operations is not a solution. Imagine something like that: IncrementPeopleCount operation return successfull, however Complete operation return error. After this point you try to call DecrementPeopleCount but webservice is too busy or network problems occur. In this way, still your code does not works as a unit.

As a solution, you consider change your approach.

1) WebService call operation can be wrap and converted as transactional operation. I suggest a tool which is name Hangfire. It save operations name and parameters in db and transaction complete it read db and trigger registerd functions. You can save entity and call webservice operation execution in db as one operations.

2) You can save entity and publish user-created event or increment-user-count command. Your observer/consumer consumed that event/command and execute web api call.

Both of solutions gives eventually consistancy.

Adem Catamak
  • 1,987
  • 2
  • 17
  • 25
1

From the documentation on Unit Of Work:

If the method throws an exception, the transaction is rolled back, and the connection is disposed. In this way, a unit of work method is atomic (a unit of work).

You don't have to do anything, but sometimes you may want to save changes to the database in the middle of a unit of work operation.

You can use the SaveChanges or SaveChangesAsync method of the current unit of work.

Note that if the current unit of work is transactional, all changes in the transaction are rolled back if an exception occurs. Even the saved changes!

So, throw an exception before calling Complete to rollback:

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 can inherit UserFriendlyException to be shown to the user:

public class MyExternalWebServiceException : UserFriendlyException
{
    public MyExternalWebServiceException(string message)
        : base(message)
    {
    }
}
aaron
  • 39,695
  • 6
  • 46
  • 102
0

What is the externalWebService call doing? Is it synchronizing another data repo? Or is it updating a ui element?

If it is UI, then I would separate the two, because your data integrity should not care, much less rely on, a ui element update succeeding.

I could make a similar argument regarding synchronizing two data sets.

If the update call fails, it should not effect the data integrity in the source repo. Instead, compensate for failures by writing a second sync method that would retrieve the actual count from person repo and update the external source with that value in the event of a failure in the Increment call. In fact, I would recommend this update instead of an update that increments.

In this particular case, I cannot see a reason why the data integrity of your person repo should be dependent upon the success of the second call.

user7396598
  • 1,269
  • 9
  • 6
  • @user7395698 it's totally another web service from external company, IncrementPeopleCount method just a sample – Adem Aygun Apr 14 '18 at 05:45