.NET微服务系列之Saga分布式事务案例实践
自從Wing正式發(fā)布以后,很多童鞋反饋對Saga分布式事務比較感興趣,今天就跟大家分享一下“跨行轉賬”的分布式事務實踐案例,入門使用教程請自行前往Wing官方文檔。
假設自己名下有“中國農(nóng)業(yè)銀行(ABC)”和“中國工商銀行(ICBC)”的賬戶余額各1萬元,現(xiàn)在從“ABC”跨行轉賬1000元到“ICBC”。對于“ABC”我們創(chuàng)建一個項目名稱為
“Saga.Bank.ABC”,“跨行轉賬”這個動作我們分為兩個事務單元來處理:
1、當前賬戶扣減1000元,定義一個事務單元的數(shù)據(jù)傳輸模型(MyAccountUnitModel),一個事務單元的實現(xiàn)類(MyAccountSagaUnit),如果我們定義的事務策略是“向前恢復”,那就只需要實現(xiàn)“Commit”
方法,否則還需要實現(xiàn) “Cancel”方法,代碼如下:
事務單元的數(shù)據(jù)傳輸模型(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 }
事務單元的實現(xiàn)類(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”的接口,同樣,也是定義一個事務單元的數(shù)據(jù)傳輸模型(TransferOutUnitModel),一個事務單元的實現(xiàn)類(TransferOutSagaUnit),代碼如下:
事務單元的數(shù)據(jù)傳輸模型(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 }
事務單元的實現(xiàn)類(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狀態(tài)碼:{(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 $"我是中國農(nóng)業(yè)銀行賬戶,當前賬戶余額為:{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”,我們創(chuàng)建一個項目名稱為“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("跨行轉賬業(yè)務失敗!");
56 }
57 MyAccount.Balance += model.Amount;
58 return true;
59 }
60 }
61 }
啟動“Saga.Bank.ICBC”項目,可以看到當前賬戶余額為10000元,如下圖:
啟動“Saga.Bank.ABC”項目,可以看到當前賬戶余額也是為10000元,如下圖:
啟動Saga協(xié)調服務“Saga.Bank.Server”,啟動“Wing.UI”示例1.3, 我們調用農(nóng)業(yè)銀行跨行轉賬接口 http://localhost:9110/TransferAccounts/1000,這時我們可以看到“ABC”的余額為
9000元,“ICBC”的余額還是10000元,因為“ICBC”自身業(yè)務操作處理失敗,如下圖所示:
我們把“Saga.Bank.ICBC”的收款接口處理結果改為“成功”(調用接口 http://localhost:9111/TransferReceive/1),1分鐘左右,我們重新查看“ICBC”的賬戶余額為11000元,“跨行轉賬”事務也處理完成了,如下圖:
代碼完整示例下載地址:https://gitee.com/linguicheng/wing-demo
總結
以上是生活随笔為你收集整理的.NET微服务系列之Saga分布式事务案例实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: laravel 导入导出(实际上还是
- 下一篇: (admin.E104) 'XX