News, examples, tips, ideas and plans.
Thoughts around ORM, .NET and SQL databases.

Wednesday, January 11, 2012

DataObjects.Net Extensions, part 2. Reprocessible tasks

This is the second part of the series of posts about DataObjects.Net Extensions, created and maintained by Alexander Ovchinnikov, the author of LINQPad provider for DataObjects.Net.
The first part was about batch server-side update and delete operations and this one is dedicated to reprocessible operations support.

Reprocessible operations

In multi-threaded environment, especially under high load there are pretty good chances of deadlocks, update conflicts, unique constraint violations, etc. Obviously, such cases must be correctly handled and this is where reprocessible tasks take up the challenge.

Which tasks are reprocessable?
  1. Reprocessible task should represent an autonomous block of logic, usually a single method or a delegate so it potentially could be executed as many times as needed in case of failure.

  2. Reprocessible task must be transactional, so it shouldn't change the state of the system unless it is successfully completed. Moreover, it shouldn't spoil outermost transaction if any.

  3. Reprocessible task should be smart enough to join active session (if present) or initialize its own one.
A simple reprocessible task might look like this:
Domain.Execute(session =>
  {
    // Task logic
  });
Note Action<Session> argument. This is required to have the ability to join current Session or open a new one. However, this sort of declaration doesn't provide any way to manage conditions of reproccesibility and the transaction isolation level to follow. Overloads of the Domain.Execute extension method with full list of arguments provide such options:
void Execute(IsolationLevel isolationLevel, 
             IExecuteActionStrategy strategy, 
             Action<Session> action);

T Execute<T>(IsolationLevel isolationLevel,
             IExecuteActionStrategy strategy,
             Func<Session, T> action);
An implementor of IExecuteActionStrategy is capable of how to react to exceptions thrown, how many attempts to execute the task to make and so on.

Task execution strategies

There are dozens of ways how to execute the tasks, that's why the strategy is an interface; you may want to implement your own strategy for some specific scenarios. However, DataObjects.Net Extensions contains several ready-to-use strategies for the most common cases:
  1. HandleReprocessableExceptionStrategy
  2. HandleUniqueConstraintViolationStrategy
  3. NoReprocessStrategy

HandleReprocessableExceptionStrategy

The strategy is used to handle any ReprocessibleException, which in turn is a erroneous situation that can be recovered by rolling back active transaction and reprocessing all actions in a new one. This strategy is applicable for the majority of scenarios and is recommended as a default one.
Domain.Execute(
    ExecuteActionStrategy.Reprocessable,
    session =>
        { 
            // do some stuff
        });
The default instance of HandleReprocessableExceptionStrategy can be accessed via ExecuteActionStrategy.Reprocessable static property. Default number of attempts is 5.

HandleUniqueConstraintViolationStrategy

The strategy is used to handle either ReprocessibleException or UniqueConstraintViolationException as latter one doesn't inherit ReprocessibleException. The strategy extends HandleReprocessableExceptionStrategy and might be especially helpful in scenarios "Add or Update entity".
Domain.Execute(
    ExecuteActionStrategy.UniqueConstraintViolation,
    session =>
        { 
            var counter = session.Query.SingleOrDefault<Counter>(ID);
            if (counter == null) {
                counter = new Counter(session, ID);
            }
            counter.Value++;
        });
The default instance of HandleUniqueConstraintViolationStrategy can be accessed via ExecuteActionStrategy.UniqueConstraintViolation static property. Default number of attempts is 5.

NoReprocessStrategy

This strategy doesn't support reprocessing. It should be used in scenarios where reprocessing is not desirable or might lead to erroneous results, for example, a task operates with non-transactional objects or services:
Domain.Execute(
    ExecuteActionStrategy.NoReprocess,
    session =>
        { 
            // on transaction rollback results of these methods can't be reverted
            SendMail();
            CreateAFile();
            DeleteAFile();
            // database-related actions
        });
The default instance of NoReprocessStrategy can be accessed via ExecuteActionStrategy.NoReprocess static property.

Custom reprocessible strategies

To utilize your own reprocessible strategy, use this pattern:
Domain.Execute(
    new MyFancyReprocessibleStrategy(),
    session =>
        { 
            // some actions
        });
The implementation of custom reprocessible strategy might be based on ExecuteActionStrategy class which is the base class for the above-mentioned strategies and is included into DataObjects.Net Extensions or can be made from scratch.

Enjoy the power of DataObjects.Net Extensions, employ server-side batches and task reprocessing in your projects.


P.S.
How to install the extension is described in the first part.