What is a distributed transaction?
In the world of microservices, each service has its own local database and some operations we do in microservices may involve multiple database operations for multiple services.
So a distributed transaction may contain local transactions for multiple databases.
What we often encounter is the need to guarantee data consistency, updated in parallel by different actors.
There are some solutions handling the distributed transaction in microservice that we can follow, such as Seata, servicecomb-pack, etc.
But both Seata and servicecomb-pack are not friendly and hard to use in C# due to there being no SDK for C# version.
After some exploration, I found a distributed transaction framework named DTM, written in Golang, that was easy to use and supported multilingual technology stack.
And I will use a popular distributed transaction pattern named Saga to introduce how to do a distributed transaction using C# with this distributed transaction framework.
What is Saga?
Saga first appeared in a paper published by Hector Garcaa-Molrna & Kenneth Salem in 1987.
The core idea is to split a long transaction into multiple short transactions, coordinated by the Saga transaction coordinator, with the global transaction completing normally if each short transaction completes successfully, and invoking the compensating operations one at a time according to the reverse order if a step fails.
The Saga pattern is a widely used pattern for distributed transactions. It is asynchronous and reactive.
What is DTM?
DTM is a distributed transaction framework which provides cross-service eventual data consistency. It provides saga, tcc, xa, 2-phase message strategies for a variety of application scenarios. It also supports multiple languages and multiple store engine to form up a transaction.
There are some features of DTM:
- Extremely easy to adopt
- Easy to use
- Multi-language support
- Easy to deploy, easy to extend
- Multiple distributed transaction protocols
For more details about DTM, you can visit its official website or Github page.
https://github.com/dtm-labs/dtm
After a series of introductions, I will use a classic transfer of money across banks example to demonstrate how to use it.
Setup DTM server
Here use docker to setup DTM quickly.
docker run -d -p 36789:36789 -p 36790:36790 --name=dtm-svc yedf/dtm:1.13

To quickly experience DTM, boltdb is used to store data, and if you are deploying in a production environment, remember to switch to mysql or other storage engines
NOTE: It support using HTTP and GRPC to communicate with DTM server, 36789 is HTTP port and 36790 is GRPC port.
Basic Sample
Before creating the C# microservices, let's take a look at a typical timing diagram of a successfully completed SAGA transaction.

As we can see, in this timing diagram, the transaction initiator, after defining the orchestration information for the entire global transaction (including the forward and reverse compensation operations for each step), commits it to the DTM server, which then executes the previous SAGA logic step by step.
Now, we will create two microservices using .NET 6 minimal API.
The first is the transfer out microservice:
var builder = WebApplication.CreateBuilder(args);
// Add Dmtcli
builder.Services.AddDtmcli(x =>
{
x.DtmUrl = "http://localhost:36789";
});
var app = builder.Build();
app.MapPost("/api/TransOut", (HttpContext httpContext, TransRequest req) =>
{
Console.WriteLine($"TransOut, QueryString={httpContext.Request.QueryString}");
Console.WriteLine($"User: {req.UserId}, transfer out {req.Amount} --- forward");
return Results.Ok(TransResponse.BuildSucceedResponse());
});
app.MapPost("/api/TransOutCompensate", (HttpContext httpContext, TransRequest req) =>
{
Console.WriteLine($"TransOutCompensate, QueryString={httpContext.Request.QueryString}");
Console.WriteLine($"User: {req.UserId}, transfer out {req.Amount} --- reverse compensation");
return Results.Ok(TransResponse.BuildSucceedResponse());
});
app.Run("http://*:10000");
And the second is the transfer in microservice
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDtmcli(x =>
{
x.DtmUrl = "http://localhost:36789";
});
var app = builder.Build();
app.MapPost("/api/TransIn", (HttpContext httpContext, TransRequest req) =>
{
//
Console.WriteLine($"TransIn, QueryString={httpContext.Request.QueryString}");
Console.WriteLine($"User: {req.UserId}, transfer in {req.Amount} --- forward");
return Results.Ok(TransResponse.BuildSucceedResponse());
});
app.MapPost("/api/TransInCompensate", (HttpContext httpContext, TransRequest req) =>
{
Console.WriteLine($"TransInCompensate, QueryString={httpContext.Request.QueryString}");
Console.WriteLine($"User: {req.UserId}, transfer out {req.Amount} --- reverse compensation");
return Results.Ok(TransResponse.BuildSucceedResponse());
});
app.Run("http://*:10001");
NOTE: For the sake of simplicity, specific database operations are not introduced here, but are replaced by log output.
Adding a console application to impersonate the transaction initiator. And we should add one of the following package from nuget.
dotnet add package Dtmcli --version 1.0.0
or
dotnet add package Dtmgrpc --version 1.0.0
NOTE: Dtmcli uses the HTTP protocol to communicate with the DTM server, and Dtmgrpc uses the GRPC protocol.
The following example will use Dtmcli to demonstrate how to commit a SAGA transaction.
var services = new ServiceCollection();
services.AddDtmcli(x =>
{
x.DtmUrl = "http://localhost:36789";
});
var provider = services.BuildServiceProvider();
var dtmClient = provider.GetRequiredService<IDtmClient>();
var outApi = "http://192.168.0.101:10000/api";
var inApi = "http://192.168.0.101:10001/api";
var userOutReq = new Common.TransRequest() { UserId = "1", Amount = -30 };
var userInReq = new Common.TransRequest() { UserId = "2", Amount = 30 };
var cts = new CancellationTokenSource();
var gid = await dtmClient.GenGid(cts.Token);
var saga = new Saga(dtmClient, gid)
.Add(outApi + "/TransOut", outApi + "/TransOutCompensate", userOutReq)
.Add(inApi + "/TransIn", inApi + "/TransInCompensate", userInReq)
.EnableWaitResult()
;
await saga.Submit(cts.Token);
Console.WriteLine($"Submit SAGA transaction, gid is {gid}.");






Kha BuiPosted Jun 21, 2023, 4:13 AM
Hi my friend, I 'm implemeting DTM for my project, I have a problem about sub barrier transaction C#, how implement for example this above example, can you implement more?
hnieefPosted Apr 13, 2022, 10:00 PM
Is DTM same as dapr.io