Modeling a Persistent Command Structure

The team I work with came up with a great way of encapsulating business transactions into commands that require no data persistence, require data persistence and require transactional data persistence. We were able to create a fairly simple structure by using generics to allow our return types to vary, and using class constructors to pass in parameters. In this fashion we were able to create a business command framework to use in a predictable and testable manner in our business layer. The basic structure is defined below:

Command Class Structure
Command Class Structure

To use the CommandBase class an implementation simply overrides Execute and does it’s business. Execute is marked as abstract in fact so an implementation must do this. A constructor is also created taking whatever data the command will need to manipulate to do it’s work. To use the PersistenceCommandBase class an implementation simply overrides the method ‘Execute(datacontext data)’.  However, PersistenceCommandBase implements the ‘Execute()’ method defined in CommandBase.  PersistenceCommandBase handles the creation and disposal of the data context in the ‘Execute()” method and then makes a call to the abstract ‘Execute(dataContext)’ method.

The logic needing persistence uses the passed in dataContext.  If the ‘Execute()’ method is called on the implementation the base class handles creating and cleaning up the dataContext.  The implementer can ignore all the messy details of creating and killing a dataContext.  The ‘Execute(dataContext data)’ method can also be invoked directly passing in an already in use datacontext.  Again the concrete implementation of the PersistenceCommandBase does not have to worry about where the IDataContext came from.  Below is what a simple implementation of the PersistenceCommandBase looks like:

public abstract class PersistenceCommandBase<T> : CommandBase<T>, IPersistenceCommand<T>
    {
        public override T Execute()
        {
            using (IDataContext data = DataContextFactory.MakeDataContext())
            {
                return this.Exectue(data);
            }
           
        }
        
        public abstract T Execute(IDataContext dataContext)
        {}
    }

Essentially we are using a template approach to centralize the creation and release of persistence contexts in our application. We took the same approach and created a TransactionalCommandBase, which looks a lot like the PersistenceCommandBase, except it handles the the details of starting and committing or rolling back a transaction, it look like the below:

public abstract class TransactionalCommandBase<T> : PersistenceCommandBase<T>
    {
        public override T Execute()
        {
            using (IDataContext data = DataContextFactory.MakeDataContext())
            {
                try
                {   data.StartTransaction();
                    return this.Exectue(data);
                    data.Commit();
                }
                catch
                {   
                    data.Rollback();
                    throw;
                }
            }

        }

        public override T Execute(IDataContext dataContext)
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }

The thing I love about this setup is the creator of a business transaction only has to decide to what level data persistence is required and then create a class extending from the appropriate base class. Developers can focus on creating and testing their business logic and let the base class handle the data context details.

Another bonus to our structure is we can create aggregate commands, that is a command that uses other commands. Once we have a data context, we can simply pass it to the ‘Execute(dataContext)’ method of another command. In this manner we can create transactional commands that wrap non transactional commands and have them still enlisted in our transaction. Below is an example:

 /// <summary>
    /// Non Transactional Save Command
    /// </summary>
    public class SaveCustomer : PersistenceCommandBase<Customer>
    {
        private Customer _customer = null;

        public SaveCustomer(Customer customer)
        {
            this._customer = customer;
        }

        public override Customer Execute(IDataContext dataContext)
        {
            dataContext.Save(this._customer);
        }
    }

    /// <summary>
    /// Class using non transaction command in transaction
    /// </summary>
    public class SaveManyCustomers : TransactionalCommandBase<List<Customer>>
    {
        private List<Customer> _customers = null;

        public SaveManyCustomers(List<Customer> customers)
        {
            this._customers = customers;
        }

        public override List<Customer> Execute(IDataContext dataContext)
        {
            for(int i = 0; i< this._customers.Count; i++)
            {
                SaveCustomer saveCommand = new SaveCustomer(this._customers[i]);
                this._customers[i] = saveCommand.Execute(dataContext);
            }

            return this._customers;
        }
    }

We’ve run into some issues, and have made some adjustments that I’ll address in subsequent posts. The first issue is testing. Since we’re passing what are essentially parameters to our commands in their constructors it makes for an interesting testing situation. We’ve adopted a command factory approach so we can essentially abstract the creation of the commands. Only the factory creates commands, so we can test to make sure the factory calls are made correctly.

Another issue we’ve run into is how to handle rollbacks in transactions that are not based on an exception. What happens if we want our command to just rollback? We’ve also had to address what to do if a transactional command is called directly with a dataContext that is not in a transaction? It seems that a transactional command should always run in a transaction even if the dataContext it is passed is not in one. Interesting issues I look forward to addressing here soon.

Leave a Reply

Your email address will not be published. Required fields are marked *