In this third article in a series about BizTalk Server 2006 R2 integration with Windows Communication Foundation (.NET 3.0), the focus shifts to consuming advanced services from BizTalk. Specifically, this article explains WCF transactions and how BizTalk participates in them.
Transaction Service Setup
Before diving into how BizTalk uses transactional WCF services, it seems appropriate to first explain the basics of WCF transaction support. Transactional programming can be tricky, and the WCF development team chose to make the use of transactions opaque by utilizing a declarative model and not forcing the developer to explicitly engage transactions themselves. By decorating contracts and operations with specific attribute values, a service is capable of fairly robust transactional behavior.
WCF makes it possible to send a client transaction to a service, thus allowing a transaction to be distributed across platforms and environments. However, it’s key to remember that not all WCF binding protocols support transactions being sent from the caller. From the BizTalk perspective, only WSHttpBinding, NetTcpBinding, and NetNamedPipeBinding are capable of flowing transactions.
Once a binding is selected, transaction flow can be turned on via a service configuration flag (transactionFlow = “true”). However, just because transaction flow is turned on for a binding doesn’t mean that the service associated with that binding will use that transaction. Instead, the service contract operations (and implementation) explicitly “opt-in” to use the client transaction.
There are three values that can used when decorating service contracts with transaction flow declarations: Allowed, NotAllowed or Mandatory. If the value is set to TransactionFlowOption.Allowed, this means that if a transaction is flowed from the client application, the service operation may participate in that transaction. A TransactionFlowOption.NotAllowed, the default value, means that even if a transaction is flowed, the operation will ignore it. Finally, a TransactionFlowOption.Mandatory setting requires a client to flow a transaction to the operation.
Before looking at why one flow option might be chosen over another, let’s look at how the service implementing this contract further specifies transaction settings. First, there is the TransactionScopeRequired attribute. This attribute is how WCF knows how to handle the ambient transaction. What’s the ambient transaction? This is the transaction that a particular block of code is executing under. This cool concept was introduced by the .NET Framework 2.0’s Transaction class and allows a developer to always find out the current transaction via Transaction.Current. If there is no transaction surrounding that statement, the result is a null value. The TransactionScopeRequired is how one designates a service operation to use the flowed client transaction. The default value, “TransactionScopeRequired = false”, means this operation doesn’t want to use the flowed transaction, or have WCF create one automatically either. A “TransactionScopeRequired = true” means that the operation will always have an ambient transaction. If the client flows a transaction, it will be used by an operation decorated with this attribute. If the client does not flow a transaction, or one is not allowed (via TransactionFlowOption.NotAllowed), then WCF will create a fresh new transaction and wrap the operation in it. Very useful.
The second attribute one can put above the operation is TransactionAutoComplete. If this value is set to true, the default value, then WCF automatically commits the transaction once the operation successfully completes. If this value is set to false, then the transaction remains open and must be explicitly closed or rolled back.
Why choose one binding over another? Juval Lowy explains the choice quite well in his excellent book Programming WCF. Think of TransactionFlowOption.Allowed + TransactionScopeRequired = true as a “Client/Service mode. “ That is, the services use the client transaction, if available. If the client flows a transaction, then the client and service(s) can commit as a single atomic action. If no transaction is flowed, that’s fine to as there still exists the protection of a local transaction. This is a good option if the service(s) may be used in a standalone fashion and you don’t want to force a flowed transaction.
The second option is a more aggressive, but more consistent, “Client mode.” Using TransactionFlowOption.Mandatory + TransactionScopeRequired = true, one forces the service to participate in the client’s transaction. This model reduces potential for deadlocks and ensures that all operations commit atomically.
The third option is a “Service mode” reflected with TransactionFlowOption.NotAllowed + TransactionScopeRequired = true. Here the client transaction is always suppressed, and the local operation always enlists a new transaction. This is a valid choice when the operation should operate outside the scope of a parent transaction. Juval’s rule is that this mode is appropriate when the service’s transaction is more likely to commit than the client. He points to a “logging” service that shouldn’t be rolled back if a parent/client transaction fails later on. It should be independent. One should use caution with this mode because it’s fairly easy to get caught in an inconsistent state where parts of the functions have succeeded and parts have failed. This will be demonstrated later in this article.
The final possible transaction configuration mode is a “None mode” where no transaction is flowed or used. One would set the TransactionFlowOption.NotAllowed + TransactionScopeRequired = false if the operation was non-essential or shouldn’t cause the parent transaction to fail if an error occurred.
With that simple transaction primer out of the way, let’s look at the services used in this demonstration. In this fictional “Event Booking” service, there are three operations: “AddAttendees”, “RegisterRoom” and “RegisterCatering.” Each one accepts data and updates a SQL Server 2005 database. In reality, these services should all be set to use the “Client mode” where all operations must succeed together for the Event to be formally booked. However, to mix it up a bit, the “RegisterRoom” operation uses a TransactionFlowOption.NotAllowed setting, while all three operations still have “TransactionScopeRequired = true.” This means the “RegisterRoom” operation will use a transaction, but not the flowed one.
To call these services and to flow a transaction in a standard .NET project, the following code would work:
A TransactionScope object is created, and inside that transaction, all three service operations are called. The result?
The SQL query returns the committed results inside the three database tables, and TCPTrace shows that the “AddAttendees” and “ReserveCatering” had a value for Transaction.Current.TransactionInformation.DistributedIdentifier (the flowed transaction’s ID), while “ReserveRoom” has a null flowed transaction ID. This makes sense since “ReserveRoom” explicitly blocks flowed transactions.
The BizTalk usage of these services, at first glance, is fairly straightforward. The bits generated via the BizTalk WCF Service Consuming Wizard (schemas+ base orchestration + send port bindings) were used to build a simple orchestration that called the “AddAttendees” operation. The WCF “transaction” concept does not exist in a BizTalk orchestration, so building this orchestration flow is consistent with the process to consume any WCF (or basic SOAP) service.
Notice that because an “existing send port type” was used (pointing to the send port types in the wizard-generated orchestration), all service operations are available. After deploying this project, the wizard-generated binding was used to construct a valid WCF-WSHttp send port. Because the service was configured to accept flowed transactions, the BizTalk send port reflects that via the “Bindings” configuration tab (“Enable transactions”).
After the orchestration is bound to the send port, and the process triggered, a successful note was published inside TCPTrace and the orchestration itself. BizTalk created and flowed a transaction that was used by the destination service.
Now, how about making BizTalk behave like that code snippet above where all the operations are called under a single transaction? Alas, it’s not a straightforward thing to do. The BizTalk Server 2006 R2 documentation states:
“The transactional scope is limited by the MessageBox. For example, a BizTalk orchestration cannot participate in a client’s transaction. Similarly, a destination endpoint cannot participate in a transaction that is initiated by a BizTalk orchestration.”
So, the following orchestration does NOT behave as one might think …
Wrapping a series of services inside a transaction (atomic/long running) does not propagate the transaction to the called services. Instead, each service is called and committed under a distinct transaction. If an exception occurs in one of them, the developer would have to catch the error, and use a compensating action to execute a rollback operation.
In the case of the services built for this demonstration, an error occurs even using the configuration in the orchestration above (i.e. separate transaction being used for each actual service call from the MessageBox). Recall that the demonstration service has a mix of TransactionFlowOption.Allowed and TransactionFlowOption.NotAllowed. The single send port created via the wizard-generated binding has the “Enable Transactions” checked meaning a transaction is flowing to all service operations. Executing the above orchestration results in the second operation (“ReserveRoom”) failing with the following error:
“The header ‘CoordinationContext’ from the namespace ‘http://schemas.xmlsoap.org/ws/2004/10/wscoor’ was not understood by the recipient of this message, causing the message to not be processed. This error typically indicates that the sender of this message has enabled a communication protocol that the receiver cannot process. Please ensure that the configuration of the client’s binding is consistent with the service’s binding.”
Ouch. What happens is that the Transaction headers (CoordinationContext) is included in the service call to “ReserveRoom”, but the service rejected it because the operation does not accept flowed transactions. If the “Enable transactions” is turned off in the WCF-WSHttp send port, the process succeeds, but none of the service operations are executed under a transaction (notice empty DistributedIdentifier value).
The problem here stems from that fact that a single physical send port cannot be used to call a mix of both transactional and non-transactional service operations unless “Enable transactions” is unchecked. One way to get around this is to ignore the wizard-generated orchestration and instead manually create logical send ports for each service call. Such an orchestration would look like this:
Each logical send port’s operation matches an operation name in the WCF-WSHttp adapter’s SOAPActionHeader section. Each send port also uses messages of the types defined in the wizard-generated schema. Following deployment, a new send port is created which is identical to the wizard-generated port (down to repeating all the SOAPActionHeader values) EXCEPT that the “Enable transaction” checkbox is unchecked. Now, each operation can be associated with either a “transaction” or “non-transaction” send port. Because each physical send port has the same SOAPActionHeader list, any orchestration port could easily be rebound to the alternating send port for any operation. Notice in the image below how each service has a different transaction ID.
Interestingly enough, the code block used earlier in this article to call all operations under a transaction worked with no problem. How? The proxy class generated for the service and used by the code block strips off the transaction-related headers before the service is called. This can be verified by using a tool like TCPTrace to watch the raw XML payload cross the wire. So the proxy class allows the mixed-transaction operations to be called under a single transaction, however BizTalk Server doesn’t use any auto-generated proxy class, so the single send port can’t be used to call a mixed-transaction operations.
Now back to the problem of calling multiple services in a SINGLE transaction. How to get past this problem in BizTalk? Another service was built which referenced this original service, and wrapped all the individual operation calls in a single operation. Then, BizTalk can call this single operation, flow a transaction, and the “wrapper” service (which uses the auto-generated proxy class from the svcutil.exe tool) gracefully calls each secondary operation. So, a new BizTalk orchestration was built which calls the wrapper service. After deploying the project, importing the new binding for the wrapper send port, and calling the wrapper service, the trace log shows that BizTalk flowed a transaction to the wrapper, and the wrapper flowed the transaction to the individual operations (that accepted it). Not bad!
Transaction Recovery Processing
This article on transactions wouldn’t be complete without investigating the results of transaction failures and recovery processing. Specifically, given the demonstration service built above, what happens when failures happen at particular layers of the application?
What if an operation fails? What happens to the previous committed operations? Let’s start off by aborting the transaction in the 3rd operation (“ReserveCatering”). This operation is called after “AddAttendees” and “ReserveRoom” is called. Remember that “ReserveRoom” has a flow attribute of TransactionFlowOption.NotAllowed meaning that it does not participate in the parent transaction. Inside “ReserveCatering” a rollback was triggered (Transaction.Current.Rollback()).
The trace log (and SQL Server query window) revealed the following result:
From the trace log it’s apparent that all the services were called. However, because there was a rollback of the “ReserveCatering” operation, the parent transaction rolled back all operations participating in the client transaction. In this case, that was only the “AddAttendees” service. Recall that the “ReserveRoom” operation would NOT rollback if the client transaction aborted. The database results above show that the “Room” database table still has a value after the service transaction aborted while the “Attendee” and “Catering” tables have no committed values.
What if the “ReserveRoom” operation performs a rollback? Would that be localized due to the TransactionFlowOption.NotAllowed, or would that bubble up and cause the other operation(s) to rollback as well? As the image below demonstrates, the “AddAttendees” operation succeeded, but after “ReserveRoom” was called, and then had its internal transaction aborted, the error was captured by the “Wrapper” and triggered a rollback of the “AddAttendees” operation as well.
What if there is a failure in the wrapper service? If a transaction “rollback” command was added after all the sub operations have completed, the two operations with TransactionFlowOption.Allowed were rolled back, while the TransactionFlowOption.NotAllowed operation was unaffected.
WCF’s transaction model is quite robust while also being fairly simple to implement. This article explained the basics of WCF transactions, and how to enable (and disallow) them in a WCF service. We also looked at how to consume transactional services from BizTalk Server 2006 R2. We also uncovered the BizTalk restriction that a single send port cannot be used to call both transactional and non-transactional service operations. It was also shown that BizTalk orchestrations cannot participate in WCF transactions, as any WCF transaction only exists between the MessageBox and the service itself. Finally, we saw how exceptions at various points in the service stack cause rollbacks to be cascaded up and down.
Once this entire BizTalk+WCF series is complete, I will make the entire source code available.
Questions, comments or corrections? Go ahead and leave a comment on my blog post about this article.
You can read more about BizTalk, SOA and enterprise architecture on my blog at https://seroter.wordpress.com.