1

I am writing an C# WEB API which takes multipart-form data to create a record in DB and upload files to a location. As the part of creation I am also creating a Jira Issue and updating the created record with JIRA details.

  [Route("api/request/create")]
  [Consumes("multipart/form-data")]
  public async Task<HttpResponseMessage> CreateRequest()
  {
     try
     {
        var multipartMemoryStream = await Request.Content.ReadAsMultipartAsync();
        var newRequestData = await multipartMemoryStream.Contents[0].ReadAsStringAsync();

       CreateRequestCommand createRequestCommand = JsonConvert.DeserializeObject<CreateRequestCommand>(newRequestData);

       var requestId = CreateRequestInDB(createRequestCommand)
       var jira = await CreateJira(createContentRequestCommand);
       await WriteSourceFiles(multipartMemoryStream.Contents);
       await UpdateRequestWithJiraDetails(requestId, jira);
       return new SuccessResponse(requestId)
    }
   catch (Exception ex)
   {
      throw ex;
   }
}

This is working fine in most cases. How can I handle the exception in the best way so that if any of the method fails system should not retain the record in DB and delete the JIRA issue too. What is the best stratergy to maintain system in consistent state if any of the step fails.

Harjinder Singh
  • 23
  • 1
  • 10
  • Welcome to StackOverflow. Unfortunately this a really complex topic. You have entered into the realm of distributed transactions. There is no one-size-fits-all solution for this, but I would suggest to check the [Sagas pattern](https://microservices.io/patterns/data/saga.html). – Peter Csala Jul 16 '20 at 09:15
  • @PeterCsala Why don't you write an **answer** instead of commenting on all existing posts? – nvoigt Jul 17 '20 at 08:51

2 Answers2

0

If you have multiple operations and you want them to either all succeed or all fail, then you need a concept known as a Transaction.

In .NET this is implemented in the System.Transactions namespace, with the class TransactionScope probably what you will end up using.

Sorry that this is vague and without code example, but since you only posted your own method names, you will have to figure out by yourself, what the code that is actually executed in those methods does and how well it plays with the Microsoft classes. If your database is Entity Framework or a plain SqlConnection, it might work out of the box. But that's up to you to figure out.

nvoigt
  • 75,013
  • 26
  • 93
  • 142
  • The `Transaction` and `TransactionScope` are designed for database operations not for general purpose. For instance if you want to modify files in a transnational manner then you should consider to use [Transaction NTFS](https://learn.microsoft.com/en-gb/windows/win32/fileio/about-transactional-ntfs). Since the OP's example code contains operations like database write, file write and even jira ticket creation that's why your proposed solution is not a good fit. Two Phase Commit could be a solution with DTC, but it has poor guarantees. Check Saga instead. – Peter Csala Jul 17 '20 at 07:47
  • @PeterCsala You are wrong here. TransactionScope **is** general purpose. You can write your own providers that work with it. It's just that everybody on the internet uses MSSql to demo this, since the MSSql Provider already comes with the .NET Framework. If you have a better alternative, please provide an answer using it. Comments are not meant to last forever. – nvoigt Jul 17 '20 at 08:58
0

If you look at WikiPedia for the definition of Distributed transaction then it says the following:

A distributed transaction is a database transaction in which two or more network hosts are involved. Usually, hosts provide transactional resources, while the transaction manager is responsible for creating and managing a global transaction that encompasses all operations against such resources. Distributed transactions, as any other transactions, must have all four ACID (atomicity, consistency, isolation, durability) properties, where atomicity guarantees all-or-nothing outcomes for the unit of work (operations bundle).

Even though the definition focuses on the database relevance the sample applies even if you want to write a database and a file as a unit-of-work. If either of them fail then both of them should rolled back.

Back in those days when .NET could run only just on Windows operating system the .NET could take advantage of the Transaction Manager. All you had to do is to implement the IEnlistmentNotification interface for Two-Phase-Commit. That interface exposes 4 methods:

  • Prepare for voting
  • Commit for happy path
  • Rollback for unhappy path
  • InDoubt during second phase

At the end you had to register that implentation to the TransactionManager via the following call: Transaction.Current.EnlistVolatile

.NET Core does not support distributed transaction. Maybe .NET 5...

2 Phase Commit (and 3PC) protocols have the blocking problem (1, 2, 3) there might be situations when it stuck and can't move on. Martin Kleppmann's Designing Data-Intensive Applications book details the problem in a really easily consumable way.


In the word of Microservices the recommended approach is the Sagas pattern. You have local transactions where you have all your ACID guarantees. What need is some sort of coordination among the participants. There can be orchestrator who conducts the whole flow and the participants don't know about each other. Or the participants can form a choreography where each participant knows how to communicate with previous and next participant.

In case of failure you can either rollback or apply a compensation action to undo the effect of the previous action. There are several really good resources where all of these are detailed in depth. I recommend Chris Richardson's Microservices Patterns book. For online get started read this article.

If you want to go through a real world example then I suggest to read Jimmy Bogard's excellent blog series.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
  • Related SO topic [#1](https://stackoverflow.com/questions/59135418/distributed-transactions-net-framework-vs-net-core), [#2](https://stackoverflow.com/questions/56328832/transactionscope-throwing-exception-this-platform-does-not-support-distributed-t) – Peter Csala Jul 17 '20 at 10:24