Abstracted Data Access Architecture and Transaction Control
I have noticed a lot of questions lately about transaction control especially pertaining to Data Access architectures, so I have decided to outline one of my approaches that addresses the topic.
Wouldn't it be nice if all of your data access transaction control came down to the following lines of code:
public class OrderFacade
{
public void Save(Order order)
{
OrderSaveVisitor visitor = new OrderSaveVisitor();
order.Accept(visitor);
CommandProcessor processor = new CommandProcessor();
processor.Process(visitor.Commands);
}
}
That's it! All of your transaction and data access wrapped up into these four lines of code. Believe it or not, it is possible. The architecture behind this allows for a complete separation and de-coupling of the entities, data access and transaction control. None of the parts are coupled to the other and you never end up repeating your transaction logic.
What allows this to take place is the basic premise that you will collect all of the data access commands for your entities and the execute them within the scope of a single transaction. The visitor pattern is used to capture all of the commands from your object hierarchy and an abstracted CommandProcessor class is created to contain the actual transaction logic. The CommandProcessor is then shared throughout your entire system allowing you to avoid explicitly declaring transactions unless you need them for some specific business process. Take a look at the implementions of the classes below to get a better idea of what is happening. You can also download my simplified solution to follow along with.
public class OrderSaveVisitor : EntitySaveVisitor<Order>
{
public void Visit(Order order)
{
// Save the order.
OrderMapper mapper = new OrderMapper();
this.Commands.Add(mapper.GetSaveCommand(order));
// Save the order items.
OrderItemSaveVisitor visitor = new OrderItemSaveVisitor();
order.OrderItems.Accept(visitor);
this.Commands.AddRange(visitor.Commands);
}
public void Visit(OrderCollection orders)
{
foreach (Order order in orders)
{
order.Accept(this);
}
}
}
The OrderSaveVisitor class above is used to traverse through the Order entity hierarchy and collect all of the commands to be executed. In order to do this, an OrderMapper class is created to abstract out the actual data access implementation. The OrderMapper class would contain your command building and Order populating logic.
public sealed class CommandProcessor
{
public void Process(List<DbCommand> commands)
{
using (TransactionScope transaction = new TransactionScope())
{
foreach (DbCommand command in commands)
{
command.ExecuteNonQuery();
}
transaction.Complete();
}
}
}
The CommandProcessor class above handles the actual transaction implementation. It accepts the commands to be executed and then executes them all within the scope of a single transaction.
While this is a simplified example, it can quite easily be expanded to an enterprise scale. One of the real beauties of this kind of solution is that your business entities are completly abstracted from any data access logic, allowing them to participate in any type of business process. Everything is de-coupled allowing for high levels of extensibility. More facade methods can be added to provide for more business processes, more visitors can be added to encapsulate business process logic that is abstracted from the entities, more mappers can be added to communicate with varying data sources, and of course lots and lots of entities can be added. Take a look at my sample solution for more information and to really see how things are wired up.
Good luck!
Comments