Showing posts with label transaction. Show all posts
Showing posts with label transaction. Show all posts

Tuesday, May 1, 2012

Solving Race Conditions



Example

We have 'Tags' that can be applied to a 'Post'. When creating a post, we want to only create new tag entities when the tag does not already exists. A race condition can exist when two posts with the same new tag are created at the same time as the check if the tag exists can be false for both posts.

Solutions

1) Always create a new tag - we don't really care that we have duplicate tags if we always perform operations on the tagName, not the actual instance of a tag - this in ddd would be a 'Value Object'.

Side effects are; a potential drop in performance, and an increased database size

2) Use Database constraints to mark the field as unique, so the second post will attempt to create the tag and fail.

Side effects are; it is difficult to impose unique constraints in ORM, not all underlying providers (object store for example) will support this, and requires 'retry' logic.

3) Use a Transaction to check the existance of the entry before the insert.

Side effects are; a drop in performance from transactional locking and increased solution complexity.

4) Use a messaging service model for processing each post creation as a separate operation so the race conditions wont exist.

Side effects are; an increase in application complexity due to the asynchronous nature of the queue, and reduced performance due to the overhead (albeit with an increased scalability)

Conclusions

Option 1 in this scenario is entirely valid but this is not always the case, often guaranteed uniqueness is important, so using this as a general solution to race conditions is not appropriate.

Options 2 and 3 are very reiliant on the functionality of the underlying data provider.  In most LOB solutions is probably OK, but it is not as flexible and scalable as I would like.

Option 4 seems like a fairly drastic change in application design, but in reality this should not be as large an impact as you would think.

Friday, November 11, 2011

The difference between bad code and good code

A colleague asked for some advice today on a project that he inherited (which I am extending with a separate module incidentally).

The issue was related to the usage of Entity Framework in the code that he had to maintain, and he needed some advice on how to proceed.  The problem was that the service layer was calling the repository multiple times, but each repository method was wrapped in a separate unit of work.
e.g.

public void DeleteEntity(int entityID)
{
    using (var context = EntityContext())
    {
        var entity = context.Postings.SingleOrDefault(p => p.entityID == entityID);
        context.Entities.DeleteObject(entity);
        context.SaveChanges();
    }
}
and
public Entity GetPosting(int entityID)

{
    using (var context = EntityContext())
    {
        return context.Entities.FirstOrDefault(p => p.entityID == entityID);
    }
}
This caused two problems for the developer, who needed to perform a complex action in his service that referenced multiple repository calls.
  1. He had no control over the transactional scope for the repository methods
  2. Each operation was on a separate EF context, so the service could not load and entity, edit it, and then save the changes (unless the repository was designed for disconnected entities, which it wasn't).
From a maintainability and testability point of view this was also a very poor design, as the repository methods created instances of dependency object (the service method also created instances of the repositories, making the services inherently untestable).


The version of this design that I implemented for my component follows a similar service/repository/entity pattern, but is implemented in a far more testable and robust manner.

The first improvement over the legacy design is in the dependency management
My service accepts a context and all required repositories in the constructor, and my repositories accepts a context, which allows for improved maintainability (all dependencies are described) and testability (all dependencies can be mocked).  This also allows us to use dependency injection/IoC to create our object instances.

The second improvement was in the Unit of Work design.
Rather than have each repository method as a single unit of work, the service methods are the units of work, so any action within the service uses the same context (as it is passed as a dependency to the repositories that the service uses), and each service call acts as a Unit of Work, calling SaveChanges at the end of the service to ensure that the changes act under a single transaction.
There are limitations to this design (your public service methods become an atomic transaction and you should not call other public methods from within another method) but for simplicity and maintainability it is a pretty good solution.

Below is a simple example of the design I am using, preserving maintainability, testability, and predictability.  I'm not saying it is necessarily the best code around, but it solves a number of issues that I often see in other developers code.

public class HydrantService
{
  public HydrantService(HydrantsSqlServer context, EFRepository<Hydrant> hydrantRepository, EFRepository<WorkOrder> workOrderRepository, EFRepository<HydrantStatus> hydrantStatusRepository)
  {
    _context = context;
    _hydrantRepository = hydrantRepository;
    _workOrderRepository = workOrderRepository;
    _hydrantStatusRepository = hydrantStatusRepository;
  }
  public void createFaultRecord(WorkOrder order)
  {
    HydrantStatus status = _hydrantStatusRepository.GetSingle<HydrantStatus>(x => x.StatusCode == "Fault"); //_context.HydrantStatuses.Where(x => x.StatusCode == "Fault").FirstOrDefault();
    order.Hydrant.HydrantStatus = status;
    _workOrderRepository.Add(order);
    _context.SaveChanges();
  }
}

  public class EFRepository<T>
 {
 public EFRepository(IDbContext context)
 {
    _context = context;
  }   public virtual ICollection GetAll()

  {
    IQueryable query = _context.Set();
    return query.ToList();
  }
}