When it comes to implementing transactions in applications, developers seem to do better job nowadays. Most of developers know what the drawbacks are of not using transactions when executing queries modifying data. I also see that more developers use TransactionScope class to make a code block transactional, but unfortunately I don’t see very often classes they build are created in the way that they could participate in transactions or rather take advantage of ambient transaction context, which is automatically managed by a resource manager, whoso actions are coordinated by a transaction manager. Resource managers work in cooperation with the transaction manager to provide the application with a guarantee of atomicity and isolation. Microsoft SQL Server, durable message queues, in-memory hash tables are all examples of resource managers. But I think everyone could find quite a few classes (or even more) that are resource managers but have not been designed to work in cooperation with the transaction manger, hence they can’t participate in ambient transactions and automatically commit changes when TransactionScope has completed or rollback changes when their operation or other participant’s operation has failed. And I think that’s the beauty of TransactionScope class and being managed by transaction manager that all operations are automatic so you don’t have to check if all operations have succeeded to invoke Commit or call Rollback when something failed as those operations are taken care of for you by the aforementioned transaction manager. But in order for a resource to participate in a transaction, it must enlist in the transaction. By enlisting, the resource manager ensures that it gets callbacks from the transaction manager when the transaction commits or aborts. A resource class is required to implement the following interfaces in order to be supported for enlistment: ISinglePhaseNotification and IEnlistmentNotification. Of course one could build a serviced component by inheriting from System.EnterpriseServices.ServicedComponent base class, but that would mean that your class is a COM+ component, which most of the time is not desirable, so ISinglePhaseNotification and IEnlistmentNotification interfaces are the way to go. So below is a simple application that calls some insert query (which is by default managed by transaction manager) and our class that puts some data in the file in the way that if either query or save to a file process has failed, both operations are rollbacked or in other words, the changes are persisted or committed only if both operations have succeeded. The application was created to demonstrate how to use the aforementioned interfaces, hence it's lacking locking or concurrancy detection mechanism but those can be added as required. The insert query I used is a simple insert statement to a table I created as below but it can be any table and any DML query.
[IRepository.cs]
[MSSQLProductRepository.cs]
[FileProductRepository.cs]
[Program.cs]
Personally I have to admit that I love those interfaces. They make building transaction-aware classes a very simple task. Not only does it help a developer in designig and building such classes, but also it simplifies writing code that consumes such classes in client applications as all commit or abort logic happens behind the scene and it is automatically managed for you by transaction manager upon instantiation of TransactionScope class. And having your class participate in ambient transactions is a guarantee of a data atomicity, consistency, isolation and durability, which is a guarantee that transactions are processed reliably, which should be a goal of every thriving software developer.
0 comments: