Design pattern for enlisting a "plain old API call" in a transaction?

We have a third-party workflow server process that exposes a plain old managed .NET API with the following the following methods:

1. Connecting to the server

2. Setting datafields within the server

3. Causing the datafields to be updated within server

4. Closing the server

We have a business object with a UpdateServer() method that accepts a collection of datafield values and calls the above methods to store them in the workflow server.

We want to include the call to UpdateServer() amongst some database update calls.

We need to have these calls to execute as a single transactions - i.e. the UpdateServer() and database updates are all executed; or they are all aborted/rolled back.

In EnterpriseServices, I presume I would use CompensatingResourceManager for this.

Does anyone have a sample or design pattern I can use?

Thanks,
Michael.

[943 byte] By [MichaelHerman-Parallelspace-] at [2008-2-15]
# 1

If you are using .Net 2.0+ you can also look at System.Transactions and implement a resource manager (either volatile or durable) that is participating in the transactions and it is able to compensate the actions during failure: http://msdn2.microsoft.com/en-us/library/ms229975.aspx

As for samples for Compensating Resources Manager in EntepriseServices, here are some pointers:

http://msdn2.microsoft.com/en-us/library/8xkdw05k.aspx

http://msdn.microsoft.com/msdnmag/issues/01/03/crm/

FlorinLazar-MSFT at 2007-8-30 > top of Msdn Tech,Software Development for Windows Vista,Transactions Programming...
# 2

Thanks Forin,

We've headed down the MSDN Mag route. Fortunately, the workflow API is structured as:

.OpenConnection()

.SetDatafield()

.SetDatafield()

.Update()

.CloseConnection()

We're going to squirrel these away as LogRecords; during Prepare make sure we can Open the connection, Read the datafields and Close the connection (read only verification during Prepare); and then during Commit, call Open/SetDatafield/Close to actually update datafields in the workflow server.

Another view of this is: "Two Phase Deferred API Calling"

1. Cache the API calls as persistent log records in the worker component

2. In prepare phase, do a read-only verification (.GetDatafield) that the calls should succeed (this is possible because of the design of this API)

3. In Commit, playback the log records to actually the update APIs (.SetDatafield)

I may not have the details exactly right but I believe the strategy is correct?

Thanks,

Michael.

# 3

I believe you are on the right track as long as you follow the 2PC rules: Whatever you do during Prepare must ensure that when the TM tells you to Commit or Abort, you should be able to do that in any situation (including computer crash and reboot).

FlorinLazar-MSFT at 2007-8-30 > top of Msdn Tech,Software Development for Windows Vista,Transactions Programming...
# 4

I now have code that works when EndPrepare() returns true but doesn't work when EndPrepare() returns true (i.e. doesn't trigger the Abort() phase reliably). That is, I can see in my trace output that all the Prepare and Commit methods are being called and called in the correct order when EndPrepare() returns true but not the Abort() methods when it returns false.

My Questions

1a. Each of the 2-3 CRM samples I've found so far decorate the worker ServicedComponent class method with [AutoComplete] or explicitly call .SetAbort()/.SetComplete(). Why is this? It doesn't make sense to me because I'm expecting the success/failure of my worker method (aka transaction) to determined by PrepareRecord() with the actual success/failure being returned by EndPrepare()? I expect these methods to control the transaction; not the .SetAbort()/.SetComplete()?

1b. Question #1a above is based on my assumption that my "read-only" execution of my workflow API should be handled in PrepareRecord() when it passed the log record of values (Datafields) that it should be used for the (read-only) version of the API call. However, each of the 2-3 CRM samples I've found seem to do the "prepare logic" as the last steps of the worker ServicedComponent class method and then call .SetComplete() or .SetAbort() and always have PrepareRecord() return false. Is this the correct pattern?

That is, the only actual "active" component of my Compensator is CommitRecord()?

I REALLY hope the above makes sense.

Michael.

# 5

Florin, a couple closely related questions about the CRM I created (and works great) for wrapping/exposing a set of API calls to a workflow server as a transaction ...

Quick Recap

In the worker method, I check that the workflow server is "up", then cache the worflow API method name and parameters in a Clerk Log Record. In the CRM class CommitRecord() method, I unpack the workflow API method name and parameters and then make the "real" workflow API call.

Key Questions

1. How can my worker method return a value? ...more specifically, how can my worker method return the value that is returned from the workflow API call?

Note: Of course, in the standard design pattern for a CRM, it is important to note that the "real" workflow API call is "deferred" until CommitRecord() is executed. ...and by that time, the worker method has usually completed and returned to the client application.

2. The other simpler scenario: Suppose the client application didn't need the workflow API return value ...but the client application needed the worker method to *not* return until CommitRecord() (or AbortRecord) completed? ...i.e. same synchronization challenge (worker method to wait until CRM completes or a timeout) but without the requirement to return the workflow API return value.

Drill Down Questions

3. (Ignoring the abort scenarios for a moment), Can the worker method wait on a AutoResetWaitEvent (or something like that) until CommitRecord() saves the workflow API return value in a global variable and then signals the AutoResetWait? In general, does this approach work? ...I'm actually not sure because the worker method and CommitRecord() are in different classes. ...any ideas?

4. Are there more specific approaches you can recommend for having CommitRecord() return a value to the client application (either via the worker method or some other approach)?

Thanks (once again),

Michael.

# 6

Returning false in EndPrepare should trigger the abort of the transaction.

If you can give more details about the cases where you see Abort() not being called, I can look deeper into it. Maybe you are forgetting the log record in PrepareRecord by returning true?

1a. Using AutoComplete or explicitly calling SetComplete/SetAbort is a matter of preference. If you want to vote abort only by throwing an exception, then use AutoComplete. If you want to take a decision to abort explicitly without throwing an exception, you will use SetComplete/SetAbort. The fact that you can take a decision in the worker class to continue or abort the transaction is similar to a normal ServicedComponent: it gives you an early possibility to verify that the work under the transaction has a chance to be committed/accomplished. Imagine the case where the worker class wants to write to a file given the name; in the worker method you can verify if you actually have the rights to write to that file; if you find that you don't have the rights, then you can call SetAbort to abort earlier instead of waiting for the prepare phase to fail and abort the transaction. Look at it as an optimization.

1b. As I mentioned above, you can do this "read-only" verifications in the worker method to detect earlier that the transaction has no chance to be committed. Data managed by resource managers goes through 3 stages under a transaction:

1. While the transaction is active (i.e. during the worker method), the data is modified "temporarily" in a very volatile way; based on your isolation level, you can hide this temporary data from other transactions happening in the same time with different degrees of visibility and locking; the temporary data should be visible to other methods executing under the same transaction. In this stage you are allowed and it is recommended to do validations checks: take a money checking account for example with a value of 50. If the action requested is "substract 1000" then you should fail the call and abort the transaction because that is for sure not a possible action, since you can't go to a negative value for this type of account.

2. Second stage for the data under transaction is the "prepared" one. You get into this stage as soon as you voted yes during the Prepare phase. In this stage, the data is living into a "format" from which it can go into both directions "committed into real data" or "aborted as nothing has happened" at any time when the "transaction coordinator" is going to dictate it. Here is when you put hard locks on the data to prevent other transactions to modify the data: going back to the money account, if the action is to "substract 40" from the current balance of 50, you want to prevent other transactions to bring the balance below 40. Of course, the granularity of the locks is based on your implementation, but in any case, you absolutely must guarantee that at any time, when you get a commit request, you will "commit". Not everyone can see this prepared data (again based on the isolation levels), but for sure everyone is affected by the fact that the data is already "locked".

3. The third stage of the data under a transaction is "real" data. When commit is called, the "prepared" data is moved into actual real data. After this moment, everyone can see the update data. In our money account example, in this stage, the balance is finally updated to be (balance - 40).

I hope this helps.

FlorinLazar-MSFT at 2007-8-30 > top of Msdn Tech,Software Development for Windows Vista,Transactions Programming...
# 7

All these are good questions :) - for which the answer is not simple :)

Here are few explanations/principles:

A transaction moves into the preparing phase (and to the commit phase after that) only after Transaction.Commit is called. In case of ServicedComponents, the Transaction.Commit is called only after the root-transaction component gets deactivated (assuming SetAbort wasn't called). The root-transaction component is the component that first creates and starts the transaction. For instance, imagine two components A and B, both marked [Transaction(TrasactionOption.Requires)] and both having a method marked [AutoComplete] called DoWork. Assuming a client called A.DoWork() and A::DoWork calls B.DoWork(), A is the root-transaction component if no transaction exists in the COM+ context when A.DoWork is called. In this case, the transaction starts the two-phase-commit immediately after component A is deactivated and before A.DoWork. The good thing is that A.DoWork will wait and return only after phase 1 (preparing) finishes. If after the preparing phase the transaction moves to committing, A.DoWork will return without error. If after the preparing phase the transaction moves to aborting, A.DoWork will throw an exception saying that transaction aborted.

So, if you try to wait inside the worker method for the CommitRecord to be called, you will wait forever because CommitRecord won't be called before you exit the worked method.

On the "return value from CommitRecord" - the answer is that you should know that value to return during the worker method, and you shouldn't have to wait for CommitRecord to be called. Let's take an example. Let's say that the "API" or "resource" that you want to make it part of the transaction is an integer variable. Now, you want the worker method to add 5 to that transactional integer variable and return the new value. You don't need to wait for CommitRecord to be called to know that new value. Let's say that when you read the value of the integer in worked thread, the value is 1. The principles of a "transactional resource" should guarantee that if the transaction succeeds, the value of the integer will be 1+5=6. So, it should be "safe" to return 6 from the worker method. Now how do you do that - how do you ensure these principles for the transactional resource? It is mostly done with locking, with different levels of granularity that depends on the isolation level you want to work with - serializable being the safest one. For example, if at the moment the integer is "incremented" in the worker method, a lock is set to prevent other threads/processes from modifying the value, you can guarantee that CommitRecord will succeed and the new value will be 6.

I hope this helps.

FlorinLazar-MSFT at 2007-8-30 > top of Msdn Tech,Software Development for Windows Vista,Transactions Programming...
# 8

Thanks Florin,

I'm starting a new thread entitled "How to detect when a Compensating Resource Manager server component has completed?" in this forum.

Checkout http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=714942&SiteID=1

Michael.

Software Development for Windows Vista

Site Classified