自从Wing正式发布以后,很多童鞋反馈对Saga分布式事务比较感兴趣,今天就跟大家分享一下“跨行转账”的分布式事务实践案例,入门使用教程请自行前往Wing官方文档

假设自己名下有“中国农业银行(ABC)”和“中国工商银行(ICBC)”的账户余额各1万元,现在从“ABC”跨行转账1000元到“ICBC”。对于“ABC”我们创建一个项目名称为

“Saga.Bank.ABC”,“跨行转账”这个动作我们分为两个事务单元来处理:

1、当前账户扣减1000元,定义一个事务单元的数据传输模型(MyAccountUnitModel),一个事务单元的实现类(MyAccountSagaUnit),如果我们定义的事务策略是“向前恢复”,那就只需要实现“Commit”

方法,否则还需要实现 “Cancel”方法,代码如下:

事务单元的数据传输模型(MyAccountUnitModel)

 1 using System;
2 using Wing.Saga.Client;
3
4 namespace Saga.Bank.ABC.TransferSagaUnits
5 {
6 [Serializable]
7 public class MyAccountUnitModel : UnitModel
8 {
9 /// <summary>
10 /// 账号
11 /// </summary>
12 public string BankNo { get; set; }
13
14 /// <summary>
15 /// 转出金额
16 /// </summary>
17 public double Amount { get; set; }
18 }
19 }

事务单元的实现类(MyAccountSagaUnit)

 1 using Microsoft.AspNetCore.Mvc;
2 using System.Threading.Tasks;
3 using Wing.Saga.Client;
4
5 namespace Saga.Bank.ABC.TransferSagaUnits
6 {
7 /// <summary>
8 /// 当前账户操作
9 /// </summary>
10 public class MyAccountSagaUnit : SagaUnit<MyAccountUnitModel>
11 {
12 public override Task<SagaResult> Cancel(MyAccountUnitModel model, SagaResult previousResult)
13 {
14 MyAccount.Balance += model.Amount;
15 return Task.FromResult(new SagaResult());
16 }
17
18 public override Task<SagaResult> Commit(MyAccountUnitModel model, SagaResult previousResult)
19 {
20 var result = new SagaResult();
21 if (MyAccount.Balance < model.Amount)
22 {
23 result.Success = false;
24 result.Msg = "转账失败,当前账户余额不足!";
25 }
26 MyAccount.Balance -= model.Amount;
27 return Task.FromResult(result);
28 }
29 }
30 }

2、调用收款行“ICBC”的接口,同样,也是定义一个事务单元的数据传输模型(TransferOutUnitModel),一个事务单元的实现类(TransferOutSagaUnit),代码如下:

事务单元的数据传输模型(TransferOutUnitModel)

 1 using System;
2 using Wing.Saga.Client;
3
4 namespace Saga.Bank.ABC.TransferSagaUnits
5 {
6 [Serializable]
7 public class TransferOutUnitModel : UnitModel
8 {
9 /// <summary>
10 /// 收款账号
11 /// </summary>
12 public string BankNo { get; set; }
13
14 /// <summary>
15 /// 收款行
16 /// </summary>
17 public string BankName { get; set; }
18
19 /// <summary>
20 /// 接收金额
21 /// </summary>
22 public double Amount { get; set; }
23 }
24 }

事务单元的实现类(TransferOutSagaUnit)

 1 using System.Net.Http;
2 using System;
3 using System.Threading.Tasks;
4 using Wing;
5 using Wing.Saga.Client;
6 using Wing.ServiceProvider;
7 using Newtonsoft.Json;
8 using Wing.Result;
9 using System.Text;
10
11 namespace Saga.Bank.ABC.TransferSagaUnits
12 {
13 /// <summary>
14 /// 账户转出操作
15 /// </summary>
16 public class TransferOutSagaUnit : SagaUnit<TransferOutUnitModel>
17 {
18 private readonly IServiceFactory _serviceFactory = App.GetService<IServiceFactory>();
19 private readonly IHttpClientFactory _httpClientFactory = App.GetService<IHttpClientFactory>();
20
21 public override Task<SagaResult> Cancel(TransferOutUnitModel model, SagaResult previousResult)
22 {
23 throw new NotImplementedException();
24 }
25
26 public override Task<SagaResult> Commit(TransferOutUnitModel model, SagaResult previousResult)
27 {
28 return _serviceFactory.InvokeAsync("Saga.Bank.ICBC", async serviceAddr =>
29 {
30 var client = _httpClientFactory.CreateClient();
31 client.BaseAddress = new Uri(serviceAddr.ToString());
32 var response = await client.PostAsync("/TransferReceive", new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json"));
33 var sagaResult = new SagaResult();
34 if (response.IsSuccessStatusCode)
35 {
36 var apiStrResult = await response.Content.ReadAsStringAsync();
37 var apiResult = JsonConvert.DeserializeObject<ApiResult<bool>>(apiStrResult);
38 if (apiResult.Code == ResultType.Success)
39 {
40 sagaResult.Success = apiResult.Data;
41 }
42 else
43 {
44 sagaResult.Success = false;
45 }
46 sagaResult.Msg = apiResult.Msg;
47 }
48 else
49 {
50 sagaResult.Success= false;
51 sagaResult.Msg = $"调用工商银行接口失败,http状态码:{(int)response.StatusCode}";
52 }
53 return sagaResult;
54 });
55 }
56 }
57 }

以上两个事务单元将组成一个完整的“跨行转账”事务,代码如下:

 1 using Microsoft.AspNetCore.Mvc;
2 using Microsoft.AspNetCore.Routing;
3 using Saga.Bank.ABC.TransferSagaUnits;
4 using System;
5 using Wing.Persistence.Saga;
6 using Wing.Saga.Client;
7
8 namespace Saga.Bank.ABC.Controllers
9 {
10 /// <summary>
11 /// 转账
12 /// </summary>
13 [ApiController]
14 [Route("[controller]")]
15 public class TransferAccountsController : ControllerBase
16 {
17 public TransferAccountsController()
18 {
19 }
20
21 /// <summary>
22 /// 当前账户余额
23 /// </summary>
24 /// <returns></returns>
25 public string Get()
26 {
27 return $"我是中国农业银行账户,当前账户余额为:{MyAccount.Balance}¥";
28 }
29
30
31 [HttpGet("{amount}")]
32 public bool Get(double amount)
33 {
34 if (amount <= 0)
35 {
36 throw new Exception("转账金额必须大于0");
37 }
38 var result = Wing.Saga.Client.Saga.Start("跨行转账", new SagaOptions { TranPolicy = TranPolicy.Forward })
39 .Then(new MyAccountSagaUnit(), new MyAccountUnitModel
40 {
41 Name = "当前账户扣减",
42 BankNo = MyAccount.BankNo,
43 Amount = 1000
44 })
45 .Then(new TransferOutSagaUnit(), new TransferOutUnitModel
46 {
47 Name = "调用收款行接口",
48 BankNo = "987654321",
49 Amount = 1000,
50 BankName = "中国工商银行"
51 })
52 .End();
53 if (!result.Success)
54 {
55 throw new Exception(result.Msg);
56 }
57 return result.Success;
58 }
59 }
60 }

对于“ICBC”,我们创建一个项目名称为“Saga.Bank.ICBC”,它的职责很简单,就是增加收款账号的转账金额,代码如下:

 1 using Microsoft.AspNetCore.Mvc;
2 using Saga.Bank.ICBC.Models;
3 using System;
4
5 namespace Saga.Bank.ICBC.Controllers
6 {
7 /// <summary>
8 /// 转账
9 /// </summary>
10 [ApiController]
11 [Route("[controller]")]
12 public class TransferReceiveController : ControllerBase
13 {
14 private static bool _result = false;
15 public TransferReceiveController()
16 {
17 }
18
19 /// <summary>
20 /// 当前账户余额
21 /// </summary>
22 /// <returns></returns>
23 public string Get()
24 {
25 return $"我是中国工商银行账户,当前账户余额为:{MyAccount.Balance}¥";
26 }
27
28 /// <summary>
29 /// 手动控制跨行转账收款是否成功,测试需要
30 /// </summary>
31 /// <param name="result"></param>
32 /// <returns></returns>\
33 [HttpGet("{result}")]
34 public bool Get(int result)
35 {
36 _result = result == 1;
37 return _result;
38 }
39
40 /// <summary>
41 /// 跨行转账收款动作
42 /// </summary>
43 /// <param name="model"></param>
44 /// <returns></returns>
45 /// <exception cref="Exception"></exception>
46 [HttpPost]
47 public bool Post(ReceivedModel model)
48 {
49 if (model.BankNo != MyAccount.BankNo)
50 {
51 throw new Exception("账号不存在!");
52 }
53 if (!_result)
54 {
55 throw new Exception("跨行转账业务失败!");
56 }
57 MyAccount.Balance += model.Amount;
58 return true;
59 }
60 }
61 }

启动“Saga.Bank.ICBC”项目,可以看到当前账户余额为10000元,如下图:

启动“Saga.Bank.ABC”项目,可以看到当前账户余额也是为10000元,如下图:

启动Saga协调服务“Saga.Bank.Server”,启动“Wing.UI”示例1.3,  我们调用农业银行跨行转账接口 http://localhost:9110/TransferAccounts/1000,这时我们可以看到“ABC”的余额为

9000元,“ICBC”的余额还是10000元,因为“ICBC”自身业务操作处理失败,如下图所示:

我们把“Saga.Bank.ICBC”的收款接口处理结果改为“成功”(调用接口 http://localhost:9111/TransferReceive/1),1分钟左右,我们重新查看“ICBC”的账户余额为11000元,“跨行转账”事务也处理完成了,如下图:

代码完整示例下载地址:https://gitee.com/linguicheng/wing-demo

.NET微服务系列之Saga分布式事务案例实践的更多相关文章

  1. Microservices 微服务概念和优点 自治 弹性 级联故障 微服务的问题 CAP 分布式事务 修改一个服务并对其部署而不影响其他任务服务

    https://en.wikipedia.org/wiki/Microservices https://zh.wikipedia.org/wiki/微服務 微服務 (Microservices) 是一 ...

  2. 微服务系列之分布式日志 ELK

    1.ELK简介 ELK是ElasticSearch+LogStash+Kibana的缩写,是现代微服务架构流行的分布式日志解决方案,旨在大规模服务的日志集中管理查看,极大的为微服务开发人员提供了排查生 ...

  3. 【转】「Chris Richardson 微服务系列」微服务架构的优势与不足

    Posted on 2016年5月4日 编者的话|本文来自 Nginx 官方博客,是微服务系列文章的第一篇,主要探讨了传统的单体式应用的不足,以及微服务架构的优势与挑战. 作者介绍:Chris Ric ...

  4. 【CHRIS RICHARDSON 微服务系列】事件驱动的数据管理-5

    编者的话 |本文来自 Nginx 官方博客,是「Chris Richardson 微服务」系列的第五篇文章.第一篇文章介绍了微服务架构模式,并且讨论了使用微服务的优缺点:第二和第三篇描述了微服务架构模 ...

  5. 带你十天轻松搞定 Go 微服务系列(一)

    本文开始,我们会出一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下: 环境搭建(本文) 服务拆分 用户服务 产品服务 订单服务 支付服务 RPC 服务 Au ...

  6. 带你十天轻松搞定 Go 微服务系列(二)

    上篇文章开始,我们通过一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下: 环境搭建 服务拆分(本文) 用户服务 产品服务 订单服务 支付服务 RPC 服务 ...

  7. 聊一聊如何用C#轻松完成一个SAGA分布式事务

    背景 银行跨行转账业务是一个典型分布式事务场景,假设 A 需要跨行转账给 B,那么就涉及两个银行的数据,无法通过一个数据库的本地事务保证转账的 ACID ,只能够通过分布式事务来解决. 市面上使用比较 ...

  8. 带你十天轻松搞定 Go 微服务系列(三)

    序言 我们通过一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下: 环境搭建 服务拆分 用户服务(本文) 产品服务 订单服务 支付服务 RPC 服务 Auth ...

  9. 带你十天轻松搞定 Go 微服务系列(五)

    序言 我们通过一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下: 环境搭建 服务拆分 用户服务 产品服务 订单服务(本文) 支付服务 RPC 服务 Auth ...

  10. 带你十天轻松搞定 Go 微服务系列(六)

    序言 我们通过一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下: 环境搭建 服务拆分 用户服务 产品服务 订单服务 支付服务(本文) RPC 服务 Auth ...

随机推荐

  1. 前端树形结构图组件 tree组件,可拖拽移动,点击展开收缩,无限添加子集

    快速实现树形结构图组件 tree组件,可拖拽移动,点击展开收缩,无限添加子集; 下载完整代码请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=1 ...

  2. 如何通过AWS的AmazonSageMaker进行机器学习

    目录 <如何通过 AWS 的 Amazon SageMaker 进行机器学习> 一.引言 随着人工智能和机器学习的发展,越来越多的企业和机构开始使用这些技术来进行各种应用场景的处理和分析. ...

  3. 图书商城Vue+Element+Node项目练习(...)

    本系列文章是为学习Vue的项目练习笔记,尽量详细记录一下一个完整项目的开发过程.面向初学者,本人也是初学者,搬砖技术还不成熟.项目在技术上前端为主,包含一些后端代码,从基础的数据库(Sqlite).到 ...

  4. 一行命令使用 Docker 编译 Latex 文件,简单优雅

    使用 Docker 编译 LaTeX 文章 LaTeX 是一种常用的排版系统,它可以帮助用户创建漂亮.专业的文档.但是,安装和配置 LaTeX 比较麻烦,特别是对于初学者而言. Docker 是一个开 ...

  5. CF1829H Don't Blame Me题解

    题意: 给定一个长度为 \(n\) 的数组,选择它的一个子序列(不一定要连续的),问有多少种选法使得它们 AND 的值的二进制表示法中有 \(k\) 个 \(1\). 思路: 这个题就是一个简单的 D ...

  6. AcWing 4486. 数字操作题解

    题目描述 给定一个整数 \(n\),你可以对该数进行任意次(可以是 \(0\) 次)变换操作. 每次操作为以下两种之一: 将整数 \(n\) 乘以任意一个正整数 \(x\). 将整数 \(n\) 替换 ...

  7. Truncate 和 Delete 的区别与选择

    1)事务和日志 delete   语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行回滚操作. truncate table  则 一次性地从表中删除所有 ...

  8. java读取txt文件解决乱码问题

    说明:由于txt文件有bom和不同的编码方式,导致导入数据时产生乱码,以下代码完美解决乱码问题. 参考他人代码,结合自己的业务加工完成,费了大半天功夫完成,希望对大家有点用处. 废话不多说,直接上代码 ...

  9. cesium中限制地图浏览范围

    https://blog.csdn.net/qq_42740164/article/details/119375782?ops_request_misc=%257B%2522request%255Fi ...

  10. win10安装mysql5.7.35教程

    前提条件:我下载的是压缩包版本5.7.35,下载地址是 https://downloads.mysql.com/archives/community/ 下载完后解压,并在如下图目录里加入data文件夹 ...