I've spent part of the weekend playing with some code for a BizTalk 2006 Send adapter, and while looking at the failure handling code, remember something that always struck me a bit odd about the model.
BizTalk supports a several different ways of implementing a send adapter, depending on the complexity of the adapter and the capabilities it provides. An adapter can made to handle a single message at a time (but with likely multiple messages in parallel), or handle batches of messages. An adapter can also handle messages synchronously or asynchronously.
Synchronous adapters are not really very desirable in general terms, unless the adapter itself doesn't do much work (like my Null Send Adapter, which does no work at all!). The reason for this is that a synchronous adapter will do its message processing directly in the thread provided by the BizTalk Messaging engine, thus blocking the thread temporarily and getting in the way of the Messaging Engine itself. Since the messaging engine threads are usually CPU bound, and the primary function of an adapter is to do I/O, this isn't a very desirable scenario.
However, there's something interesting about how the messaging engine accommodates synchronous adapters. When BizTalk wants to send a message through an adapter, it will call the adapter's IBTTransmitter.TransmitMessage() implementation .
TransmitMessage() has a boolean return value. If the adapter returns true, it means that this was a synchronous send operation, and the message has been sent successfully. If an exception is raised, then the send operation will be failed. In either case, the messaging engine will do the right thing and process the message accordingly. If the adapter returns false, however, it means that the adapter will handle the message asynchronously and it's responsible for telling the messaging engine what [eventually] happened with the message.
In practice, the last part is a lot harder than it sounds. Basically, a synchronous adapter is pretty worry free: It simply needs to return true or throw an exception, and the messaging engine does all the hard work. However, an asynchronous adapter has to do all the hard work itself, which includes:
- Deleting the message from the message box when it has been successfully sent.
- Submitting messages for retries according to their retry counts and retry intervals as configured in the send port configuration.
- Moving messages to the secondary transport configured in the send location if the retries have been exhausted.
- Suspending messages that could not otherwise be sent.
- All of this needs to be retried until the messaging engine acknowledges the operation successfully!
As you can see it's a lot of responsibilities for the adapter developer; particularly the last part. See, if you tell the engine to delete a message, it might tell you that it couldn't do it (maybe there was contention on the message box); it's your responsibility to keep trying until it succeeds. Same story for all of the operations mentioned above. Getting all this right can be a bit tricky, as you can imagine (though the adapter framework in the SDK can help a bit here).
Is this unnecessary? Not at all; it is quite required particularly for batched and transaction aware adapters, and it actually is quite flexible. It also allows adapters with special needs to make decisions about what the right action might be. For example, maybe your adapter must suspend a message without trying the secondary transport (ordered delivery requirements, maybe).
The downside of this is that, if you were not interested in creating a batched adapter (or couldn't!), but did want to make it relatively efficient by not doing your work on the messaging engine threads, it's a whole lot of complexity you have to deal with. You can't simply tell the messaging engine "I'm done with this message" and have the right things happen anymore; instead you need to actually handle batches (containing a single message, but batches nonetheless) and implement the required semantics all on your own.
Simple synchronous adapters are still useful though. It's an easy way to get started with your send adapter, verify that your core adapter logic works, and then move on to support asynchronous processing and batches, if you can support those.
 If the adapter supports batching, it will implement IBTBatchTransmitter instead of IBTTransmitter and the workflow will be different, but let's forget about that for now.