NServiceBus 5 transactional behavior when disabling DTC and TransactionScope


(Cabraham) #1

I’m interested to know what the transactional behavior is in this scenario. We have a need to integrate to an external client system via Http Post. This external Api however can take a very long time to process and the .NET client can timeout, but the api call may still be successful. In such a scenario, we cannot deterministically tell if the call was successful (sometimes it is). In the case a retry was kicked off, we want to send an email communication to the client stating that there may be a possible duplicate entry in their system.

I am interested in knowing what happens when we have an endpoint configured as such:
class ConfigureTransactions : INeedInitialization
{
public void Customize(BusConfiguration configuration)
{
configuration.Transactions()
.DisableDistributedTransactions()
.DoNotWrapHandlersExecutionInATransactionScope();
}
}

and a handler that does the following:

public void Handle(SendToExternalSystem message)
{
	if (IsRetry()) // check headers
	{
		DispatchRetryEmail(message);
	}
	externalSystem.Post(xml); //http post
}

private void DispatchRetryEmail(SendToExternalSystem email)
{
	 using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew))
	{
		var sendEmail = new SendEmail
		{
			Email = new Email () { } // email details }
		};
		bus.Send(sendEmail);
		scope.Complete();
	}
}

I understand that this does not completely make sense. In one part, we’re disabling DTC and transaction scope, yet we’re creating a new transaction scope. Without getting into the business details, a bug was introduced because the ConfigureTransactions class included in a changeset, which prevented the emails from being sent.

I would like to know the mechanics of why the emails were not sent and how disabling transactions at an endpoint level and creation of transactionscope within a handler interact.

Thanks in advance!


(Andreas Öhlund) #2

With your endpoint configured like that it will run in what is called “Transport transaction mode”

https://docs.particular.net/transports/transactions?version=core_5#transactions-transport-transaction

This means that all messaging operations, like your bus.Send(sendEmail) will participate in the existing receive transaction. In this mode all outgoing operations will be atomic with the current receive operation. If the receive fails due to an exception or if it times out no outgoing messages will be dispatched.

In your case it sounds like you might want to dispatch your “retry email” even if the processing of the message fails?

If so take a look at “Immediate dispatch”

https://docs.particular.net/nservicebus/messaging/send-a-message?version=core_5#dispatching-a-message-immediately

In your case you would use a TransactionScopeOption.Suppress instead of a RequiresNew.

Does this make sense?

All this said I think the design can be improved by introducing a SendToExternalSystemResult message. With this in place you can configure this endpoint to be non transactional so that the receive operation won’t time out should the call take a long time.

https://docs.particular.net/transports/transactions?version=core_5#transactions-unreliable-transactions-disabled

You would then do a try catch around the call and then bus.Reply(new SendToExternalSystemResult(false)) or similar. The requesting endpoint can then bus.Send(new SendEmail()) for failed requests.

Food for thought :slight_smile:

Cheers,

Andreas