Google
WWW Yariv Hammer's Code Site

Tuesday, February 28, 2006

Transactions in Enterprise Services

Introduction
One of the most important features in COM+ is distributed transactions. A local transaction is a transaction in which we open a connection, perform a series of actions, and close the connection. In a distributed transaction we have several connections we need to synchronize in order to be able to perform several related actions simultaneously.
An example: We have two bank accounts. We need to move 100$ from one account to the other. In order to achieve that we need to decrease the amount of money in the first account and increase the amount of money in the second account. But what if one of the operations fails? There could have been a network problem, an electrical power interruption or a malfunction in the database. If we managed to increase the money in the second account but failed to decrease the money in the first account, we will loose money. We need a way to perform both operations as one unit - if one of them fails - they both fails. Only if both succeeds - they all succeed. Transactions can help in those situations.

Two Phases Commit
DTC of Microsoft works in an algorithm called Two Phases Commit. In a One Phase Commit we will perform some operations in a serial way, and at the end, if we succeeded, we commit the changes. In our case, each transaction is split into sevral local transaction. At the first action COM+ handles the transaction. First we perform the Prepare Phase - each component must indicate that it is prepared to commit. There is no actual Commit yet. If one of the component aborted the transaction, we do not even enter the second phase. If all the components voted that they are prepared, we perform the Commit Phase. The DTC waits for a vote of Commit from all the component. After Fail or Timeout, all the components are told to Roll-back the transaction, and return to the state from before the transaction.

In COM+ we use DTC. SQL Server and MSMQ are examples for services which support Two Phase Commit, and thus are considered as valid Resource Managers. For other systems, such as Oracle, which doesn't support Two Phase Commit, we have the CRM (Compensating Resource Manager) mechanism in COM+. It helps us to implement a class that will manage the resource in the COM+ way.

The Requirement of Transactions - ACID
Atomicity - The transaction perform several operations that are considered as one unit - all succeed or all fail.
Consistency - Information is not lost during the transaction. If the transaction was commited, we are at a stable consistent state. If the transaction rolls-back we are at the state prior to the transaction.
Isolated - There are no outside interference during the transaction. Components which do not participate in the transaction cannot access the resources while the transaction is in action. In order to achieve this, we must of some synchronization mechanism (we have COM+ of course for that too).
Durable - Failures can be recovered. In case of a failue the system, once working again, should be at a consistent state. DTC uses a logging mechanism in order to achieve that.

Setting Up Transactions Using COM+ Configuration Tool
Each component, in the Transaction tab, has the following options:
Disabled - The component is not built in a technology in which transactions are supported.
Not Supported - The component does not participate in any transactions.
Supported - The component will participate in a transaction only if the creating component is in a transaction
Required - The component will always participate in a transaction. If the calling component is in a transaction, the component will exist in that transaction. If the caller do not participate in a transaction, the component will start its own transaction.
Required New - The component always start a new transaction.

When you program a transaction, the first step would be to take a pen or pencil (or your favourite case tool), and draw the components and their relation. Lets do an example: We have a BankManager which has a method MoveFunds. The method should take money from one account into a second account. So Account will have AddFunds and RemoveFunds methods. We will set the BankManager to be Require New, and the Account to be Supported. This way, whenever we call MoveFunds we start a new transaction. The class will use Accounts, and the AddFunds and RemoveFunds will participate in the transaction. If one of the methods will fail, the whole transaction will fail, and the money will be restored into the first account.

The costs of the transactions are management of flags. JITA (Just-In-Time-Activation) is on if you use transaction. Each objects is created when it is called in the trasaction, and is released when it is not used. This insures isolation. Another mandatory feature is synchronization.
In order to support JITA there is a Done flag. In order to support consistency there is Consistent and Abort flags. There are also costs in performance. It is better not to include too many components in the same transaction.

Programming Transactions
When you want your component to participate in a transaction, you mark it with attribute [Transaction]. The properties of this attribute correspond with the Transaction Tab in the COM+ Configuratin Tool: TransactionOptions contains enumerations of all transaction support section (Requirted, Supported, etc). Take for example the case of RequiredNew: any call to any method of the class will start a new transaction. You can also set Isolation and Timeout.

In the beginning of transaction the Consistent flag of all components is set to true. Once we do a change in state which makes it no loger consistent, we set the flag to false. The Done flag is set to false in every component. Once we call the SetComplete method in the end of the operation the Done flag is set to true. If we call the SetAbort method, the Done flag will be true, but the Consistent flag will be false, and the whole transaction will be rolled back. Once every component called the SetComplete method, the transaction succeeds. It takes only one component to abort for the whole transaction to fail.
We can have an intermediate state, on which there is a problem, but the caller component might be able to fix it. For example, the database failed to respond, but the caller can start it up and the transaction can continue. In this case we can call the DisableCommit which leaves both the Done and the Consistent flags in false. The object still lives, and at any other point the component will be able to call the SetComplete method.

The ContextUtil class has static properties and methods that can help us manage the transactions.
- SetComplete - Done = true Consistent = false. Commit transaction.
- SetAbort - Done = true Consistent = true. Abort Transaction.
- EnableCommit - Done = false Consistent = true. The transaction can be commited. The object cannot be deactivated
- DisableCmmit - Done = false Consistent = false. The transaction should not commit (but it can be changed).
- DeactivateOnReturn - Controls the Done flag
- MyTransactionVote - Controls the Consistent flag

---------------------------------------------------
[Transaction(TransactionOption.Required)]
class Account:ServicedComponent
{
public class AdjustBalance(int account, decimal amount)
{
try
{
PrepareTransfer();
ExcecuteTransfer();
ContextUtil.SetComplete();
}
catch
{
ContextUtil.SetAbort();
}
}
}
--------------------------------------------------
You can have a shortcut by using the [AutoComplete] attribute. The SetComplete will automatically be called for you when the method is over, unless an exception is thrown, and in this case SetAbort will be called for you:
--------------------------------------------------
[AutoComplete(true)]
public class AdjustBalance(int account, decimal amount)
{
PrepareTransfer();
ExecuteTransfer();
if (fail)
{
throw new exception("Insufficient funds");
}
}
-------------------------------------------------

Any component that is instanciated in the methods PrepareTransfer or ExecuteTransfer with transaction set to Supported or Required will be in the same transaction, need to vote SetComplete in order for the transaction to work, and will be created by the JITA rules.

Summary
I showed how transactions work, what are the costs, how to configure them in the COM+ Configuration Tool, and how to program them.
I showed the Transaction attribute attached to the ServicedComponent. I showed the usage of ContextUtil in order to vote Complete or Abort. I showed the usage of the AutoComplete attribute as a shortcut.

0 Comments:

Post a Comment

<< Home

Feel free to use everything here. Add links to my site if you wish.

Do not copy anything to other sites without adding link to here.

All the contents of the site belong to Yariv Hammer.