<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: yedf2</title>
    <description>The latest articles on DEV Community by yedf2 (@yedf2).</description>
    <link>https://dev.to/yedf2</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F830373%2Fbfce2519-4d34-41f3-8527-8bf0edb44a3e.jpeg</url>
      <title>DEV Community: yedf2</title>
      <link>https://dev.to/yedf2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yedf2"/>
    <language>en</language>
    <item>
      <title>Use workflow to handle distributed transactions</title>
      <dc:creator>yedf2</dc:creator>
      <pubDate>Thu, 28 Jul 2022 12:13:00 +0000</pubDate>
      <link>https://dev.to/yedf2/use-workflow-to-handle-distributed-transactions-12ma</link>
      <guid>https://dev.to/yedf2/use-workflow-to-handle-distributed-transactions-12ma</guid>
      <description>&lt;p&gt;In the world of microservices a transaction is now distributed to multiple services that are called in a sequence to complete the entire transaction.&lt;/p&gt;

&lt;p&gt;With the advent of microservice architecture we are losing the ACID nature of databases. Transactions may now span multiple microservices and therefore databases. &lt;/p&gt;

&lt;p&gt;To address the problems of distributed transactions, the following list of approaches have been described:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Two-Phase Commit / XA&lt;/li&gt;
&lt;li&gt;Eventual Consistency and Compensation / SAGA&lt;/li&gt;
&lt;li&gt;Try, Confirm, Cancel / TCC.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In classic solutions, the developers should choose one of the three patterns to handle distributed transactions. &lt;/p&gt;

&lt;p&gt;But in this article, we introduce a workflow pattern in &lt;a href="https://github.com/dtm-labs/dtm"&gt;github.com/dtm-labs/dtm&lt;/a&gt;. Under this pattern, a mixture of XA, SAGA and TCC can be applied to different branches in a single distributed transactions, allowing users to customize most of the contents of a distributed transaction, providing great flexibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  workflow example
&lt;/h2&gt;

&lt;p&gt;In Workflow mode of DTM, both HTTP and gRPC protocols can be used. The following is an example of the gRPC protocol, which is divided into the following steps.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initialize the SDK&lt;/li&gt;
&lt;li&gt;Register workflow&lt;/li&gt;
&lt;li&gt;Execute workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Initialize the SDK
&lt;/h3&gt;

&lt;p&gt;First you need to Initialize the SDK's workflow before you can use it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/dtm-labs/dtmgrpc/workflow"&lt;/span&gt;

&lt;span class="c"&gt;// Initialize the workflow SDK with three parameters.&lt;/span&gt;
&lt;span class="c"&gt;// the first parameter, the dtm server address&lt;/span&gt;
&lt;span class="c"&gt;// the second parameter, the business server address&lt;/span&gt;
&lt;span class="c"&gt;// The third parameter, grpcServer&lt;/span&gt;
&lt;span class="c"&gt;// workflow needs to receive the dtm server callback from the "business server address" + "grpcServer"&lt;/span&gt;
&lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitGrpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dtmGrpcServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BusiGrpc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gsvr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Register workflow
&lt;/h3&gt;

&lt;p&gt;Then you need to register workflow's handler function. The following is a saga workflow to do cross-bank transfer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;wfName&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"wf_saga"&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wfName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wf&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Workflow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;MustUnmarshalReqGrpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;wf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBranch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnRollback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bb&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BranchBarrier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BusiCli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransOutRevert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BusiCli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;wf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBranch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnRollback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bb&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BranchBarrier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BusiCli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransInRevert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BusiCli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;This registration operation needs to be executed after the business service is started, because when the process crashes, dtm will call back to the business server to continue the unfinished task&lt;/li&gt;
&lt;li&gt;The above code &lt;code&gt;NewBranch&lt;/code&gt; will create a transaction branch, one that will include a forward action and a callback on global transaction commit/rollback&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OnRollback/OnCommit&lt;/code&gt; will register a callback on global transaction rollback/commit for the current transaction branch, in the above code, only &lt;code&gt;OnRollback&lt;/code&gt; is specified, so it is in Saga mode&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;busi.BusiCli&lt;/code&gt; in the above code needs to add a workflow interceptor which will automatically record the results of the rpc request to dtm as follows
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;conn1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;grpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BusiGrpc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;grpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithUnaryInterceptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interceptor&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nossl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BusiCli&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBusiClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can of course add &lt;code&gt;workflow.Interceptor&lt;/code&gt; to all gRPC clients, this middleware will only handle requests under &lt;code&gt;wf.Context&lt;/code&gt; and &lt;code&gt;wf.NewBranchContext()&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the workflow function returns nil/ErrFailure, the global transaction enters the Commit/Rollback phase, calling the operations registered in OnCommit/OnRollback inside the function in reverse order&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Execute workflow
&lt;/h3&gt;

&lt;p&gt;Finally the workflow is executed&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReqGrpc&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wfName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shortuuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;dtmgimp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustProtoMarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;When the result of Execute is &lt;code&gt;nil/ErrFailure&lt;/code&gt;, the global transaction has succeeded/been rolled back.&lt;/li&gt;
&lt;li&gt;When the result of Execute is other values, the dtm server will subsequently call back this workflow task to retry&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Principle of workflow
&lt;/h2&gt;

&lt;p&gt;How does workflow ensure data consistency in distributed transactions? When a business process has a crash or other problem, the dtm server will find that this workflow global transaction has timed out and not completed, then the dtm server will use an exponential retreat algorithm and retry the workflow transaction. When the workflow retry request reaches the business service, the SDK will query the progress of the global transaction from the dtm server, and for the completed branch, it will take the previously saved result and return the branch result directly through an interceptor such as gRPC/HTTP. Eventually the workflow will complete successfully.&lt;/p&gt;

&lt;p&gt;Workflow functions need to be idempotent, i.e. the first call, or subsequent retries, should get the same result&lt;/p&gt;

&lt;h2&gt;
  
  
  Saga in Workflow
&lt;/h2&gt;

&lt;p&gt;The core idea of the Saga pattern, derived from this paper &lt;a href="https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf"&gt;SAGAS&lt;/a&gt;, is that long transactions are split into short transactions, coordinated by the Saga transaction coordinator, and if each short transaction operation successfully completes, then the global transaction completes normally, and if a step fails, the compensating operations are invoked one at a time in reverse order.&lt;/p&gt;

&lt;p&gt;In Workflow mode, you can call the function for the operation directly in the function and then write the compensation operation to &lt;code&gt;OnRollback&lt;/code&gt; of the branch, and then the compensation operation will be called automatically, achieving the effect of Saga mode&lt;/p&gt;

&lt;h2&gt;
  
  
  Tcc under Workflow
&lt;/h2&gt;

&lt;p&gt;The Tcc pattern is derived from this paper &lt;a href="https://www.ics.uci.edu/~cs223/papers/cidr07p15.%20pdf"&gt;Life beyond Distributed Transactions: an Apostate's Opinion&lt;/a&gt;, he divides a large transaction into multiple smaller transactions, each of which has three operations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try phase: attempts to execute, completes all business checks, set aside enough business resources&lt;/li&gt;
&lt;li&gt;Confirm phase: if the Try operation succeeds on all branches, it goes to the Confirm phase, which actually executes the transaction without any business checks, using only the business resources set aside in the Try phase&lt;/li&gt;
&lt;li&gt;Cancel phase: If one of the Try operations from all branches fails, we go to the Cancel phase, which releases the business resources reserved in the Try phase.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For our scenario of an inter-bank transfer from A to B, if SAGA is used and the balance is adjusted in the forward operation, and is adjusted reversely in the compensating operation, then the following scenario would occur.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A deducts the money successfully&lt;/li&gt;
&lt;li&gt;A sees the balance decrease and tells B&lt;/li&gt;
&lt;li&gt;The transfer of the amount to B fails and the whole transaction is rolled back&lt;/li&gt;
&lt;li&gt;B never receives the funds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This causes great distress to both ABs. This situation cannot be avoided in SAGA, but it can be resolved by TCC with the following design technique.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introduce a trading_balance field in addition to the balance field in the account&lt;/li&gt;
&lt;li&gt;Try phase to check if the account is frozen, check if the account balance-trading_balance is sufficient, and then adjust the trading_balance (i.e. the funds frozen for business purposes)&lt;/li&gt;
&lt;li&gt;Confirm phase, adjust balance, adjust trading_balance (i.e. unfrozen funds for the business)&lt;/li&gt;
&lt;li&gt;Cancel phase, adjust trading_balance (i.e. unfrozen funds on the business)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case, once end user A sees his balance deducted, then B must be able to receive the funds&lt;/p&gt;

&lt;p&gt;In Workflow mode, you can call the &lt;code&gt;Try&lt;/code&gt; operation directly in the function, then register the &lt;code&gt;Confirm&lt;/code&gt; operation to &lt;code&gt;OnCommit&lt;/code&gt; in the branch and register the &lt;code&gt;Cancel&lt;/code&gt; operation to &lt;code&gt;OnRollback&lt;/code&gt; in the branch, achieving the effect of the &lt;code&gt;Tcc&lt;/code&gt; mode&lt;/p&gt;

&lt;h2&gt;
  
  
  XA under Workflow
&lt;/h2&gt;

&lt;p&gt;XA is a specification for distributed transactions proposed by the X/Open organization. The XA specification essentially defines the interface between a (global) Transaction Manager (TM) and a (local) Resource Manager (RM). Local databases such as mysql play the RM role in the XA&lt;/p&gt;

&lt;p&gt;XA is divided into two phases.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Phase 1 (prepare): i.e. all participants RM prepare to execute the transaction and lock the required resources. When the participants are ready, they report to TM that they are ready.&lt;/li&gt;
&lt;li&gt;Phase 2 (commit/rollback): When the transaction manager (TM) confirms that all participants (RMs) are ready, it sends a commit command to all participants.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Currently all major databases support XA transactions, including mysql, oracle, sqlserver, postgres&lt;/p&gt;

&lt;p&gt;In Workflow mode, you can call &lt;code&gt;NewBranch().DoXa&lt;/code&gt; in the workflow function to open your XA transaction branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mixing multiple modes
&lt;/h2&gt;

&lt;p&gt;In Workflow mode, Saga, Tcc and XA as described above are all patterns of branching transactions, so you can use one pattern for some branches and another pattern for others. The flexibility offered by this mixture of patterns allows for sub-patterns to be chosen according to the characteristics of the branch transaction, so the following is recommended.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;XA: If the business has no row lock contention, and the global transaction will not last long, then XA can be used. This pattern requires less additional development and &lt;code&gt;Commit/Rollback&lt;/code&gt; is done automatically by the database. For example, this pattern is suitable for order creation business, where different orders lock different order rows and have no effect on concurrency between each other; it is not suitable for deducting inventory, because orders involving the same item will all compete for the row lock of this item, which will lead to low concurrency.&lt;/li&gt;
&lt;li&gt;Saga: common business that is not suitable for XA can use this model, this model has less extra development than Tcc, only need to develop forward operation and compensation operation&lt;/li&gt;
&lt;li&gt;Tcc: suitable for high consistency requirements, such as the transfer described earlier, this pattern has the most additional development and requires the development of operations including &lt;code&gt;Try/Confirm/Cancel&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  gRPC support
&lt;/h2&gt;

&lt;p&gt;Support for gRPC, consists of two aspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;communicating with the dtm server by gRPC protocol, e.g. in the above example, after calling &lt;code&gt;workflow.InitGrpc&lt;/code&gt; above to initialize, the dtm SDK will take the gRPC interface to interact with the dtm server&lt;/li&gt;
&lt;li&gt;In the above example, &lt;code&gt;reply, err = busi.BusiCli.TransIn(wf.Context, req)&lt;/code&gt;, this gRPC call will automatically save the result of the call to the dtm server through the gRPC interceptor. When workflow is executed again, the result saved by the dtm server will be returned directly to the caller.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  HTTP support
&lt;/h2&gt;

&lt;p&gt;Support for HTTP, consists of two aspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Communicating with the dtm server by means of the HTTP protocol. After initialization of &lt;code&gt;workflow.InitHTTP&lt;/code&gt;, the dtm SDK will take the HTTP interface to interact with the dtm server.&lt;/li&gt;
&lt;li&gt;Accessing the transaction branch by HTTP protocol, e.g. &lt;code&gt;resp, err := wf.NewBranch().NewRequest().SetBody(req).Post(Busi + "/TransOut")&lt;/code&gt;, in this HTTP call, the result of the call will be automatically saved to the dtm server and, when workflow is executed again, the result saved by the dtm server is automatically returned directly to the caller.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Local Operation Support
&lt;/h2&gt;

&lt;p&gt;In Workflow mode, your transaction branch, not only can use remote branches such as HTTP/gRPC, but can also be a local operation. The following code demonstrates a local transactional operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;wf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bb&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BranchBarrier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CallWithDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbGet&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SagaAdjustBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransOutUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can perform local transaction operations as well as other operations, as flexibly as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  gRPC/HTTP/Local Mixed Usage
&lt;/h2&gt;

&lt;p&gt;Within a distributed transaction, you can also use a mix of gRPC/HTTP/local. These approaches to your transaction branch can give you a great deal of flexibility. It provides a very good solution to introducing distributed transactions for multiple technology stacks, for various scenarios such as the coexistence of multiple protocols, and for existing legacy systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotency Requirements
&lt;/h2&gt;

&lt;p&gt;In the Workflow pattern, when a crash occurs, a retry is performed, so the individual operations are required to support idempotency, i.e. the result of the first call is the same as the next tries, returning the same result. In business, the &lt;code&gt;unique key&lt;/code&gt; of the database is usually used to achieve idempotency, specifically &lt;code&gt;insert ignore "unique-key"&lt;/code&gt;, if the insert fails, it means that this operation has been completed, this time directly ignored to return; if the insert succeeds, it means that this is the first operation, continue with the subsequent business operations.&lt;/p&gt;

&lt;p&gt;If your business itself is idempotent, then you can operate your business directly; if your business does not provides idempotent functionality, then dtm provides a &lt;code&gt;BranchBarrier&lt;/code&gt; helper class, based on the above unique-key principle, which can easily help developers implement idempotent operations for &lt;code&gt;Mysql/Mongo/Redis&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Please note that the following two are typical non-idempotent operations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timeout rollback: If you have an operation in your business that may take a long time, and you want your global transaction to roll back after waiting for the timeout to return a failure. Then this is not an idempotent operation because in the extreme case of two processes calling the operation at the same time, one returns a timeout failure and the other a success, resulting in different results&lt;/li&gt;
&lt;li&gt;Rollback after reaching the retry limit: the analysis process is the same as above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Workflow mode does not support the above timeout rollback and retry limit rollback at the moment, if you have a relevant scenario, please send us the specific scenario, we will actively consider whether to add this kind of support&lt;/p&gt;

&lt;h2&gt;
  
  
  Branch Operation Results
&lt;/h2&gt;

&lt;p&gt;Branching operations will return the following results.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Success: the branch operation returns &lt;code&gt;HTTP-200/gRPC-nil&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Business failure: the branch operation returns &lt;code&gt;HTTP-409/gRPC-Aborted&lt;/code&gt;, no more retries, and the global transaction needs to be rolled back&lt;/li&gt;
&lt;li&gt;In progress: branch operation returns &lt;code&gt;HTTP-425/gRPC-FailPrecondition&lt;/code&gt;, this result indicates that the transaction is in progress normally and requires the dtm to retry not with the exponential retreat algorithm but with fixed interval retries&lt;/li&gt;
&lt;li&gt;Unknown error: the branch operation returns other results, indicating an unknown error, and dtm will retry this workflow, using the exponential retreat algorithm&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your existing service has a different result to the above, then you can customise this part of the result with &lt;code&gt;workflow.Options.HTTPResp2DtmError/GRPCError2DtmError&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Saga's Compensation operation and Tcc's Confirm/Cancel operation are not allowed to return business failures according to the Saga and Tcc protocols, because when in the second stage of the workflow, Commit/Rollback, is neither successful nor allowed to retry, then the global transaction cannot be completed, so please take care to avoid this when designing&lt;/p&gt;

&lt;h2&gt;
  
  
  Transaction Completion Notification
&lt;/h2&gt;

&lt;p&gt;Some business scenarios where you want to be notified of the completion of a transaction can be achieved by setting an &lt;code&gt;OnFinish&lt;/code&gt; callback on the first transaction branch. By the time the callback is called, all business operations have been performed and the global transaction is therefore substantially complete. The callback function can determine whether the global transaction has finally been committed or rolled back based on the &lt;code&gt;isCommit&lt;/code&gt; passed in.&lt;/p&gt;

&lt;p&gt;One thing to note is that when the &lt;code&gt;OnFinish&lt;/code&gt; callback is called, the state of the transaction has not yet been modified to final state on the dtm server, so if you use a mixture of transaction completion notifications and querying global transaction results, the results of the two may not be consistent, and it is recommended that users use only one of these methods rather than a mixture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We introduce a workflow pattern to support the mixed usage of Saga, XA and Tcc. This pattern also support HTTP, gRPC and local transactions.&lt;/p&gt;

&lt;p&gt;We also illustrate the usage of workflow in Golang with runnable examples. &lt;/p&gt;

&lt;p&gt;You are welcome to visit &lt;a href="https://github.com/dtm-labs/dtm"&gt;https://github.com/dtm-labs/dtm&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to elegantly implement a multi-database outbox pattern</title>
      <dc:creator>yedf2</dc:creator>
      <pubDate>Tue, 12 Jul 2022 08:00:52 +0000</pubDate>
      <link>https://dev.to/yedf2/how-to-elegantly-implement-a-multi-database-outbox-pattern-50og</link>
      <guid>https://dev.to/yedf2/how-to-elegantly-implement-a-multi-database-outbox-pattern-50og</guid>
      <description>&lt;h2&gt;
  
  
  Introduction to the Outbox pattern
&lt;/h2&gt;

&lt;p&gt;A microservice may need to perform two steps, "save data" and "send events". For example, after publishing an article, the author's posting statistics need to be updated. The business requirement is that both operations fail or succeed at the same time, rather than one succeeding and one failing. If the article is eventually published and the update of posting statistics fails, the data will be inconsistent.&lt;/p&gt;

&lt;p&gt;The outbox pattern is the most common pattern used to solve this problem and works as follows.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the local business runs as a transaction, writing events to the message table before committing the transaction; when the transaction is committed, it commits both the business, and the events&lt;/li&gt;
&lt;li&gt;the events are sent to the message queue by polling the message table or listening to the binlog

&lt;ul&gt;
&lt;li&gt;Polling: retrieve events from the message table every 1s or 0.2s, send them to the message queue, and then delete them&lt;/li&gt;
&lt;li&gt;Listening to the binlog: using a database tool such as Debezium, listen to the database binlog, fetch the events and send them to the message queue&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Write consumers and process events&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As in 1, the business and the event are updated in the same transaction, ensuring that both will be committed at the same time.&lt;br&gt;
In steps 2,3, both are operations that will not fail and will be retried and eventually succeed if a downtime event occurs in between, etc.&lt;/p&gt;

&lt;p&gt;For the aforementioned post-commit statistics scenario, the above solution ensures that the statistics are finally updated and the data will reach eventual consistency&lt;/p&gt;

&lt;h2&gt;
  
  
  Problems with multiple databases
&lt;/h2&gt;

&lt;p&gt;With today's popular microservices architecture, a single database is usually used for a microservice. When multiple services need to use the outbox model, then the traditional outbox architecture is more difficult to maintain.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Polling for events: multiple database polling tasks need to be written in the polling process&lt;/li&gt;
&lt;li&gt;Listening to binlog for events: you need to listen to multiple database binlogs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both of the above methods of fetching events are not very maintainable when dealing with a large number of databases. The architecture is not very resilient and if there are many databases and few events generated at any one time, the load on the architecture will be high and resources will be wasted. The ideal architecture load is one that is only related to the number of events sent, and not to other factors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;The open source distributed transaction framework &lt;a href="https://github.com/dtm-labs/dtm" rel="noopener noreferrer"&gt;https://github.com/dtm-labs/dtm&lt;/a&gt; has a two-stage message pattern inside that handles this problem very well. The following is an example of the use of an interbank transfer operation.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 Go
msg := dtmcli.NewMsg(DtmServer, gid).
    Add(busi.Busi+"/TransIn", &amp;amp;TransReq{Amount: 30})
err := msg.DoAndSubmitDB(busi.Busi+"/QueryPreparedB", db, func(tx *sql.Tx) error {
    return busi.SagaAdjustBalance(tx, busi.TransOutUID, -req.Amount, "SUCCESS")
})


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In this part of the code&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First generate a msg global transaction for the DTM, passing the server address of the DTM and the global transaction id&lt;/li&gt;
&lt;li&gt;Add a branch business logic to the msg, here the business logic is the transfer operation TransIn&lt;/li&gt;
&lt;li&gt;Then call msg's DoAndSubmitDB, a function that ensures that the business is executed successfully and the msg global transaction is committed, either succeeding or failing at the same time

&lt;ol&gt;
&lt;li&gt;the first parameter for the check-back URL, the detailed meaning will be described later&lt;/li&gt;
&lt;li&gt;the second parameter is sql.DB, which is the database object accessed by the business&lt;/li&gt;
&lt;li&gt;the third parameter is the business function, the business in our example is to deduct $30 from the balance of A&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Success Flow
&lt;/h2&gt;

&lt;p&gt;How does DoAndSubmitDB ensure the atomicity of successful business execution and msg submission? Consider the following timing diagram.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqeki1npxokwxsnus7893.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqeki1npxokwxsnus7893.png" alt="success-timeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In general, the 5 steps in the timing diagram will complete normally, the whole business proceeds as expected and the global transaction completes. There is a new element that needs to be explained here, which is that the commit of a msg is initiated in two phases, the first phase calls Prepare and the second phase calls Commit. when DTM receives the Prepare call, it does not call the branch transaction, but waits for the subsequent Submit. only when it receives the Submit, it starts the branch call and finally completes the global transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exceptions
&lt;/h2&gt;

&lt;p&gt;In a distributed system, all types of downtime and network exceptions need to be considered, so let's look at the following possible problems.&lt;/p&gt;

&lt;p&gt;First of all the most important goal we want to achieve is that the business executes successfully and the msg transaction is an atomic operation, so what if in the previous timing diagram, after the &lt;code&gt;Prepare&lt;/code&gt; message is sent successfully and before the &lt;code&gt;Submit&lt;/code&gt; message is sent successfully, what happens if there is an abnormal downtime? At this point dtm will detect that the transaction has timed out and will check back. For developers, this check-back is as simple as pasting in the following code.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 Go
app.GET(BusiAPI+"/QueryPreparedB", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
    return MustBarrierFromGin(c).QueryPrepared(dbGet())
}))


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you are using something other than the go framework &lt;code&gt;gin&lt;/code&gt;, then you will need to make some minor modifications to suit your framework, but the code is generic and suitable for each of your businesses.&lt;/p&gt;

&lt;p&gt;The main principle of check-back is mainly through the message table, but dtm's check-back has been carefully proved to be able to handle the following situations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The local transaction has not started at the time of the check-back&lt;/li&gt;
&lt;li&gt;The local transaction is still in progress at the time of the check-back&lt;/li&gt;
&lt;li&gt;The local transaction has been rolled back at the time of the check-back&lt;/li&gt;
&lt;li&gt;The local transaction has been committed t the time of the check-back&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The detailed check-back principle is somewhat complex and has been patented, so it will not be described in detail here, for more information you can refer to &lt;a href="https://en.dtm.pub/practice/msg.html" rel="noopener noreferrer"&gt;https://en.dtm.pub/practice/msg.html&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-database support
&lt;/h2&gt;

&lt;p&gt;With this solution, if you need to handle multiple databases, all you need to do is create the event tables for each database, and pass in different database connections where you check back.&lt;/p&gt;

&lt;p&gt;Compared to the original polling table and listening binlog solution, the cost of operation and maintenance is greatly reduced. The load of the architecture is only related to the number of events and not to other factors such as the number of databases, making it more resilient.&lt;/p&gt;

&lt;h2&gt;
  
  
  More storage engine support
&lt;/h2&gt;

&lt;p&gt;dtm's two-stage messages provide not only database support for &lt;code&gt;DoAndSubmitDB&lt;/code&gt;, but also NoSQL support&lt;/p&gt;

&lt;h3&gt;
  
  
  Mongo support
&lt;/h3&gt;

&lt;p&gt;The following code ensures that both business and messages are committed simultaneously under Mongo&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 Go
err := msg.DoAndSubmit(busi.Busi+"/RedisQueryPrepared", func(bb *dtmcli.BranchBarrier) error {
    return bb.MongoCall(MongoGet(), func(sc mongo.SessionContext) error {
        return SagaMongoAdjustBalance(sc, sc.Client(), TransOutUID, -reqFrom(c).Amount, reqFrom(c).TransOutResult)
    })
})



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Redis support
&lt;/h3&gt;

&lt;p&gt;The following code ensures that both the business and the message are committed simultaneously under Redis&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 Go
err := msg.DoAndSubmit(busi.Busi+"/RedisQueryPrepared", func(bb *dtmcli.BranchBarrier) error {
    return bb.RedisCheckAdjustAmount(busi.RedisGet(), busi.GetRedisAccountKey(busi.TransOutUID), -30, 86400)
})


```''
The dtm check-back pattern can easily be extended to a wide variety of other transaction-enabled storage engines

## Solution features
The following features are available under two-phase messaging.
- Elegant support for multiple databases
- Support for not only SQL databases, but also NoSQL such as Mongo and Redis
- Short code, significantly less code than the usual outbox pattern
- The entire architecture and development process does not involve message queues, only api, making it easier to get started
- Load is only related to the volume of messages, not the number of databases involved

## Compare to RocketMQ transactional messages
Check-back was first proposed in RocketMQ transaction messages, but I has searched for examples of check-backs and various case studies, but has not found a check-back solution that handles all kinds of exceptions well. None of the solutions found correctly handle the "local transaction is still in progress" scenario, and all have corner cases that lead to inconsistent data, see [https://en.dtm.pub/practice/msg.html](https://en.dtm.pub/practice/msg.html) for more information .

In addition, dtm's two-stage messages do not require the introduction of a queue, or can be used in conjunction with other message queues, so they are more widely available

## Summary
The dtm two-stage messaging presented in this article is better suited to multi-database situations. The architecture solution, with its many advantages, is a perfect alternative to the outbox pattern and gives developers a simpler and easier to use architecture.

You are welcomed to visit [https://github.com/dtm-labs/dtm](https://github.com/dtm-labs/dtm)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>programming</category>
      <category>go</category>
      <category>database</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>Three Pitfalls and Best Practice for Saga Pattern in Microservices</title>
      <dc:creator>yedf2</dc:creator>
      <pubDate>Thu, 26 May 2022 03:15:28 +0000</pubDate>
      <link>https://dev.to/yedf2/how-to-manage-anomalies-in-saga-pattern-in-microservices-7ki</link>
      <guid>https://dev.to/yedf2/how-to-manage-anomalies-in-saga-pattern-in-microservices-7ki</guid>
      <description>&lt;p&gt;Saga is the most common pattern for ensuring cross-service data consistency. It splits a global transaction into multiple sub-transactions, and if all sub-transaction operations succeed, then the global transaction succeeds, and if one sub-transaction still fails after retries, then all executed sub-transactions are compensated.&lt;/p&gt;

&lt;p&gt;The above process is simple and clear, but in a distributed environment, there are anomalies that can occur. It is not an easy job to ensure that a Saga transaction is working correctly and to keep the data in consistent. We will discuss the various anomalies that can occur here and how to manage them.&lt;/p&gt;

&lt;p&gt;For the purposes of discussion, we will refer to the forward action of a branch transaction in Saga as &lt;code&gt;Action&lt;/code&gt; and the compensating action as &lt;code&gt;Compensation&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Types of Anomalies
&lt;/h2&gt;

&lt;p&gt;Normally, the &lt;code&gt;Action&lt;/code&gt; is executed before the &lt;code&gt;Compensation&lt;/code&gt; in Saga transactions if rollback happens. But due to network delays, or process pauses, it is possible that the &lt;code&gt;Compensation&lt;/code&gt; is executed first and the &lt;code&gt;Action&lt;/code&gt; is executed later or not at all.&lt;/p&gt;

&lt;p&gt;This scenarios introduces two anomalies in distributed transactions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Null Compensation: When &lt;code&gt;Compensation&lt;/code&gt; is executing, the corresponding &lt;code&gt;Action&lt;/code&gt; has not been executed, so the &lt;code&gt;Compensation&lt;/code&gt; needs to determine that the &lt;code&gt;Action&lt;/code&gt; has not been executed, ignore the business data updates, and return directly.&lt;/li&gt;
&lt;li&gt;Hanging Action: When the &lt;code&gt;Action&lt;/code&gt; is executing, the &lt;code&gt;Compensation&lt;/code&gt; has been executed, so the &lt;code&gt;Action&lt;/code&gt; needs to determine that the &lt;code&gt;Compensation&lt;/code&gt; has been executed, ignore the business data updates, and return directly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Another common anomaly that needs to be dealt with in distributed transactions is &lt;code&gt;Duplicated Requests&lt;/code&gt;. When a process crashes and Saga retries the service, the service may be called multiple times, so idempotent processing is needed.&lt;/p&gt;

&lt;p&gt;These three types of anomalies need to be managed carefully, otherwise data inconsistencies can occur and disrupt the business.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practice
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/dtm-labs/dtm"&gt;https://github.com/dtm-labs/dtm&lt;/a&gt; pioneered a technique name &lt;code&gt;Sub-transaction Barrier&lt;/code&gt; for managing these three types of anomalies at the same time. For Saga pattern, the principle is as follows.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a table dtm_barrier.barrier in the local database, with a unique index of &lt;code&gt;gid-branchid-branchop&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Begin a local transaction&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;Action&lt;/code&gt; and &lt;code&gt;Compensation&lt;/code&gt;, insert ignore a row &lt;code&gt;gid-branchid-action|compensation&lt;/code&gt;, if the number of affected rows is 0 (in case of Duplicate Requests, Hanging Action), commit directly and return&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;Compensation&lt;/code&gt;, insert ignore an additional row &lt;code&gt;gid-branchid-action&lt;/code&gt;, if the number of rows affected is 1 (in case of Null Compensation), commit directly and return&lt;/li&gt;
&lt;li&gt;Execute business logic and commit, or roll back if errors occur&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Duplicated Requests
&lt;/h3&gt;

&lt;p&gt;Because of the unique index above, insertion in duplicated requests are guaranteed to be ignored and business processing is skipped&lt;/p&gt;

&lt;h3&gt;
  
  
  Null Compensation
&lt;/h3&gt;

&lt;p&gt;When a null compensation occurs, a successful insertion of step 3 and an ignored insertion of step 4 will be committed directly and returned, skipping the business logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hanging Action
&lt;/h3&gt;

&lt;p&gt;When &lt;code&gt;Hanging Action&lt;/code&gt; occurs, the compensation has been executed and a row &lt;code&gt;gid-branchid-action&lt;/code&gt; has been inserted, so in &lt;code&gt;Action&lt;/code&gt;, it will find that the insertion at step 3 was ignored and then return directly, skipping the business logic&lt;/p&gt;

&lt;h3&gt;
  
  
  Action and Compensation Overlap
&lt;/h3&gt;

&lt;p&gt;If &lt;code&gt;Action&lt;/code&gt; and &lt;code&gt;Compensation&lt;/code&gt; overlap in execution time, both &lt;code&gt;Compensation&lt;/code&gt; and &lt;code&gt;Action&lt;/code&gt; will insert the same row &lt;code&gt;gid-branchid-action&lt;/code&gt;. Due to a unique index conflict, only one of the two operations will succeed, while the other will wait for the transaction holding the lock to complete and then return.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scenario 1: The &lt;code&gt;Action&lt;/code&gt; fails and the &lt;code&gt;Compensation&lt;/code&gt; succeeds in inserting &lt;code&gt;gid-branchid-action&lt;/code&gt;. This is a typical &lt;code&gt;Null Compensation&lt;/code&gt; and &lt;code&gt;Hanging Action&lt;/code&gt; scenario and both &lt;code&gt;Action&lt;/code&gt; and &lt;code&gt;Compensation&lt;/code&gt; will ignore the business logic and return directly according to the above algorithm&lt;/li&gt;
&lt;li&gt;Scenario 2: The &lt;code&gt;Action&lt;/code&gt; succees and the &lt;code&gt;Compensation&lt;/code&gt; fails in inserting &lt;code&gt;gid-branchid-action&lt;/code&gt;. The order of transaction execution is &lt;code&gt;Action&lt;/code&gt; before &lt;code&gt;Compensation&lt;/code&gt;, and no anomaly occur. According to the above algorithm, the business in &lt;code&gt;Action&lt;/code&gt; and &lt;code&gt;Compensation&lt;/code&gt; will be executed orderly&lt;/li&gt;
&lt;li&gt;Scenario 3, If the database go down during the overlap, the operations will be retried and will eventually go to scenario 1 or 2.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In summary of the above scenarios, the &lt;code&gt;Sub-transaction Barrier&lt;/code&gt; is able to properly manage the anomalies and ensure the data consistency.&lt;/p&gt;

&lt;p&gt;The algorithm described above also applies to TCC distributed transactions. Even if you are using a workflow engine, such as Candence, Camunda, to handle Saga transactions, the algorithm also applies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The above &lt;code&gt;Sub-transaction Barrier&lt;/code&gt; technique, when used in conjunction with the distributed transaction framework &lt;a href="https://github.com/dtm-labs/dtm"&gt;https://github.com/dtm-labs/dtm&lt;/a&gt;, has been made available in several language &lt;a href="https://en.dtm.pub/ref/sdk"&gt;SDKs&lt;/a&gt;, with the following example code in Go.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BusiAPI&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaBTransOut"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;barrier&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;MustBarrierFromGin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;barrier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txGet&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;SagaAdjustBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TransOutUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;reqFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BusiAPI&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaBTransOutCom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;barrier&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;MustBarrierFromGin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;barrier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txGet&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;SagaAdjustBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TransOutUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;TransOut&lt;/code&gt; service, the first line is to create a barrier from http request, and the next several lines call the business inside the barrier.&lt;/p&gt;

&lt;p&gt;When we put business processing inside a sub-transaction barrier, the various anomalies described earlier, are filtered and the developer only needs to consider the business processing under normal flow.&lt;/p&gt;

&lt;p&gt;The complete example can be found here: &lt;a href="https://github.com/dtm-labs/dtm-examples"&gt;https://github.com/dtm-labs/dtm-examples&lt;/a&gt;. After &lt;code&gt;dtm&lt;/code&gt; setup, you can run a complete example by following command:&lt;br&gt;
&lt;code&gt;go run main.go http_saga_barrier&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This article propose an algorithm to handle anomalies in Saga pattern for microservices. It is elegant and efficient, which can apply to TCC pattern, and also apply to many workflows.&lt;/p&gt;

&lt;p&gt;You are welcomed to visit &lt;a href="https://github.com/dtm-labs/dtm"&gt;https://github.com/dtm-labs/dtm&lt;/a&gt;. It is an opensourced project dedicated to ease the development of distributed transactions.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Understanding XA Transactions With Practical Examples in Go</title>
      <dc:creator>yedf2</dc:creator>
      <pubDate>Tue, 17 May 2022 08:36:23 +0000</pubDate>
      <link>https://dev.to/yedf2/understanding-xa-transactions-with-practical-examples-in-go-a9i</link>
      <guid>https://dev.to/yedf2/understanding-xa-transactions-with-practical-examples-in-go-a9i</guid>
      <description>&lt;h2&gt;
  
  
  What is XA
&lt;/h2&gt;

&lt;p&gt;XA is a specification for distributed transactions proposed by the X/Open organization.&lt;br&gt;
The X/Open Distributed Transaction Processing (DTP) model envisages three software&lt;br&gt;
components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An application program (AP) defines transaction boundaries and specifies actions that constitute a transaction.&lt;/li&gt;
&lt;li&gt;Resource managers (RMs, such as databases or file access systems) provide access to shared resources.&lt;/li&gt;
&lt;li&gt;A separate component called a transaction manager (TM) assigns identifiers to transactions, monitors their progress, and takes responsibility for transaction completion and for failure recovery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following figure illustrates the interfaces defined by the X/Open DTP model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F28fgsocj398hr34a1z28.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F28fgsocj398hr34a1z28.png" alt="xa-dtp" width="552" height="253"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;XA is divided into two phases.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Phase 1 (prepare): All participating RMs prepare to execute their transactions and lock the required resources.&lt;br&gt;
When each participant is ready, it report to TM.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Phase 2 (commit/rollback): When the transaction manager (TM) receives that all participants (RM) are ready, it sends commit commands to all participants. Otherwise, it sends rollback commands to all participants.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At present, almost all popular databases support XA transactions, including Mysql, Oracle, SqlServer, and Postgres&lt;/p&gt;
&lt;h2&gt;
  
  
  XA in Mysql
&lt;/h2&gt;

&lt;p&gt;Let's see how database Mysql supports XA.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;XA&lt;/span&gt; &lt;span class="k"&gt;start&lt;/span&gt; &lt;span class="s1"&gt;'4fPqCNTYeSG'&lt;/span&gt; &lt;span class="c1"&gt;-- start a xa transaction&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="nv"&gt;`user_account`&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="nv"&gt;`balance`&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;`update_time`&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'2021-06-09 11:50:42.438'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;XA&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="s1"&gt;'4fPqCNTYeSG'&lt;/span&gt;
&lt;span class="c1"&gt;-- if connection closed before `prepare`, then the transaction is rolled back automatically&lt;/span&gt;
&lt;span class="n"&gt;XA&lt;/span&gt; &lt;span class="k"&gt;prepare&lt;/span&gt; &lt;span class="s1"&gt;'4fPqCNTYeSG'&lt;/span&gt;

&lt;span class="c1"&gt;-- When all participants have all prepared, call commit in phase 2&lt;/span&gt;
&lt;span class="n"&gt;xa&lt;/span&gt; &lt;span class="k"&gt;commit&lt;/span&gt; &lt;span class="s1"&gt;'4fPqCNTYeSG'&lt;/span&gt;

&lt;span class="c1"&gt;-- When any participants have failed to prepare, call rollback in phase 2&lt;/span&gt;
&lt;span class="c1"&gt;-- xa rollback '4fPqCNTYeSG'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Business Scenario
&lt;/h2&gt;

&lt;p&gt;An inter-bank transfer is a typical distributed transaction scenario, where A needs to transfer money across a bank to B. Both the withdraw and the deposit may succeed or fail, and it is required that the sum of balance of A and B should not change after the transfer finished, regardless of any errors that occur.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement a Distributed XA Transaction
&lt;/h2&gt;

&lt;p&gt;Distributed XA transactions can solve the above business problem. This article presents a solution based on &lt;a href="https://github.com/dtm-labs/dtm" rel="noopener noreferrer"&gt;dtm-labs/dtm&lt;/a&gt;. DTM is a popular distributed transaction framework which supports XA, Saga, OutBox, and TCC patterns.&lt;/p&gt;

&lt;p&gt;A successful global transaction is illustrated by the following figure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmr9x3apg872vtuevlbnt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmr9x3apg872vtuevlbnt.png" alt="xa-normal" width="800" height="842"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code to implement it in Go is quite simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;gid&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustGenGid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultHTTPServer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;XaGlobalTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultHTTPServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xa&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Xa&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;resty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;xa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CallBranch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransReq&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Busi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/TransOutXa"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;xa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CallBranch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransReq&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Busi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/TransInXa"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BusiAPI&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/TransInXa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapHandler2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;XaLocalTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;BusiConf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xa&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Xa&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;AdjustBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TransInUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransInResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BusiAPI&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/TransOutXa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapHandler2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;XaLocalTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;BusiConf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xa&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Xa&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;AdjustBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TransOutUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransOutResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code first registers a global XA transaction, and then calls two sub-transactions TransOut, TransIn.&lt;br&gt;
After all the sub-transactions are executed successfully, the global XA transaction is committed to DTM.&lt;br&gt;
DTM receives the commitment of the XA global transaction, then calls the &lt;code&gt;XA commit&lt;/code&gt; of all the sub-transactions, and finally change the status of global transaction to succeeded.&lt;/p&gt;

&lt;p&gt;Code samples in other languages can be found here: &lt;a href="https://en.dtm.pub/ref/sdk" rel="noopener noreferrer"&gt;SDKs&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Run It
&lt;/h2&gt;

&lt;p&gt;You can run the above example by running the following commands.&lt;/p&gt;
&lt;h3&gt;
  
  
  Run DTM
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/dtm-labs/dtm &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;dtm
go run main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Run Example
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/dtm-labs/dtm-examples &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;dtm-examples
go run main.go http_xa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Rollback upon Failure
&lt;/h2&gt;

&lt;p&gt;If any &lt;code&gt;prepare&lt;/code&gt; operation fails, DTM will call &lt;code&gt;XA rollback&lt;/code&gt; of each sub-transaction to roll back, and finally change the status of the global transaction to failed.&lt;/p&gt;

&lt;p&gt;Let's pass &lt;code&gt;TransInResult="FAILURE"&lt;/code&gt; in the request payload of XaFireRequest to trigger a failure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransReq&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TransInResult&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"FAILURE"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The timing diagram for failure is as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyuuvpcof1gwp377ur78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyuuvpcof1gwp377ur78.png" alt="xa-rollback" width="800" height="848"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Notices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The command of second phase, is also sent to the API &lt;code&gt;BusiAPI+"/TransOutXa"&lt;/code&gt;, and within this service, &lt;code&gt;dtmcli.XaLocalTransaction&lt;/code&gt; will automatically call &lt;code&gt;XA commit&lt;/code&gt; | &lt;code&gt;XA rollback&lt;/code&gt;. So the body of the request is nil, and parsing body operations, such as the previous &lt;code&gt;reqFrom&lt;/code&gt;, need to be placed inside &lt;code&gt;XaLocalTransaction&lt;/code&gt;, otherwise the body parsing will result in errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TM Failure
&lt;/h2&gt;

&lt;p&gt;The complexity of two-phase commit comes from all the failure scenarios that can arise. The most annoying failure happens after a participant has acknowledged prepared and before it receives the decision, such as a failure of the coordinator.&lt;/p&gt;

&lt;p&gt;Our solution to this failure is quite simple and robust. Since most of cloud vendors provide highly-available databases, we can store the progress of sub-transactions in these databases, run multiple instance of TMs. Each instance will poll the paused transactions and continue to process them.&lt;/p&gt;

&lt;p&gt;The cloud vendor takes care of the database failure, and may use Paxos/Raft to robustly elect a healthy instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advantages and Disadvantages
&lt;/h2&gt;

&lt;p&gt;Compared to other patterns like SAGA and TCC, the advantages of XA global transactions are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple and easy to understand&lt;/li&gt;
&lt;li&gt;Automatic rollback of business, no need to write compensation manually&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The disadvantages of XA are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Need the XA transaction from underlying database&lt;/li&gt;
&lt;li&gt;Data is locked from data modification until the commitment, much longer than other patterns. It not suitable for highly concurrent business.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, I've introduce the basic principle of XA transaction, and presents a practical example of it. Readers can easily follow the example to handle their own businesses.&lt;/p&gt;

&lt;p&gt;Welcome to visit &lt;a href="https://github.com/dtm-labs/dtm" rel="noopener noreferrer"&gt;github.com/dtm-labs/dtm&lt;/a&gt;. It is a dedicated project to make distributed transactions in microservices easier. It supports multiple languages, and multiple patterns like a 2-phase message, Saga, TCC, and XA.&lt;/p&gt;

</description>
      <category>go</category>
      <category>mysql</category>
      <category>distributedsystems</category>
      <category>microservices</category>
    </item>
    <item>
      <title>How to Implement a Distributed Transaction Across Mysql, Redis, and Mongo</title>
      <dc:creator>yedf2</dc:creator>
      <pubDate>Thu, 05 May 2022 02:27:34 +0000</pubDate>
      <link>https://dev.to/yedf2/how-to-implement-a-distributed-transaction-across-mysql-redis-and-mongo-ind</link>
      <guid>https://dev.to/yedf2/how-to-implement-a-distributed-transaction-across-mysql-redis-and-mongo-ind</guid>
      <description>&lt;p&gt;Mysql, Redis and Mongo are all very popular stores, and each has its own advantages. In practical applications, it is common to use multiple stores at the same time and ensuring data consistency across multiple stores becomes a requirement.&lt;/p&gt;

&lt;p&gt;This article gives an example of implementing a distributed transaction across multiple store engines, Mysql, Redis and Mongo. This example is based on the Distributed Transaction Framework &lt;a href="https://github.com/dtm-labs/dtm"&gt;https://github.com/dtm-labs/dtm&lt;/a&gt; and will hopefully help to solve your problems in data consistency across microservices.&lt;/p&gt;

&lt;p&gt;The ability to flexibly combine multiple storage engines to form a distributed transaction is firstly proposed by DTM, and no other distributed transaction framework has stated the ability like this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem scenarios
&lt;/h2&gt;

&lt;p&gt;Let's look at the problem scenario first. Suppose that a user now participates in an promotion: he or she have a balance, recharge the phone bill, and the promotion will give away mall points. The balance is stored in Mysql, the bill is stored in Redis, the mall points is stored in Mongo. Because the promotion is limited in time, there is a possibility that participation may fail, so rollback support is required.&lt;/p&gt;

&lt;p&gt;For the above problem scenario, you can use DTM's Saga transaction, and we will explain the solution in detail below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing the Data
&lt;/h2&gt;

&lt;p&gt;The first step is to prepare the data. To make it easier for users to quickly get started with the examples, we have prepared the relevant data at &lt;a href="https://en.dtm.pub"&gt;en.dtm.pub&lt;/a&gt;, which includes Mysql, Redis and Mongo, and the specific connection username and password can be found at &lt;a href="https://github.com/dtm-labs/dtm-examples"&gt;https://github.com/dtm-labs/dtm-examples&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to prepare the data environment locally yourself, you can use &lt;a href="https://github.com/dtm-labs/dtm/blob/main/helper/compose.store.yml"&gt;https://github.com/dtm-labs/dtm/blob/main/helper/compose.store.yml&lt;/a&gt; to start Mysql, Redis, Mongo; and then execute scripts in &lt;a href="https://github.com/dtm-labs/dtm/tree/main/sqls"&gt;https://github.com/dtm-labs/dtm/tree/main/sqls&lt;/a&gt; to prepare the data for this example, where &lt;code&gt;busi.*&lt;/code&gt; is the business data and &lt;code&gt;barrier.*&lt;/code&gt; is the auxiliary table used by DTM&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Writing the Business Code
&lt;/h2&gt;

&lt;p&gt;Let's start with the business code for the most familiar Mysql.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The following code is in Golang. Other languages such as C#, PHP, Java can be found here: &lt;a href="https://en.dtm.pub/ref/sdk.html"&gt;DTM SDKs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;SagaAdjustBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dtmimp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DBExec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"update dtm_busi.user_account set balance = balance + ? where user_id = ?"&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code mainly performs the adjustment of the user's balance in the database. In our example, this part of the code is used not only for Saga's forward operation, but also for the compensation operation, where only a negative amount needs to be passed in for compensation.&lt;/p&gt;

&lt;p&gt;For Redis and Mongo, the business code is handled similarly, just incrementing or decrementing the corresponding balances.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Ensure Idempotency
&lt;/h2&gt;

&lt;p&gt;For the Saga transaction pattern, when we have a temporary failure in the sub-transaction service, the failed operation will be retried. This failure may occur before or after the sub-transaction commits, so the sub-transaction operation needs to be idempotent.&lt;/p&gt;

&lt;p&gt;DTM provides helper tables and helper functions to help users achieve idempotency quickly. For Mysql, it will create an auxiliary table &lt;code&gt;barrier&lt;/code&gt; in the business database, when the user start a transaction to adjust the balance, it will first insert &lt;code&gt;Gid&lt;/code&gt; in the &lt;code&gt;barrier&lt;/code&gt; table. If there is a duplicate row, then the insertion will fail, and then skip the balance adjustment to ensure the idempotent. The code using the helper function is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BusiAPI&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaBTransIn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapHandler2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MustBarrierFromGin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txGet&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;SagaAdjustBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TransInUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransInResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mongo handles idempotency in a similar way to Mysql, so I won't go into detail again.&lt;/p&gt;

&lt;p&gt;Redis handles idempotency differently than Mysql, mainly because of the difference in the principle of transactions. Redis transactions are mainly ensured by atomic execution of Lua. the DTM helper function will adjust the balance via a Lua script. Before adjusting the balance, it will query &lt;code&gt;Gid&lt;/code&gt; in Redis. If &lt;code&gt;Gid&lt;/code&gt; exists, it will skip the balance adjustment; if not, it will record &lt;code&gt;Gid&lt;/code&gt; and perform the balance adjustment. The code used for the helper function is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BusiAPI&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaRedisTransOut"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapHandler2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MustBarrierFromGin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RedisCheckAdjustAmount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RedisGet&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;GetRedisAccountKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TransOutUID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;reqFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to do Compensation
&lt;/h2&gt;

&lt;p&gt;For Saga, we also need to deal with the compensation operation, but the compensation is not simply a reverse adjustment, and there are many pitfalls that should be aware of.&lt;/p&gt;

&lt;p&gt;On the one hand, compensation needs to take idempotency into account, because the failure and retries described in previous subsection also exists in compensation. On the other hand, compensation also needs to take "null compensation" into account, since the forward operation of Saga may returns a failure, which may have happened before or after the data adjustment. For failures where the adjustment has been committed we need to perform the reverse adjustment; but for failures where the adjustment has not been committed we need to skip the reverse operation.&lt;/p&gt;

&lt;p&gt;In the helper table and helper functions provided by DTM, on the one hand, it will determine whether the compensation is a null compensation based on the Gid inserted by the forward operation, and on the other hand, it will insert Gid+'compensate' again to determine whether the compensation is a duplicate operation. If there is a normal compensation operation, then it will execute the data adjustment on the business; if there is a null compensation or duplicate compensation, it will skip the adjustment on the business.&lt;/p&gt;

&lt;p&gt;The Mysql code is as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BusiAPI&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaBTransInCom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapHandler2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MustBarrierFromGin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txGet&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;SagaAdjustBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TransInUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;reqFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code for Redis is as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BusiAPI&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaRedisTransOutCom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapHandler2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MustBarrierFromGin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RedisCheckAdjustAmount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RedisGet&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;GetRedisAccountKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TransOutUID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;reqFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compensation service code is almost identical to the previous code of the forward operation, except that the amount is multiplied by -1. The DTM helper function automatically handles idempotency and null compensation properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other exceptions
&lt;/h2&gt;

&lt;p&gt;When writing forward operations and compensation operations, there is actually another exception called "Suspension". A global transaction will roll back when it is timeout or retries has reach the configured limit. The normal case is that the forward operation is performed before the compensation, but in case of process suspension the compensation may be performed before the forward operation. So the forward operation also needs to determine whether the compensation has been executed, and in the case that it has, the data adjustment needs to be skipped as well.&lt;/p&gt;

&lt;p&gt;For DTM users, these exceptions have been handled gracefully and properly and you, as a user, need only follow the &lt;code&gt;MustBarrierFromGin(c).Call&lt;/code&gt; call described above and do not need to care about them at all. The principle for DTM handling these exceptions is described in detail here: &lt;a href="https://dtm.pub/practice/barrier.html"&gt;Exceptions and sub-transaction barriers&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Initiating a Distributed Transaction
&lt;/h2&gt;

&lt;p&gt;After writing the individual sub-transaction services, the following codes of the code initiates a Saga global transaction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;saga&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewSaga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultHTTPServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustGenGid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultHTTPServer&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
  &lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Busi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaBTransOut"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Busi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaBTransOutCom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransReq&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
  &lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Busi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaMongoTransIn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Busi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaMongoTransInCom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransReq&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
  &lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Busi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaRedisTransIn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Busi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaRedisTransOutIn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransReq&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Submit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this part of the code, a Saga global transaction is created which consists of 3 sub-transactions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transfer out 50 from Mysql&lt;/li&gt;
&lt;li&gt;Transfer in 30 to Mongo&lt;/li&gt;
&lt;li&gt;Transfer in 20 to Redis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Throughout the transaction, if all the sub-transactions complete successfully, then the global transaction succeeds; if one of the sub-transactions returns a business failure, then the global transaction rolls back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run
&lt;/h2&gt;

&lt;p&gt;If you want to run a complete example of the above, the steps are as follows.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run DTM
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/dtm-labs/dtm &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;dtm
go run main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Run a successful example
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/dtm-labs/dtm-examples &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;dtm-examples
go run main.go http_saga_multidb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt; Run a failed example
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/dtm-labs/dtm-examples &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;dtm-examples
go run main.go http_saga_multidb_rollback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can modify the example to simulate various temporary failures, null compensation situations, and various other exceptions where the data is consistent when the entire global transaction is finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This article gives an example of a distributed transaction across Mysql, Redis and Mongo. It describes in detail the problems that need to be dealt with, and the solutions.&lt;/p&gt;

&lt;p&gt;The principles in this article are suitable for all storage engines that support ACID transactions, and you can quickly extend it for other engines such as TiKV.&lt;/p&gt;

&lt;p&gt;Welcome to visit &lt;a href="https://github.com/dtm-labs/dtm"&gt;github.com/dtm-labs/dtm&lt;/a&gt;. It is a dedicated project to make distributed transactions in microservices easier. It supports multiple languages, and multiple patterns like a 2-phase message, Saga, Tcc, and Xa.&lt;/p&gt;

</description>
      <category>go</category>
      <category>database</category>
      <category>distributedsystems</category>
      <category>redis</category>
    </item>
    <item>
      <title>A Better Pattern than OutBox - 2-Phase Message</title>
      <dc:creator>yedf2</dc:creator>
      <pubDate>Tue, 12 Apr 2022 12:26:03 +0000</pubDate>
      <link>https://dev.to/yedf2/a-better-pattern-than-outbox-2-phase-message-353k</link>
      <guid>https://dev.to/yedf2/a-better-pattern-than-outbox-2-phase-message-353k</guid>
      <description>&lt;p&gt;This article proposes an alternative pattern to OutBox: 2-phase message. It is not based on message queue, but based on &lt;a href="https://github.com/dtm-labs/dtm" rel="noopener noreferrer"&gt;github.com/dtm-labs/dtm&lt;/a&gt;, a highly available distributed transaction framework.&lt;/p&gt;

&lt;p&gt;An inter-bank transfer is a typical distributed transaction scenario, where A needs to transfer money across a bank to B. The balances of A and B are not in the same bank so that they are not stored in a single database. This transfer is typically crossing micro-services also.&lt;/p&gt;

&lt;p&gt;The main problem is that the transfer must update two systems simultaneously -- the increment of A' balance and the decrement of B's balance. This is called well-known "dual writes". A process crash between the two updates leaves the entire system in an inconsistent state.&lt;/p&gt;

&lt;p&gt;This "dual writes" problem can be solved by OutBox pattern. The principal of OutBox pattern can be found here &lt;a href="https://microservices.io/patterns/data/transactional-outbox.html" rel="noopener noreferrer"&gt;Transactional OutBox&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2-Phase Message
&lt;/h2&gt;

&lt;p&gt;First let's take a glance at how to accomplish the above transfer task using the new pattern. The following codes is in Go, other languages like C#, PHP can be found here: &lt;a href="https://en.dtm.pub/ref/sdk.html" rel="noopener noreferrer"&gt;dtm SDKs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewMsg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DtmServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Busi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/TransIn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;TransReq&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoAndSubmitDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Busi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/QueryPrepared"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;AdjustBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransOutUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above codes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First new a DTM &lt;code&gt;msg&lt;/code&gt; global transaction, passing the dtm server address and the global transaction id&lt;/li&gt;
&lt;li&gt;Add to the &lt;code&gt;msg&lt;/code&gt; a branch business, which is the transfer operation TransIn, together with the data that needs to be passed to this service, the amount 30$&lt;/li&gt;
&lt;li&gt;Then call &lt;code&gt;msg&lt;/code&gt;'s DoAndSubmitDB. This function will ensure the atomic execution of both the business and submission of &lt;code&gt;msg&lt;/code&gt;, either both succeeded, or both failed. There are three parameters for this function:

&lt;ol&gt;
&lt;li&gt;The check-back URL, will be explained later&lt;/li&gt;
&lt;li&gt;DB, is the database object for the business&lt;/li&gt;
&lt;li&gt;The business function, here in our example is to debit 30$ for A's balance&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What will happen when the process crashed immediately after the success of decrement for A's balance? After a timeout, DTM will call the check-back URL to query whether the decrement is successful or unsuccessful. We can accomplish the check-back service by pasting the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BusiAPI&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/QueryPrepared"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapHandler2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MustBarrierFromGin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryPrepared&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After writing these two pieces of codes, a 2-phase message is accomplished, much easier to use than OutBox.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run It
&lt;/h2&gt;

&lt;p&gt;You can run the above example by running the following commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run DTM
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/dtm-labs/dtm &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;dtm
go run main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/dtm-labs/dtm-examples &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;dtm-examples
go run main.go http_msg_doAndCommit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Successful Process
&lt;/h2&gt;

&lt;p&gt;How does DoAndSubmitDB ensure the atomicity of successful business execution and msg submission? Please see the following timing diagram.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjaa9ccmvol7i1sarmpr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjaa9ccmvol7i1sarmpr.png" alt="msg-normal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In general, the 5 steps in the timing diagram will complete normally, and the global transaction completes. There is something needed to explain here: the commitment of &lt;code&gt;msg&lt;/code&gt; is done in two phases, first Prepare, then Submit. After DTM receives the Prepare request, it does not call the branch transaction, but waits for the subsequent Submit. Only when it receives the Submit request, it starts the branch call and finally completes the global transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crash After Commitment
&lt;/h2&gt;

&lt;p&gt;In a distributed system, all kinds of downtime and network exceptions need to be considered, so let's take a look at what can happen.&lt;/p&gt;

&lt;p&gt;The most important goal we want to achieve is that both the business execution and the message submission compose an atomic operation. So let's first look at what will happen if there is a downtime failure after the business execution and before the message submission, and how the new pattern will ensure the atomicity.&lt;/p&gt;

&lt;p&gt;Let's take a look at the timing diagram in this case.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl5mcewyrlfeoqez2e2r1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl5mcewyrlfeoqez2e2r1.png" alt="msg-query"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, DTM will poll the messages that is only Prepared but not Submitted after a certain timeout and call the check-back service specified by the message to query whether the business execution is successful.&lt;/p&gt;

&lt;p&gt;This check-back service goes inside the message table and queries whether the local transaction for business has been committed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Committed:&lt;/strong&gt; Returns success, dtm submits the global transaction and proceeds to the next sub-transaction call&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rolled back:&lt;/strong&gt; Failure is returned, dtm terminates the global transaction and no more sub-transaction calls are made&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In progress:&lt;/strong&gt; This check-back will wait for the final result and then proceeds to the previous committed/rollbacked case&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not Started:&lt;/strong&gt; This check-back will insert data to ensure that the local transaction for business eventually fails&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Crash Before Commitment
&lt;/h2&gt;

&lt;p&gt;Let's take a look at the timing diagram of a local transaction being rolled back.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0oxw5cz3vb8iwn9mufu7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0oxw5cz3vb8iwn9mufu7.png" alt="msg-rollback"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the process is crashed immediately after the dtm receives the Prepare call and before the transaction commitment, the local database will detect the process's disconnection and rollback the local transaction automatically.&lt;/p&gt;

&lt;p&gt;Subsequently, dtm polls for the global transactions that have timed out, that is only Prepared but not Submitted, and checks back. The check-back service finds that the local transaction has been rollbacked and returns the result to dtm. dtm receives the result indicating rollbacked, and then marks the global transaction as failed, and finally ends the global transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  2-Phase Message VS OutBox
&lt;/h2&gt;

&lt;p&gt;The OutBox pattern can also ensure the eventual consistency of the data. As far as OutBox pattern is used, the work required includes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Executing the local business logic in the local transaction, inserting the messages into the message table and committing them at last.&lt;/li&gt;
&lt;li&gt;Writing polling tasks to take messages from the local message table and send them to the message queue. Instead of periodically  executing SQL to poll, this step may use another technique &lt;a href="https://debezium.io/blog/2018/07/19/advantages-of-log-based-change-data-capture/" rel="noopener noreferrer"&gt;Log-based Change Data Capture&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Consuming messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compared with OutBox, 2-phase message has the following advantages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No need to learn or maintain any message queues&lt;/li&gt;
&lt;li&gt;No polling tasks to handle&lt;/li&gt;
&lt;li&gt;No need to consume messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2-phase message only need DTM, which is much easier to learn or to maintain than message queues. All skills involved are function calls and services calls, which are familiar things to all developers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The exposed interfaces of 2-phase messages are completely independent of the queue and are only related to the actual business and service calls, making it more developer-friendly&lt;/li&gt;
&lt;li&gt;2-phase messages do not have to consider the message stacking and other failures, because 2-phase messages depend only on dtm. Developers can think of dtm as being the same as any other ordinary stateless service in the system, relying only on the storage behind it, Mysql/Redis.&lt;/li&gt;
&lt;li&gt;The message queue is asynchronous, while 2-phase messages support both asynchronous and synchronous. The default behaviour is asynchronous, and you can wait for the downstream service to complete synchronously just by setting &lt;code&gt;msg.WaitResult=true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;2-phase messages also support specifying multiple downstream services at the same time&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Application of 2-Phase Message
&lt;/h3&gt;

&lt;p&gt;2-phase messages can significantly reduce the difficulty of the eventual consistency solution and have been widely used, here are two typical applications.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="//../app/flash"&gt;flash-sale system&lt;/a&gt;: this architecture can easily carry tens of thousands of order requests on a single machine, and ensure that the number of inventory deducted and the number of orders are accurately matched&lt;/li&gt;
&lt;li&gt;
&lt;a href="//../app/cache"&gt;cache consistency&lt;/a&gt;: this architecture can easily ensure the consistency of DB and cache through 2-phase message, which is much better than queue or subscription binlog solution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example of using redis, Mongo storage engine in combination with 2-phase messages can be found in &lt;a href="https://github.com/dtm-labs/dtm-examples" rel="noopener noreferrer"&gt;dtm-examples&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Check-back Principle
&lt;/h2&gt;

&lt;p&gt;The check-back service appears in the previous timing diagram, as well as in the interface. This check-back design firstly existed in RocketMQ, and the implementation is left to developers to handle manually. In the 2-phase messages, it is handled automatically by copy-and-paste code. So what is the principle of automatic processing?&lt;/p&gt;

&lt;p&gt;To perform a check-back, we firstly create a separate table in the business database instance where the gid(global transaction id) is stored. Gid is written to this table when the business transaction is processed.&lt;/p&gt;

&lt;p&gt;When we check back with the gid, if we find gid in the table, then it means the local transaction has been committed, so we can return to dtm the result that the local transaction has been committed.&lt;/p&gt;

&lt;p&gt;When we check back with the gid, if we don't find gid in the table, then it means the local transaction has not been committed. There are three possible results:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The transaction is still in progress.&lt;/li&gt;
&lt;li&gt;The transaction has been rolled back.&lt;/li&gt;
&lt;li&gt;The transaction has not started.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I have searched a lot of information about RocketMQ's check-back, but have not found a error-free solution. Most suggestions is that if the gid is not found, then do nothing and wait for the next check-back in next 10 seconds. If the check-back has lasted 2 minutes or longer and still cannot find the gid, then the local transaction is considered rollbacked.&lt;/p&gt;

&lt;p&gt;There are problems in the following cases.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the extreme case, a database failure (such as a process pause or disk jam) may occur, lasting longer than 2 minutes, and finally the data is committed. But RocketMQ assume the transaction is rolled back, and cancel the global transaction, leaving the data in inconsistent state.&lt;/li&gt;
&lt;li&gt;If a local transaction, has been rollbacked, but the check-back service, within two minutes, will constantly polling every 10 seconds, causing unnecessary load on the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These problems are completely solved by dtm's 2-phase message solution. It works as follows.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When a local transaction is processed, gid is inserted into the table &lt;code&gt;dtm_barrier.barrier&lt;/code&gt; with an insert reason of &lt;code&gt;COMMITTED&lt;/code&gt;. Table &lt;code&gt;dtm_barrier.barrier&lt;/code&gt; has a unique index on gid.&lt;/li&gt;
&lt;li&gt;When checking back, the 2-phase message does not directly query whether gid exists, but instead insert ignore a row with the same gid, together with the reason &lt;code&gt;ROLLBACKED&lt;/code&gt;. At this time, if there is already a record with gid in the table, then the new insert operation will be ignored, otherwise the row will be inserted.&lt;/li&gt;
&lt;li&gt;Query the records in the table with gid, if the reason of the record is &lt;code&gt;COMMITTED&lt;/code&gt;, then the local transaction has been committed; if the reason of the record is &lt;code&gt;ROLLBACKED&lt;/code&gt;, then the local transaction has been rolled back or will be rolled back.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So how do 2-phase message distinguish between in-progress and rolled back messages? The trick lies in the data inserted during the check-back. If the database transaction is still in progress at the time of the check-back, then the insert operation will be blocked by the in-progress transaction, because the insert operation in check-back will wait for the row lock held by the in-progress transaction. If the insert operation returns normally, then the local transaction in the database, which must have ended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Normal messages
&lt;/h2&gt;

&lt;p&gt;2-phase messages can replace not only OutBox, but also the normal message pattern. If you call Submit directly, then it is similar to the normal message pattern, but provides a more flexible and simple interface.&lt;/p&gt;

&lt;p&gt;Suppose an application scenario where there is a button on the UI to participate in an activity that grants permanent access to two eBooks. In this case, the server side can be handled like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewMsg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DtmServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Busi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/AuthBook"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Req&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;UID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BookID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Busi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/AuthBook"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Req&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;UID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BookID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Submit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach also provides an asynchronous interface without relying on a message message queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The 2-phase message proposed in this article has a simple and elegant interface that brings a more elegant pattern than OutBox.&lt;/p&gt;

&lt;p&gt;Welcome to visit &lt;a href="https://github.com/dtm-labs/dtm" rel="noopener noreferrer"&gt;github.com/dtm-labs/dtm&lt;/a&gt;. It is a dedicated project to make distributed transactions in micro-services easier. It support multiple languages, and multiple patterns like 2-phase message, Saga, Tcc and Xa.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>go</category>
      <category>microservices</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>How to Implement Saga Pattern in Microservices</title>
      <dc:creator>yedf2</dc:creator>
      <pubDate>Mon, 04 Apr 2022 12:54:13 +0000</pubDate>
      <link>https://dev.to/yedf2/how-to-implement-saga-pattern-in-microservices-2gj3</link>
      <guid>https://dev.to/yedf2/how-to-implement-saga-pattern-in-microservices-2gj3</guid>
      <description>&lt;p&gt;The Microservice architecture allows us to maintain applications services and their databases separately. As a sequence, it is common to have data changes that need to propagate across multiple services. Database transactions can not go across multiple services, so the data integrity becomes a challenge.&lt;/p&gt;

&lt;h2&gt;
  
  
  SAGA
&lt;/h2&gt;

&lt;p&gt;Saga is a distributed transaction pattern mentioned in this paper &lt;a href="https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf" rel="noopener noreferrer"&gt;SAGAS&lt;/a&gt;. The core idea is to split long transactions into multiple short local transactions, which are coordinated by the Saga transaction coordinator, so that if each local transaction completes successfully then it completes normally, and if anyone step fails then the compensating operations are invoked one at a time in reverse order.&lt;/p&gt;

&lt;p&gt;This article will present a complete SAGA example to give the reader an accurate understanding of SAGA transactions&lt;/p&gt;

&lt;h2&gt;
  
  
  Business Scenario
&lt;/h2&gt;

&lt;p&gt;An inter-bank transfer is a typical distributed transaction scenario, where A needs to transfer money across a bank to B. Both the withdraw and the deposit may succeed of fail, and it is required that the sum of balance of A and B should not change after the transfer finished, regardless of any errors that occur.&lt;/p&gt;

&lt;h2&gt;
  
  
  SAGA transaction
&lt;/h2&gt;

&lt;p&gt;We will present you a detailed runable example base on &lt;a href="https://github.com/dtm-labs/dtm" rel="noopener noreferrer"&gt;dtm&lt;/a&gt;, a distributed transaction framework.&lt;/p&gt;

&lt;p&gt;Suppose that you have finished your implementation of the business for transfer and rollback.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"/api/SagaBTransOut"&lt;/li&gt;
&lt;li&gt;"/api/SagaBTransOutCom" compensation for TransOut&lt;/li&gt;
&lt;li&gt;"/api/SagaBTransIn"&lt;/li&gt;
&lt;li&gt;"/api/SagaBTransInCom" compensation for TransIn&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following code will orchestrate these operations to a Sagas distributed transaction. The transaction will finished when both &lt;code&gt;TransIn&lt;/code&gt; and &lt;code&gt;TransOut&lt;/code&gt; succeed, or both rolled back. In both cases, the sum of balance of A and B remains the same.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"amount"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c"&gt;// load of microservice&lt;/span&gt;
    &lt;span class="c"&gt;// DtmServer is the address of the DTM service&lt;/span&gt;
    &lt;span class="n"&gt;saga&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewSaga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DtmServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustGenGid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DtmServer&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="c"&gt;// Add a child transaction of TransOut with url: qsBusi+"/TransOut" for the forward operation and url: qsBusi+"/TransOutCom" for the reverse operation&lt;/span&gt;
        &lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qsBusi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaBTransOut"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qsBusi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaBTransOutCom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="c"&gt;// Add a subtransaction of TransIn with url: qsBusi+"/TransOut" for the forward action and url: qsBusi+"/TransInCom" for the reverse action&lt;/span&gt;
        &lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qsBusi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaBTransIn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qsBusi&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaBTransInCom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// commit saga transaction, dtm will complete all subtransactions or rollback all subtransactions&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Submit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful transaction timing diagram is as follows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkyrkv6crz465lx0autbv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkyrkv6crz465lx0autbv.png" alt="saga-normal.jpg" width="800" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Core operations
&lt;/h2&gt;

&lt;p&gt;The adjustment and compensation of user balance should be handled carefully. Here we dive into the detail of the adjustment. For the example of the bank transfer we are going to perform, we will do the &lt;code&gt;TransOut&lt;/code&gt; and &lt;code&gt;TransIn&lt;/code&gt; in the action operation and the opposite adjustment in the compensation operation.&lt;/p&gt;

&lt;p&gt;First we create the account balance table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dtm_busi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s"&gt;`user_account`&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;`id`&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="n"&gt;PRIMARY&lt;/span&gt; &lt;span class="n"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;`user_id`&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;NULL&lt;/span&gt; &lt;span class="n"&gt;UNIQUE&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;`balance`&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;NULL&lt;/span&gt; &lt;span class="n"&gt;DEFAULT&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="m"&gt;0.00&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;`trading_balance`&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;NULL&lt;/span&gt; &lt;span class="n"&gt;DEFAULT&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="m"&gt;0.00&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;`create_time`&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="n"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="s"&gt;`update_time`&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="n"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then write the core business code to adjust the user's account balance&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;SagaAdjustBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dtmimp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DBExec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"update dtm_busi.user_account set balance = balance + ? where user_id = ?"&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then write the specific processing function for the action/compensation operation&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BusiAPI&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaBTransIn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapHandler2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;barrier&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;MustBarrierFromGin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;barrier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txGet&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;SagaAdjustBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TransInUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BusiAPI&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaBTransInCom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapHandler2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;barrier&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;MustBarrierFromGin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;barrier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txGet&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;SagaAdjustBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TransInUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;reqFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BusiAPI&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaBTransOut"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapHandler2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;barrier&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;MustBarrierFromGin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;barrier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txGet&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;SagaAdjustBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TransOutUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;reqFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BusiAPI&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaBTransOutCom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapHandler2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;barrier&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;MustBarrierFromGin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;barrier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txGet&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;SagaAdjustBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TransOutUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core logic of these processing functions is to adjust the balance, for which the role of &lt;code&gt;barrier.Call&lt;/code&gt; will be explained in detail later&lt;/p&gt;

&lt;h2&gt;
  
  
  Run
&lt;/h2&gt;

&lt;p&gt;Follow these steps to run a successful example.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run dtm which manage the distributed transactions
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/dtm-labs/dtm &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;dtm
go run main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Run the example
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/dtm-labs/dtm-examples &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;dtm-examples
go run main.go http_saga_barrier
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling network exceptions
&lt;/h2&gt;

&lt;p&gt;Suppose a transaction committed to dtm has a transient fault when an operation is invoked. dtm will retry the incomplete operation, which requires the subtransactions of the global transaction to be idempotent. dtm framework pioneered the sub-transaction barrier technique, providing the BranchBarrier tool class to help users handle idempotency easily. It provides a function &lt;code&gt;Call&lt;/code&gt; that guarantees that the operation inside this function will be commited at most once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bb&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;BranchBarrier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;busiCall&lt;/span&gt; &lt;span class="n"&gt;BarrierBusiFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This BranchBarrier can automatically handle not only idempotency, but also null-compensation and hanging issues, see &lt;a href="https://en.dtm.pub/practice/barrier" rel="noopener noreferrer"&gt;exceptions and solutions&lt;/a&gt; for details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling rollbacks
&lt;/h2&gt;

&lt;p&gt;What happens if the bank is preparing to transfer the amount to user B and finds that user B's account is abnormal and returns a failure? We update the handler function so that the transfer operation returns a failure&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BusiAPI&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaBTransIn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapHandler2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrFailure&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We give the timing diagram for the transaction failure interaction&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3ffffb6cnhucwja92ba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3ffffb6cnhucwja92ba.png" alt="saga-rollback" width="800" height="761"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The action of TransIn branch did nothing and returned a failure. Will compensation of TransIn branch cause the reverse adjustment to go wrong?&lt;/p&gt;

&lt;p&gt;Don't worry, the preceding sub-transaction barrier technique ensures that the TransIn failure is compensated as a null operation if error occurs before the commit, and  is compensated to do opposite adjustment if error occurs after the commit&lt;/p&gt;

&lt;p&gt;You can change the TransIn that returns an error after commit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BusiAPI&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/SagaBTransIn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtmutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WrapHandler2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;barrier&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;MustBarrierFromGin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;barrier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txGet&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;SagaAdjustBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TransInUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dtmcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrFailure&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final result balance will still be right, see &lt;a href="https://en.dtm.pub/practice/barrier" rel="noopener noreferrer"&gt;Exceptions and Solutions&lt;/a&gt; for details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This article gives a complete SAGA transaction solution, a working SAGA, which you can use to solve your real problems with a few simple modifications to this example&lt;/p&gt;

&lt;p&gt;A detail description of SAGA can be found here &lt;a href="https://en.dtm.pub/practice/saga.html" rel="noopener noreferrer"&gt;SAGA&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You are welcomed to visit &lt;a href="https://github.com/dtm-labs/dtm" rel="noopener noreferrer"&gt;https://github.com/dtm-labs/dtm&lt;/a&gt;&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>database</category>
      <category>distributedsystems</category>
      <category>go</category>
    </item>
    <item>
      <title>Best Practice for TCC Distributed Transaction In Go</title>
      <dc:creator>yedf2</dc:creator>
      <pubDate>Fri, 01 Apr 2022 13:15:11 +0000</pubDate>
      <link>https://dev.to/yedf2/best-practice-for-tcc-distributed-transaction-in-go-402m</link>
      <guid>https://dev.to/yedf2/best-practice-for-tcc-distributed-transaction-in-go-402m</guid>
      <description>&lt;p&gt;This article will present a complete TCC example to give the reader an accurate understanding of TCC-type transactions&lt;/p&gt;

&lt;h2&gt;
  
  
  Business Scenario
&lt;/h2&gt;

&lt;p&gt;A typical distributed transaction scenario is inter-bank transfers, where A needs to transfer funds across a bank to B. The hypothetical requirement scenario is that both transfers out of A and into B may succeed and fail, and that both transfers in and out will eventually succeed or fail.&lt;/p&gt;

&lt;p&gt;There is also a requirement that if there is a rollback, the SAGA mode will result in A discovering that its balance has been deducted, but the recipient, B, is late in receiving the balance, which will cause A great distress. The business would prefer to avoid this situation&lt;/p&gt;

&lt;h2&gt;
  
  
  TCC Components
&lt;/h2&gt;

&lt;p&gt;The TCC is divided into 3 part&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try part: attempts to execute, completes all business checks (consistency), reserve necessary business resources.&lt;/li&gt;
&lt;li&gt;Confirm part: if all branches succeed in the Try phase, then we move to the Confirm phase, where Confirm actually executes the business without any business checks, using only the business resources reserved in the Try phase&lt;/li&gt;
&lt;li&gt;Cancel part: If one of the Trys in all branches fails, we go to the Cancel phase, which releases the business resources reserved in the Try phase.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we were to perform a transaction similar to a bank interbank transfer, with TransOut and TransIn in separate micro-services, a typical timing diagram for a successfully completed TCC transaction would be as follows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffint6pobjeqsc0ou7y0t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffint6pobjeqsc0ou7y0t.png" alt="tcc-normal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our solution is based on distributed transaction framework &lt;a href="https://github.com/dtm-labs/dtm" rel="noopener noreferrer"&gt;dtm&lt;/a&gt;, an excellent project dedicated to distributed transaction solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Operations
&lt;/h2&gt;

&lt;p&gt;First we create the account balance table, where trading_balance indicates the amount that has been frozen.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

create table if not exists dtm_busi.user_account(
  id int(11) PRIMARY KEY AUTO_INCREMENT,
  user_id int(11) UNIQUE,
  balance DECIMAL(10, 2) not null default '0',
  trading_balance DECIMAL(10, 2) not null default '0',
  create_time datetime DEFAULT now(),
  update_time datetime DEFAULT now(),
  key(create_time),
  key(update_time)
);


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's write the core code first, the freeze/unfreeze funds operation will check the constraint balance+trading_balance &amp;gt;= 0, if the constraint is not valid, the execution fails&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 go
func tccAdjustTrading(db dtmcli.DB, uid int, amount int) error {
    affected, err := dtmimp.DBExec(db, "update dtm_busi.user_account set trading_balance=trading_balance+?       where user_id=? and trading_balance + ? + balance &amp;gt;= 0", amount, uid, amount)
    if err == nil &amp;amp;&amp;amp; affected == 0 {
        return fmt.Errorf("update error, maybe balance not enough")
    }
    return err
}

func tccAdjustBalance(db dtmcli.DB, uid int, amount int) error {
    affected, err := dtmimp.DBExec(db, "update dtm_busi.user_account set trading_balance=trading_balance-?,          balance=balance+? where user_id=? ", amount, amount, uid)
    if err == nil &amp;amp;&amp;amp; affected == 0 {
        return fmt.Errorf("update user_account 0 rows")
    }
    return err
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's write the specific Try/Confirm/Cancel handler functions&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 go
app.POST(BusiAPI+"/TccBTransOutTry", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
  bb := MustBarrierFromGin(c)
  return bb.Call(txGet(), func(tx *sql.Tx) error {
    return tccAdjustTrading(tx, TransOutUID, -req.Amount)
  })
}))
app.POST(BusiAPI+"/TccBTransOutConfirm", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
  bb := MustBarrierFromGin(c)
  return bb.Call(txGet(), func(tx *sql.Tx) error {
    return tccAdjustBalance(tx, TransOutUID, -reqFrom(c).Amount)
  })
}))
app.POST(BusiAPI+"/TccBTransOutCancel", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
  bb := MustBarrierFromGin(c)
  return bb.Call(txGet(), func(tx *sql.Tx) error {
    return tccAdjustTrading(tx, TransOutUID, req.Amount)
  })
}))
app.POST(BusiAPI+"/TccBTransInTry", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
  bb := MustBarrierFromGin(c)
  return bb.Call(txGet(), func(tx *sql.Tx) error {
    return tccAdjustTrading(tx, TransInUID, req.Amount)
  })
}))
app.POST(BusiAPI+"/TccBTransOutConfirm", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
  bb := MustBarrierFromGin(c)
  return bb.Call(txGet(), func(tx *sql.Tx) error {
    return tccAdjustBalance(tx, TransInUID, reqFrom(c).Amount)
  })
}))
app.POST(BusiAPI+"/TccBTransInCancel", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
  bb := MustBarrierFromGin(c)
  return bb.Call(txGet(), func(tx *sql.Tx) error {
    return tccAdjustTrading(tx, TransInUID, -req.Amount)
  })
}))


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The core logic of these functions is to freeze and adjust the balance, the role of &lt;code&gt;bb.Call&lt;/code&gt; in this will be explained in detail later&lt;/p&gt;

&lt;h2&gt;
  
  
  TCC transactions
&lt;/h2&gt;

&lt;p&gt;Then the TCC transaction is created and branch calls are made&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 go
// TccGlobalTransaction will open a global transaction
_, err := dtmcli.TccGlobalTransaction(DtmServer, func(tcc *dtmcli.Tcc) (rerr error) {
  // CallBranch will register the Confirm/Cancel of the transaction branch to the global transaction, and then call Try directly
  res1, rerr := tcc.CallBranch(&amp;amp;TransReq{Amount: 30}, host+"/api/TccBTransOutTry", host+"/api/TccBTransOutConfirm", host+"/api/ TccBTransOutCancel"
  if err ! = nil {
    return resp, err
  }
  return tcc.CallBranch(&amp;amp;TransReq{Amount: 30}, host+"/api/TccBTransInTry", host+"/api/TccBTransInConfirm", host+"/api/TccBTransInCancel")
})


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;At this point, a complete TCC distributed transaction is finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run
&lt;/h2&gt;

&lt;p&gt;If you want to run a successful example entirety, the steps are as follows.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;run dtm
```

bash
git clone &lt;a href="https://github.com/dtm-labs/dtm" rel="noopener noreferrer"&gt;https://github.com/dtm-labs/dtm&lt;/a&gt; &amp;amp;&amp;amp; cd dtm
go run main.go&lt;/li&gt;
&lt;/ol&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
2. Run the example

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 bash&lt;br&gt;
git clone &lt;a href="https://github.com/dtm-labs/dtm-examples" rel="noopener noreferrer"&gt;https://github.com/dtm-labs/dtm-examples&lt;/a&gt; &amp;amp;&amp;amp; cd dtm-examples&lt;br&gt;
go run main.go http_tcc_barrier&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
## Handling network exceptions

Suppose a transaction committed to dtm fails briefly at one of these steps. dtm will retry the incomplete operation, requiring the subtransactions of the global transaction to be idempotent. dtm framework pioneered the subtransaction barrier technique, providing the BranchBarrier utility class to help users handle idempotency easily. It provides a function Call which guarantees that the operation inside this function will be called at most once:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 go&lt;br&gt;
func (bb *BranchBarrier) Call(tx *sql.Tx, busiCall BarrierBusiFunc) error&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
This BranchBarrier can automatically handle not only idempotency, but also null-compensation and hanging issues, see [exceptions and solutions](https://en.dtm.pub/practice/barrier) for details.

### Rollback of TCC
What happens if the bank is preparing to transfer the amount to user 2 and finds that user 2's account is abnormal and returns a failure? We modify the code to simulate this situation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 go&lt;br&gt;
app.POST(BusiAPI+"/TccBTransInTry", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {&lt;br&gt;
  return dtmcli.ErrFailure&lt;br&gt;
}))&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is the timing diagram for a transaction failure interaction
![tcc-rollback](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8e10v4r8lrr0gx1sxjig.png)


The difference between this and a successful TCC is that when a child transaction returns a failure, the global transaction is subsequently rolled back, calling the Cancel operation of each child transaction to ensure that the global transaction is all rolled back.

The forward operation of TransInTry returned a failure without doing anything, at this point calling the TransInCancel compensation operation will cause the reverse adjustment to go wrong?

Don't worry, the preceding subtransaction barrier technique ensures that the TransInTry error is compensated as a null operation if it occurs before the commit, and that the compensation operation commits the data if the TransInTry error occurs after the commit.

You can change `TccBTransInTry` to
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 go&lt;br&gt;
app.POST(BusiAPI+"/TccBTransInTry", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {&lt;br&gt;
  bb := MustBarrierFromGin(c)&lt;br&gt;
  bb.Call(txGet(), func(tx *sql.Tx) error {&lt;br&gt;
    return tccAdjustTrading(tx, TransInUID, req.Amount)&lt;br&gt;
  })&lt;br&gt;
  return dtmcli.ErrFailure&lt;br&gt;
}))&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
The final result balance will still be right, see [Exceptions and Solutions](https://en.dtm.pub/practice/barrier) for details.

### Summary

This article gives a complete TCC transaction solution. You can use it to solve your real problems with a few simple modifications to this example

For more information on the principles of TCC, see [TCC](https://en.dtm.pub/practice/tcc)

Welcome to visit the [https://github.com/dtm-labs/dtm](https://github.com/dtm-labs/dtm) project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>go</category>
      <category>database</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
