经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
.NET微服务系列之Saga分布式事务案例实践
来源:cnblogs  作者:linguicheng  时间:2023/10/11 16:12:25  对本文有异议

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

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

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

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

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

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

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

事务单元的实现类(MyAccountSagaUnit)

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

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

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

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

事务单元的实现类(TransferOutSagaUnit)

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

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

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

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

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

原文链接:https://www.cnblogs.com/linguicheng/p/17728458.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号