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

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

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

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

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

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

using System;
using Wing.Saga.Client;

namespace Saga.Bank.ABC.TransferSagaUnits
{
    [Serializable]
    public class MyAccountUnitModel : UnitModel
    {
        /// <summary>
        /// 账号
        /// </summary>
        public string BankNo { get; set; }

        /// <summary>
        /// 转出金额
        /// </summary>
        public double Amount { get; set; }
    }
}

事务单元的实现类(MyAccountSagaUnit)

using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Wing.Saga.Client;

namespace Saga.Bank.ABC.TransferSagaUnits
{
    /// <summary>
    /// 当前账户操作
    /// </summary>
    public class MyAccountSagaUnit : SagaUnit<MyAccountUnitModel>
    {
        public override Task<SagaResult> Cancel(MyAccountUnitModel model, SagaResult previousResult)
        {
            MyAccount.Balance += model.Amount;
            return Task.FromResult(new SagaResult());
        }

        public override Task<SagaResult> Commit(MyAccountUnitModel model, SagaResult previousResult)
        {
            var result = new SagaResult();
            if (MyAccount.Balance < model.Amount)
            {
                result.Success = false;
                result.Msg = "转账失败,当前账户余额不足!";
            }
            MyAccount.Balance -= model.Amount;
            return Task.FromResult(result);
        }
    }
}

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

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

using System;
using Wing.Saga.Client;

namespace Saga.Bank.ABC.TransferSagaUnits
{
    [Serializable]
    public class TransferOutUnitModel : UnitModel
    {
        /// <summary>
        /// 收款账号
        /// </summary>
        public string BankNo { get; set; }

        /// <summary>
        /// 收款行
        /// </summary>
        public string BankName { get; set; }

        /// <summary>
        /// 接收金额
        /// </summary>
        public double Amount { get; set; }
    }
}

事务单元的实现类(TransferOutSagaUnit)

using System.Net.Http;
using System;
using System.Threading.Tasks;
using Wing;
using Wing.Saga.Client;
using Wing.ServiceProvider;
using Newtonsoft.Json;
using Wing.Result;
using System.Text;

namespace Saga.Bank.ABC.TransferSagaUnits
{
    /// <summary>
    /// 账户转出操作
    /// </summary>
    public class TransferOutSagaUnit : SagaUnit<TransferOutUnitModel>
    {
        private readonly IServiceFactory _serviceFactory = App.GetService<IServiceFactory>();
        private readonly IHttpClientFactory _httpClientFactory = App.GetService<IHttpClientFactory>();

        public override Task<SagaResult> Cancel(TransferOutUnitModel model, SagaResult previousResult)
        {
            throw new NotImplementedException();
        }

        public override Task<SagaResult> Commit(TransferOutUnitModel model, SagaResult previousResult)
        {
            return _serviceFactory.InvokeAsync("Saga.Bank.ICBC", async serviceAddr =>
            {
                var client = _httpClientFactory.CreateClient();
                client.BaseAddress = new Uri(serviceAddr.ToString());
                var response = await client.PostAsync("/TransferReceive", new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json"));
                var sagaResult = new SagaResult();
                if (response.IsSuccessStatusCode)
                {
                    var apiStrResult = await response.Content.ReadAsStringAsync();
                    var apiResult = JsonConvert.DeserializeObject<ApiResult<bool>>(apiStrResult);
                    if (apiResult.Code == ResultType.Success)
                    {
                        sagaResult.Success = apiResult.Data;
                    }
                    else
                    {
                        sagaResult.Success = false;
                    }
                    sagaResult.Msg = apiResult.Msg;
                }
                else
                {
                    sagaResult.Success= false;
                    sagaResult.Msg = $"调用工商银行接口失败,http状态码:{(int)response.StatusCode}";
                }
                return sagaResult;
            });
        }
    }
}

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

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Saga.Bank.ABC.TransferSagaUnits;
using System;
using Wing.Persistence.Saga;
using Wing.Saga.Client;

namespace Saga.Bank.ABC.Controllers
{
    /// <summary>
    /// 转账
    /// </summary>
    [ApiController]
    [Route("[controller]")]
    public class TransferAccountsController : ControllerBase
    {
        public TransferAccountsController()
        {
        }

        /// <summary>
        /// 当前账户余额
        /// </summary>
        /// <returns></returns>
        public string Get()
        {
            return $"我是中国农业银行账户,当前账户余额为:{MyAccount.Balance}¥";
        }


        [HttpGet("{amount}")]
        public bool Get(double amount)
        {
            if (amount <= 0)
            {
                throw new Exception("转账金额必须大于0");
            }
            var result = Wing.Saga.Client.Saga.Start("跨行转账", new SagaOptions { TranPolicy = TranPolicy.Forward })
                   .Then(new MyAccountSagaUnit(), new MyAccountUnitModel
                   {
                       Name = "当前账户扣减",
                       BankNo = MyAccount.BankNo,
                       Amount = 1000
                   })
                  .Then(new TransferOutSagaUnit(), new TransferOutUnitModel
                  {
                      Name = "调用收款行接口",
                      BankNo = "987654321",
                      Amount = 1000,
                      BankName = "中国工商银行"
                  })
                  .End();
            if (!result.Success)
            {
                throw new Exception(result.Msg);
            }
            return result.Success;
        }
    }
}

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

using Microsoft.AspNetCore.Mvc;
using Saga.Bank.ICBC.Models;
using System;

namespace Saga.Bank.ICBC.Controllers
{
    /// <summary>
    /// 转账
    /// </summary>
    [ApiController]
    [Route("[controller]")]
    public class TransferReceiveController : ControllerBase
    {
        private static bool _result = false;
        public TransferReceiveController()
        {
        }

        /// <summary>
        /// 当前账户余额
        /// </summary>
        /// <returns></returns>
        public string Get()
        {
            return $"我是中国工商银行账户,当前账户余额为:{MyAccount.Balance}¥";
        }

        /// <summary>
        /// 手动控制跨行转账收款是否成功,测试需要
        /// </summary>
        /// <param name="result"></param>
        /// <returns></returns>\
        [HttpGet("{result}")]
        public bool Get(int result)
        {
            _result = result == 1;
            return _result;
        }

        /// <summary>
        /// 跨行转账收款动作
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        [HttpPost]
        public bool Post(ReceivedModel model)
        {
            if (model.BankNo != MyAccount.BankNo)
            {
                throw new Exception("账号不存在!");
            }
            if (!_result)
            {
                throw new Exception("跨行转账业务失败!");
            }
            MyAccount.Balance += model.Amount;
            return true;
        }
    }
}

启动“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元,“跨行转账”事务也处理完成了,如下图:

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐