Integration Testing WCF Services with TransactionScope

Integration testing WCF services can be right pain and I experienced it firsthand on my current project. If you imagine a hypothetical person repository service exposing 4 CRUD operations, it would make sense to test all four of them. If the operations were to be tested in random order, it would be perfectly feasible that testing update after a delete may fail if the object to be updated has been deleted by a previous test. The same obviously applies to reads following updates etc. In other words tests are dependent on each other and this dependency is really evil for a number of reasons: first of all it usually means that you cannot run tests in isolation as they may depend on modifications made by other tests. Secondly, one failing test may cause a number of failures in the tests which follow and thirdly, by the end of the test run underlying database is in a right mess as it contains modified data, forcing you to redeploy it should you wish to re-run the tests. Considering the fact that running integration tests is usually time consuming exercise, this vicious circle of deploy/run/fix becomes extremely expensive as the project goes on.

TransactionScope to the rescue

Fortunately for us WCF supports distributed transactions and if there is one place where they make perfect sense it is in the integration testing. Imagine a test class written along the following lines:

image

The idea behind it is that whenever a test starts, a new transaction gets initiated. When the test completes, regardless of its outcome, the changes are rolled back leaving the underlying database in pristine condition. This means that we can break dependency between tests, run them in any order and rerun the whole lot without the need for redeploying the database. Holy grail of integration testing :) To make it work however, the service needs to support distributed transactions (which is usually a not a bad idea anyhow). Having said that  have to be aware of various and potentially serious gotchas which I will cover later.

To make a service "transaction aware" following changes have to be made (I assume default, out of the box WCF project): first of all the service has to expose an endpoint which uses a binding which in turn supports distributed transactions (e.g. WsHttpBinding) and the binding has to be configured to allow transaction flow. This configuration has to be applied on both client (unit test project) and the server side:

image

Secondly, all operations which are supposed to participate in transaction have to be marked as such in the service contract:

image

The TransactionFlowOptions enumeration includes NotAllowed, Allowed and Required flags which I hope are self explanatory. Using Allowed flag is usually the safest bet as the operation will allow callers to call the service with or without transaction scope. Making the service transaction aware as illustrated above is usually enough to make this whole idea work.

The third change which is optional but I highly recommend it, is to decorate all methods which accept inbound transactions with [OperationBehaviorAttribute(TransactionScopeRequired=true, TransactionAutoComplete = true)]. By doing so we state that regardless of the client "flowing" transaction or not, the method will execute within transaction scope. If the scope is not provided by the client, WCF will simply create one for us which means that code remains identical regardless of the client side transaction being provided. The TransactionAutoComplete option means that unless the method throws an exception, the transaction will commit. This also means that we do not have to worry about making calls to BeginTransaction/Commit/Rolback anymore. The default for TransactionAutoComplete is true so strictly speaking it is not necessary to set it but I did it here for illustration purposes.

image

The attached sample solution contains a working example of person repository and may be useful to get you started.

The small print

Important feature of WCF is the default isolation level for distributed transactions which is Serializable. This means that more often than not, your service is likely to suffer badly from scalability problems should the isolation level remain set to the default value. Luckily for us WCF allows us to adjust it; the service implementation has to simply specify required level using ServiceBehaviorAttribute. Unless you know exactly what you are doing I would strongly recommend setting the isolation level to ReadCommitted. This is the default isolation level in most SQL Server implementations and it also gives you some interesting options.

image

Having done this the caller has to explicitly specify its required isolation level as well when constructing transaction scope.

image

An interesting "feature" of using transaction scope, in testing in particular, is the fact that your test may deadlock on itself if not all operations being executed within the transaction scope participate in it. The main reason for which this may happen is lack of TransactionFlowAttribute decorating the operation in service contract. In the test below if the GetPerson operation was not supporting transactions, yet the DeletePerson was, then an attempt to read the value deleted by another transaction would cause a deadlock. Feel free to modify the code and try it for yourself. 

image 

Distributed transactions will require MSDTC running on all machines participating in the transaction i.e. the client, the WCF server and the database server. This is usually the first stumbling block as MSDTC may be disabled or may be configured in a way which prevents it from accepting distributed transactions. To configure MSDTC you will have to use "Administrative Tools\Component services" applet from the control panel. MSDTC configuration is hidden in the context menu of "My Computer\Properties". Once you activate this option you will have to navigate to MSDTC tab and make sure that security settings allow "Network access" as well as  "Inbound/Outbound transactions".

image

Performance

One issue which people usually raise with regards to distributed transactions is performance: these concerns are absolutely valid and have to be given some serious consideration. The first problem is the fact that if the service has to involve transaction managers (MSDTC) in order to get the job done it usually means some overhead. Luckily, the transaction initiated in TransactionScope does not always need to use MSDTC. Microsoft provides Local Transaction Manager which will be used by default as long as the transaction meets some specific criteria: transactions involving just one database will remain local incurring almost no overhead (~1% according to my load tests). As soon as your transaction involves other resources (databases or WCF services) it will be promoted to distributed and will get a performance hit (in my test case it is 25% decrease in performance but your mileage may vary). To check if a method executes within local or distributed transaction you may inspect Transaction.Current.TransactionInfo.DistributedIdentifier: value equal to Guid.Empty means that transaction is local. The second issue affecting performance is the fact that transactions will usually take longer to commit/rollback meaning that database locks will be held for longer. In case of WCF services the commit will happen when the results have been serialized back to the client which can introduce serious scalability issues due to locking. This problem can be usually alleviated by using ReadCommitted isolation level and row versioning in the database.

Parting shots

The project I am currently working on contains some 2500 integration tests, 600 of which test our WCF repository. In order to make sure that every test obeys the same rules with regard to transactions we have a unit test in place which inspects all test classes in the project and makes sure all of them derive from the common base class which is responsible for setting up and cleaning the transaction. I would strongly recommend to follow this approach in any non trivial project as otherwise you may end up with some misbehaving tests breaking the whole concept.

Happy testing!

November 16 2008

ThickBox, IE6 and a little secure and nonsecure item problem

So it’s been a fun packed morning looking at an issue within ie6 that was causing the “This page contains both secure and nonsecure items” prompt to be displayed then viewing a page over HTTPS.

The problem itself only reared its ugly head when the page tried to open a UI dialog to the user using the “ThickBox” add-on to jquery. So after a bit of digging around I found out that IE6 shows this message because “ThickBox” is adding an iframe to the page without the src attribute set. In order to fix the issue then all you need to do is add a dummy src attribute to the iframe when it is appended to the page by “ThickBox”. See the example below.  The bit of code you need to update can be found on line 38 within the thickbox.js file.

Original Code

 $("body").append("<iframe id='TB_HideSelect'></iframe><div id='TB_overlay'></div><div id='TB_window'></div>");

Updated Code

$("body").append("<iframe id='TB_HideSelect' src='java script:false;'></iframe><div id='TB_overlay'></div><div id='TB_window'></div>");

November 6 2008

Google Chrome: First impressions (and a few benchmarks)

Google Chrome has now been released for download - see Derek's post for some opinion on this or the "leaked" information from Google.  I've installed it and I have to say that I like it a lot - the minimalist UI is rather nice (although IE7 has drifted in a similar direction), the "omnibox" (was previously the address bar) seems to actually work in terms of how it handles it's auto completion and the initial start page is a great idea.  But the thing that really got to me was just how fast it is.  And I mean ...

fast

There's lots of numbers below where I try to measure just how fast Google Chrome is compared to IE 7, but what really impressed me was when my wife (who is non-techie) browsed a few sites using Chrome and noticed the difference in speed.

To see just how real the speed increase was, I visited a number of benchmarking sites.  QuirksMode have a benchmark for creating HTML table elements via JavaScript.  As the following table outlines, IE 7 took 4,652 ms to complete all the tests and Google Chrome took 186 ms.  That's literally an order of magnitude faster.

chrome-table-gen

Celtic Kane has a benchmark which attempts to measure the core JavaScript engine.  Again, Google Chrome is literally an order of magnitude faster than IE 7 and it looks to me as if Chrome would probably come top of his overall performance table.

chrome-javascript2

As a final test, I loaded up the SunSpider test from WebKit - this is a test which purports to target real-world usage and avoid "useless" micro-benchmarks.  I expected Chrome to do well against this, since Google say that Webkit forms a big part of the Chrome browser, but Chrome managed to beat my already high expectations.  It came in 16.9 times faster than IE 7 and some of the differences were simply staggering.

chrome-javascript

Overall, this is quite an amazing first drop of code and I didn't have a single crash or notice any rendering errors.  I'll leave others to speculate on exactly what this means for Microsoft's Internet Explorer, but I think it's fair to say that Google have raised the standard of what to expect from a browser quite considerably.

[EDIT: Rory has also posted about Google Chrome.  The interesting thing is that he also includes some numbers about memory usage.]

September 2 2008

Sharepoint feature activation and strange timeouts....

 

So I have been meaning to write a blog entry for some time now and at last I have finally manage to drag together a few coherent sentences and get the ball rolling. So what topic have I picked to start my blogging experience with at Conchango? Well Sharepoint of course!

Anyway down to business and the reason for the post is that the other day I had to deal with an issue surrounding a timeout when activating a feature via the "ManageFeatures.aspx" page within the Windows Sharepoint Services (WSS) user interface. The feature itself was somewhat of a beast and did a lot of work within the "FeatureActivated" method behind the screens setting up lists, creating content etc which meant it was going to take a long time to complete.  So a timeout issue?

Well as it turns out yes. The problem is that activating a feature via the "ManageFeatures.aspx" page means that the request to activate the feature is handled by asp.net and as such is subject to the same rules that govern any asp.net request, including maximum execution time out.  For application pages that live within the layouts directory in the 12 hive such as "ManageFeatures.aspx" this timeout value can be changed by modifying the "web.config" at the root of the layouts directory. The actual value you need to change is the attribute called "executionTimeout" within the "httpRuntime" element. An example is shown below were the execution timeout has been changed to ten minutes.

<httpRuntime executionTimeout="600" />

Additional it is worth keeping in mind that this issue will not occur if have debug set to true within the compilation element of your sites "web.config" or you are installing and activating your features via "stsadm" at the command line and as such may not be identified until a site has been deployed into a environment that mirrors production.   

July 30 2008
Newer Posts Older Posts