银行转账业务场景的几种实现思路对比

简介:

前言

ps: 由于本篇文章是我早期所写,文中的思想已经和我现在的想法完全不同了。目前我所理解的领域模型,是被调用的,应用层使用领域模型,调用领域模型中的各种构造块完成用例场景。另外,关于银行转账,我们还可以使用另一种更好的实现方案,即最终一致性的方案,通过事件驱动的流程的方式来实现转账。具体实现见ENode框架中的BankTransferSample中的代码实现:https://github.com/tangxuehua/enode

这篇文章希望通过一个银行转账的例子来和大家分享一些我最近想到的关于如何组织业务逻辑的心得和体会。当然,本人的能力和领悟有限,如有不正确之处,还希望各位看官能帮我指出来。我始终坚持一个信念,没有讨论就没有进步,任何一个非盈利为目的的人或组织始终应该把自己所学的知识共享出来与人讨论,这样不管对自己或对他人或对整个社会都大有好处。因为一个人的知识毕竟是有限的,但可以(并且也只能)和别人相互沟通交流学习来弥补这个缺陷。

转账过程简单描述

银行转账的核心业务逻辑大家应该都很熟悉了,主要有这么几步:

  1. 源账户扣除转账金额,当然首先需要先判断源账户余额是否足够,如果不够,则无法转账;
  2. 目标账户增加转账金额;
  3. 为源账户生成一笔转账记录;
  4. 为目标账户生成一笔转账记录;
下面让我们来看看各种实现该业务场景的方法,并且来做一个对比。

事务脚本(Transaction Script、贫血模型) 

这种方法的优缺点网上找一下一大堆,我这里也啰嗦列举一些:

  1. 容易理解,符合我们大脑过程化思考的习惯;
  2. 完全没有面向对象的思想,纯粹是面向过程式的一种组织业务逻辑的方式,所有的业务逻辑全部在一个方法中完成;
  3. 对象只包含数据而没有行为,对象只是用来被操作的“数据”,一般我们会设计很多的Item,以及ItemManager;
  4. 结构层次比较清晰,业务逻辑层和其他各层之间单项依赖;业务逻辑层中Item只代表数据,ItemManager则负责所有的业务逻辑实现,ItemManager只依赖于IDAL接口来完成持久化Item或重建Item; 
  5. 由于所有的业务逻辑全部写在一个方法内,如果有另外一个需求也需要类似的业务逻辑,通常我们是写一个新的方法来实现,这样就很容易导致相同的业务逻辑出现在两个方法中,导致可维护性降低;虽然可以用一些重构的技巧或设计模式来解决重用的问题,但这往往需要开发人员具有很高的编码水平,并且往往很多时候因为时间紧迫导致不允许我们花很多时间去重构;
  6. 如果业务逻辑一旦改变,我们必须去修改实现该业务逻辑的方法,并且如果该业务逻辑在多个方法中出现,我们必须同时修改多个方法;

演示代码:

复制代码
 1       public   class  BankAccountManager
 2      {
 3           private  IBankAccountDAL bankAccountDAL;
 4 
 5           public  BankAccountManager(IBankAccountDAL bankAccountDAL)
 6          {
 7               this .bankAccountDAL  =  bankAccountDAL;
 8          }
 9 
10           ///   <summary>
11           ///  该方法完成转账业务逻辑
12           ///   </summary>
13           public   void  TransferMoney(Guid fromBankAccountId, Guid toBankAccountId,  double  moneyAmount)
14          {
15              var fromBankAccount  =  bankAccountDAL.GetById(fromBankAccountId);
16              var toBankAccount  =  bankAccountDAL.GetById(toBankAccountId);
17               if  (fromBankAccount.MoneyAmount  <  moneyAmount)
18              {
19                   throw   new  NotSupportedException( " 账户余额不足。 " );
20              }
21              fromBankAccount.MoneyAmount  -=  moneyAmount;
22              toBankAccount.MoneyAmount  +=  moneyAmount;
23 
24              DateTime transferDate  =  DateTime.Now;
25              fromBankAccount.TransferHistories.Add( new  TransferHistory
26              {
27                  FromAccountId  =  fromBankAccountId,
28                  ToAccountId  =  toBankAccountId,
29                  MoneyAmount  =  moneyAmount,
30                  TransferDate  =  transferDate
31              });
32              toBankAccount.TransferHistories.Add( new  TransferHistory
33              {
34                  FromAccountId  =  fromBankAccountId,
35                  ToAccountId  =  toBankAccountId,
36                  MoneyAmount  =  moneyAmount,
37                  TransferDate  =  transferDate
38              });
39          }
40      }
41       ///   <summary>
42       ///  银行帐号
43       ///   </summary>
44       public   class  BankAccount
45      {
46           public  BankAccount() {  this .TransferHistories  =   new  List < TransferHistory > (); }
47           public  Guid Id {  get set ; }
48           public   double  MoneyAmount {  get set ; }
49           public  IList < TransferHistory >  TransferHistories {  get set ; }
50      }
51       ///   <summary>
52       ///  转账记录
53       ///   </summary>
54       public   class  TransferHistory
55      {
56           public  Guid FromAccountId {  get set ; }
57           public  Guid ToAccountId {  get set ; }
58           public   double  MoneyAmount {  get set ; }
59           public  DateTime TransferDate {  get set ; }
60      }
61       public   interface  IBankAccountDAL
62      {
63          BankAccount GetById(Guid bankAccountId);
64      }
复制代码

Evans DDD(充血模型)

这种方法的特点在网上也可以找到很多,但我也有一些其他自己的看法,见红色字体的部分:

  1. 基本是一种基于OO思想的开发方法,对象既有属性也有行为,对象之间通过相互引用和方法调用来完成对象之间的交互;
  2. 由于这是一种OO思想的设计方法,所以各种设计原则和模式都可以被充分利用;
  3. Evans对这种开发方法又作了进一步的完善,提出了:聚合、实体、值对象、服务、工厂、仓储、上下文,等这些概念;这确保我们在基于OO的思想组织业务逻辑时有了很好的指导思想;
  4. 需要特别指出的一点是,真正的Evans的DDD领域模型中的聚合根所内聚的所有值对象应该都是只读的,这一点特别重要。
  5. 基于Evans DDD的CQRS架构。这种架构的主要思想是将命令和查询分离,另一个重要的特点就是事件溯源,意思是领域对象不需要有公共的属性,只需要有行为即可,并且在任何一个行为发生后,都会触发一个事件。然后我们持久化的不是对象的状态,而是引起该对象状态改变的所有的事件。当我们需要重建一个领域对象时,只要先创建一个干净的只有唯一标识的对象,然后把和该对象相关的所有领域事件全部重新执行一遍,这样我们就得到了该对象的最终的状态了。说的简单点,就是我们不保存对象本身,而是只保存该对象的操作历史(或者叫操作日志),当我们需要重建该对象时只要”重演历史“即可。当然,为了避免性能的问题,比如因为一个对象可能会有很多的操作历史,如果每次重建该对象都是从头开始应用每个事件,那效率无疑是非常低的。因此我们使用了快照,快照保存了对象某个时刻的二进制形式(即被序列化过了)的状态。所以通常情况下,当我们要重建一个对象时都是从某个最近的快照开始回溯发生在快照之后的事件。
  6. 不管是Evans的DDD也好,CQRS架构也好,虽然都做到了让领域对象不仅有状态,而且有行为,但我觉得这还不够彻底。因为对象的行为总是“被调用”的,当现在有一个业务逻辑需要调用多个对象的一些行为来完成时,我们往往会一个一个地将对象从仓储中取出来,然后调用它们的方法。虽然Evans提出了领域服务(Service)的概念,并将一个领域对象不能完成的事情交给了领域服务去完成。但领域服务内部还是在一个个的取出对象然后调用它们的方法。这个做法在我看来和凭血模型没有本质区别,还是没有真正做到OO。因为贫血模型的情况下,对象是提供了数据让别人去操作或者说被别人使用;而充血模型的情况下,对象则是提供了数据和行为,但还是让别人去操作或者说被别人使用(数据被别人使用或方法被别人调用都是“被别人操作”的一种被动的方式)。所以从这个意义上来看对象时,我觉得贫血模型和充血模型没有本质区别。

 下面也给出一个实现了银行转账业务逻辑的充血模型实现:

复制代码
 1  ///   <summary>
 2       ///  银行帐号, 它是一个Evans DDD中的实体, 并且是聚合根
 3       ///   </summary>
 4       public   class  BankAccount
 5      {
 6           private  IList < TransferHistory >  transferHistories;
 7 
 8           public  BankAccount() :  this (Guid.NewGuid(), 0D,  new  List < TransferHistory > ()) { }
 9           public  BankAccount(Guid id,  double  moneyAmount, IList < TransferHistory >  transferHistories)
10          {
11               this .Id  =  id;
12               this .MoneyAmount  =  moneyAmount;
13               this .transferHistories  =  transferHistories;
14          }
15           public  Guid Id {  get private   set ; }
16           public   double  MoneyAmount {  get private   set ; }
17           public  IList < TransferHistory >  TransferHistories
18          {
19               get
20              {
21                   return  transferHistories.ToList().AsReadOnly();
22              }
23          }
24 
25           public   void  TransferTo(Guid toBankAccountId,  double  moneyAmount, DateTime transferDate)
26          {
27               if  ( this .MoneyAmount  <  moneyAmount)
28              {
29                   throw   new  NotSupportedException( " 账户余额不足。 " );
30              }
31               this .MoneyAmount  -=  moneyAmount;
32               this .TransferHistories.Add(
33                   new  TransferHistory( this .Id, toBankAccountId, moneyAmount, transferDate));
34          }
35           public   void  TransferFrom(Guid fromBankAccountId,  double  moneyAmount, DateTime transferDate)
36          {
37               this .MoneyAmount  +=  moneyAmount;
38               this .TransferHistories.Add(
39                   new  TransferHistory(fromBankAccountId,  this .Id, moneyAmount, transferDate));
40          }
41      }
42       ///   <summary>
43       ///  转账记录, 它是一个Evans DDD中的值对象
44       ///   </summary>
45       public   class  TransferHistory
46      {
47           public  TransferHistory(Guid fromAccountId,
48                                 Guid toAccountId,
49                                  double  moneyAmount,
50                                 DateTime transferDate)
51          {
52               this .FromAccountId  =  fromAccountId;
53               this .ToAccountId  =  toAccountId;
54               this .MoneyAmount  =  moneyAmount;
55               this .TransferDate  =  transferDate;
56          }
57 
58           public  Guid FromAccountId {  get private   set ; }
59           public  Guid ToAccountId {  get private   set ; }
60           public   double  MoneyAmount {  get private   set ; }
61           public  DateTime TransferDate {  get private   set ; }
62      }
63       ///   <summary>
64       ///  BankAccount聚合根对应的仓储
65       ///   </summary>
66       public   interface  IBankAccountRepository
67      {
68          BankAccount GetBankAccount(Guid bankAccountId);
69      }
70       ///   <summary>
71       ///  转账服务, 它是一个Evans DDD中的领域服务
72       ///   </summary>
73       public   class  BankAccountService
74      {
75           private  IBankAccountRepository bankAccountRepository;
76 
77           public  BankAccountService(IBankAccountRepository bankAccountRepository)
78          {
79               this .bankAccountRepository  =  bankAccountRepository;
80          }
81 
82           ///   <summary>
83           ///  该方法完成转账业务逻辑
84           ///   </summary>
85           public   void  TransferMoney(Guid fromBankAccountId, Guid toBankAccountId,  double  moneyAmount)
86          {
87              var fromBankAccount  =  bankAccountRepository.GetBankAccount(fromBankAccountId);
88              var toBankAccount  =  bankAccountRepository.GetBankAccount(toBankAccountId);
89 
90              DateTime transferDate  =  DateTime.Now;
91              fromBankAccount.TransferTo(toBankAccountId, moneyAmount, transferDate);
92              toBankAccount.TransferFrom(fromBankAccountId, moneyAmount, transferDate);
93          }
94      }
复制代码

基于事件驱动(EDA)的设计

这是一种根据我自己的想法而设计出来的一种设计与实现,但是离我理想中的设计还有一些距离。在我看来,真正理想的组织业务逻辑的方法或者说模型应该是这样的:

  1. 当外界需要领域逻辑的“实现模型”(简称领域模型)做某件事情时,会发出一个命令,这个命令可以理解为一个消息或者是一个事件。消息一旦创建出来后就是只读的,因为消息从某种程度上来说就是历史;
  2. 领域模型中的相关领域对象会主动响应该消息;
  3. 需要特别指出的是:我们不可以自己去获取一些相关的领域对象,然后进一步调用它们的方法而实现响应;而是应该所有可能被用到的领域对象必须好像永远已经存在于内存一样的永远在不停的在等待消息并作出响应。以银行转账作为例子,外界发出一个转账的消息,该消息会包含源帐号唯一标识、目标帐号唯一标识、转账金额这些信息。该消息的目的是希望两个两个银行帐号之间能进行转账。好了,外界要做的仅仅是发出这条消息即可。那么领域模型内部该如何去响应该消息呢?一种方法是将两个银行帐号先取出来,然后调用它们的转账方法(如TransferTo方法和TransferFrom方法)以实现转账的目的,前面的Evans的DDD的例子就是这样实现的。但这样做已经违反了我前面所说的理想的情况了。我的理想要求是,这两个银行帐号对象会像已经存在于内存一样可以直接主动去响应转账的消息,而不是转账的那两个方法(TransferTo方法和TransferFrom方法)被我们自己定义的领域服务所调用。
  4. 更加需要着重强调的是,我始终认为,真正的面向对象编程中的对象应该是一个”活“的具有主观能动性的存在于内存中的客观存在,它们不仅有状态而且还有自主行为。这里需要从两方面来解释:1)对象的状态可以表现出来被别人看到,但是必须是只读的,没有人可以直接去修改一个对象的状态,因为对象是一个在内存中的有主观意识的客观存在,它的状态必须是由它自己的行为导致自己的状态的改变。就好像现实生活中的动物或人一样,我不能强制你做什么事情,一定是我通知你(即发送消息给你),你才会做出响应并改变你自己的状态。2)对象的行为就是对象所具有的某种功能。对象的行为本质上应该是对某个消息的主动响应,这里强调的是主动,就是说对象的行为不可以被别人使用,而只能自己主动的去表现出该行为。另外,行为可以表现出来给别人看到,也可以不表现出来给别人看到。实际上,我们永远都不需要将对象的行为表现出来给别人看到,原因是别人不会去使用该行为的,行为永远只能是对象自己去表现出来。
  5. 领域模型这个生态系统中的各个领域对象在运行过程中如果需要和领域模型之外的东西(如数据持久层)交互,也应该通过消息来进行,因为只有这样才能确保领域对象是一个”活“的具有主观能动性的存在于内存中的客观存在。

以上就是我心目中理想的如何设计对象来实现业务逻辑的方式。我想了很久,要完全实现上面的目标实在是太困难了。但也不是不可能,我按照我的能力,经过不断的设计、编码、测试、重构的反复循环过程。基本上设计出了一个令自己基本满意的基础框架出来,基于该框架,以银行转账为例子,我们可以以如下的方式来实现:

复制代码
  1       public   class  TransferEvent : DomainEvent
  2      {
  3           public  TransferEvent(Guid fromBankAccountId, Guid toBankAccountId,  double  moneyAmount, DateTime transferDate)
  4          {
  5               this .FromBankAccountId  =  fromBankAccountId;
  6               this .ToBankAccountId  =  toBankAccountId;
  7               this .MoneyAmount  =  moneyAmount;
  8               this .TransferDate  =  transferDate;
  9          }
 10           public  Guid FromBankAccountId {  get private   set ; }
 11           public  Guid ToBankAccountId {  get private   set ; }
 12           public   double  MoneyAmount {  get private   set ; }
 13           public  DateTime TransferDate {  get private   set ; }
 14      }
 15       public   class  BankAccount : DomainObject < Guid >
 16      {
 17           #region  Private Variables
 18 
 19           private  List < TransferHistory >  transferHistories;
 20 
 21           #endregion
 22 
 23           #region  Constructors
 24 
 25           public  BankAccount(Guid customerId)
 26              :  this (customerId, 0D,  new  List < TransferHistory > ())
 27          {
 28          }
 29           public  BankAccount(Guid customerId,  double  moneyAmount, IEnumerable < TransferHistory >  transferHistories)
 30              :  base (Guid.NewGuid())
 31          {
 32               this .CustomerId  =  customerId;
 33               this .MoneyAmount  =  moneyAmount;
 34               this .transferHistories  =   new  List < TransferHistory > (transferHistories);
 35          }
 36 
 37           #endregion
 38 
 39           #region  Public Properties
 40 
 41           public  Guid CustomerId {  get private   set ; }
 42          [TrackingProperty]
 43           public  IEnumerable < TransferHistory >  TransferHistories
 44          {
 45               get
 46              {
 47                   return  transferHistories.AsReadOnly();
 48              }
 49          }
 50          [TrackingProperty]
 51           public   double  MoneyAmount {  get private   set ; }
 52 
 53           #endregion
 54 
 55           #region  Event Handlers
 56 
 57           private   void  TransferTo(TransferEvent evnt)
 58          {
 59               if  ( this .Id  ==  evnt.FromBankAccountId)
 60              {
 61                  DecreaseMoney(evnt.MoneyAmount);
 62                  transferHistories.Add(
 63                       new  TransferHistory(
 64                          evnt.FromBankAccountId,
 65                          evnt.ToBankAccountId,
 66                          evnt.MoneyAmount,
 67                          evnt.TransferDate));
 68              }
 69          }
 70           private   void  TransferFrom(TransferEvent evnt)
 71          {
 72               if  ( this .Id  ==  evnt.ToBankAccountId)
 73              {
 74                  IncreaseMoney(evnt.MoneyAmount);
 75                  transferHistories.Add(
 76                       new  TransferHistory(
 77                          evnt.FromBankAccountId,
 78                          evnt.ToBankAccountId,
 79                          evnt.MoneyAmount,
 80                          evnt.TransferDate));
 81              }
 82          }
 83 
 84           #endregion
 85 
 86           #region  Private Methods
 87 
 88           private   void  DecreaseMoney( double  moneyAmount)
 89          {
 90               if  ( this .MoneyAmount  <  moneyAmount)
 91              {
 92                   throw   new  NotSupportedException( " 账户余额不足。 " );
 93              }
 94               this .MoneyAmount  -=  moneyAmount;
 95          }
 96           private   void  IncreaseMoney( double  moneyAmount)
 97          {
 98               this .MoneyAmount  +=  moneyAmount;
 99          }
100 
101           #endregion
102      }
103       public   class  TransferHistory : ValueObject
104      {
105           #region  Constructors
106 
107           public  TransferHistory(Guid fromAccountId,
108                                 Guid toAccountId,
109                                  double  moneyAmount,
110                                 DateTime transferDate)
111          {
112               this .FromAccountId  =  fromAccountId;
113               this .ToAccountId  =  toAccountId;
114               this .MoneyAmount  =  moneyAmount;
115               this .TransferDate  =  transferDate;
116          }
117 
118           #endregion
119 
120           #region  Public Properties
121 
122           public  Guid FromAccountId {  get private   set ; }
123           public  Guid ToAccountId {  get private   set ; }
124           public   double  MoneyAmount {  get private   set ; }
125           public  DateTime TransferDate {  get private   set ; }
126 
127           #endregion
128 
129           #region  Infrastructure
130 
131           protected   override  IEnumerable < object >  GetAtomicValues()
132          {
133               yield   return  FromAccountId;
134               yield   return  ToAccountId;
135               yield   return  MoneyAmount;
136               yield   return  TransferDate;
137          }
138 
139           #endregion
140      }
复制代码

以上代码是转账事件、银行帐号(实体),以及转账记录(值对象)的实现代码,然后我们可以通过如下的方式来触发TransferEvent事件来让银行帐号”自动“响应。

1  EventProcesser.ProcessEvent( new  TransferEvent(bankAccount1.Id, bankAccount2.Id,  1000 , DateTime.Now));

如果不需要增加其他的任何代码就OK了的话,那可就真美了,应该差不多可以实现我上面的目标了。但理想终归是理想,而现实的情况是:

1)领域对象的行为不可能做到别人不去调用它就能自己主动表现出来的地步,毕竟它不是一个真正的”活“的有主观能动性的人或动物;

2)领域对象并没有存在于内存中,而是在数据持久化介质中,如数据库,因此我们必须去把领域对象从数据库取出来;

那么难道我们只能放弃了吗?只能自己去做这两件事情了吗?不是,我们可以告诉基础框架如下一些信息,有了这些信息,基础框架就可以帮助我们完成上面的这两件事情了。

复制代码
 1      RegisterObjectEventMappingItem < TransferEvent, BankAccount > (
 2           new  GetDomainObjectIdEventHandlerInfo < TransferEvent >
 3          {
 4              GetDomainObjectId  =  evnt  =>  evnt.FromBankAccountId,
 5              EventHandlerName  =   " TransferTo "
 6          },
 7           new  GetDomainObjectIdEventHandlerInfo < TransferEvent >
 8          {
 9              GetDomainObjectId  =  evnt  =>  evnt.ToBankAccountId,
10              EventHandlerName  =   " TransferFrom "
11          }
12      );
复制代码

上面的代码的意思是告诉框架1)BankAccount会去响应TransferEvent事件;2)BankAccount对象的唯一标识是从TransferEvent事件中的哪个属性中来的;3)因为这里BankAccount会有两个方法可能会响应TransferEvent事件,所以还指定了响应方法的名字从而可以区分。当然一般情况下,我们是不需要指定方法的名字的,因为大部分情况下一个对象对同一个事件只会有一个响应方法。比如下面的代码列出了很多中常见的事件与响应对象的映射信息:

复制代码
 1       public   class  DomainLayerObjectEventMapping : ObjectEventMapping
 2      {
 3           protected   override   void  InitializeObjectEventMappingItems()
 4          {
 5               // BankAccount Event Mappings.
 6              RegisterObjectEventMappingItem < DepositAccountMoneyEvent, BankAccount > (evnt  =>  evnt.BankAccountId);
 7              RegisterObjectEventMappingItem < WithdrawAccountMoneyEvent, BankAccount > (evnt  =>  evnt.BankAccountId);
 8              RegisterObjectEventMappingItem < TransferEvent, BankAccount > (
 9                   new  GetDomainObjectIdEventHandlerInfo < TransferEvent >
10                  {
11                      GetDomainObjectId  =  evnt  =>  evnt.FromBankAccountId,
12                      EventHandlerName  =   " TransferTo "
13                  },
14                   new  GetDomainObjectIdEventHandlerInfo < TransferEvent >
15                  {
16                      GetDomainObjectId  =  evnt  =>  evnt.ToBankAccountId,
17                      EventHandlerName  =   " TransferFrom "
18                  }
19              );
20 
21               // Topic Event Mappings.
22              RegisterObjectEventMappingItem < DomainObjectAddedEvent < Reply > , Topic > (evnt  =>  evnt.DomainObject.TopicId);
23              RegisterObjectEventMappingItem < DomainObjectRemovedEvent < Reply > , Topic > (evnt  =>  evnt.DomainObject.TopicId);
24 
25               // ForumUser Event Mappings.
26              RegisterObjectEventMappingItem < PreAddDomainObjectEvent < Topic > , ForumUser > (evnt  =>  evnt.DomainObject.CreatedBy);
27              RegisterObjectEventMappingItem < DomainObjectAddedEvent < Topic > , ForumUser > (evnt  =>  evnt.DomainObject.CreatedBy);
28 
29               // Reply Event Mappings.
30              RegisterObjectEventMappingItem < DomainObjectRemovedEvent < Topic > , Reply > (evnt  =>  Repository.Find < Reply > ( new  FindTopicRepliesEvent(evnt.DomainObject.Id)));
31          }
32      }
复制代码

关于这种组织业务逻辑的方法,大家如果有仔细研究的兴趣,可以下载我的框架源代码和聚合演示例子源代码。

好了,大家觉得这三种组织业务逻辑的方法如何呢?很想听听大家的声音。我是一个喜欢思考问题、寻找真理的人,期望能和大家多多交流。 


目录
相关文章
|
2月前
|
安全 API 开发者
转账到支付宝账户接口:一次开发,提升打款效率
转账到支付宝账户接口:一次开发,提升打款效率
39 0
|
5天前
|
消息中间件 供应链 NoSQL
电商订单待支付(思路分析)
电商订单待支付(思路分析)
|
6月前
|
安全 API 双11
【支付宝推荐】批量转账为商家大促期间转账多人保驾护航
为了提升商家在大促期间的转账效率,支付宝推出了一款免费高效的转账多人工具,免费使用!
|
7月前
|
安全 搜索推荐 区块链
DApp交易所兑换底池模式系统开发|案例分析
区块链技术还可以提高Web3.0的网络速度和处理效率
|
11月前
|
缓存 NoSQL 中间件
分布式事务之本地消息表解决方案(跨地区转账实际案例)
分布式事务之本地消息表解决方案(跨地区转账实际案例)
|
12月前
|
存储 消息中间件 JavaScript
支付设计白皮书:支付系统的对账系统设计
支付设计白皮书:支付系统的对账系统设计
|
数据采集 小程序 关系型数据库
零售交易信息处理思路
零售行业的交易信息呈现无序杂乱,再加上数据采集难度大,业务人员繁琐,拉通不齐等问题,处理起来会相对费时,这里是我整理的一部分思路,可能有不全的地方,也欢迎大佬指正。
66 0
|
存储 弹性计算 运维
上云这笔账,你算清了吗?分析数据上云的问题与解法
随着越来越多的数据涌向企业,通过云服务厂商将数据上云已经成为大多数企业的共识。数据为王的时代,企业需要告别传统上云,让企业应用实现云原生。
257 0
|
监控 算法 安全
美团点评智能支付核心交易系统的可用性实践(下)
背景 每个系统都有它最核心的指标。比如在收单领域:进件系统第一重要的是保证入件准确,第二重要的是保证上单效率。清结算系统第一重要的是保证准确打款,第二重要的是保证及时打款。我们负责的系统是美团点评智能支付的核心链路,承担着智能支付100%的流量,内部习惯称为核心交易。因为涉及美团点评所有线下交易商家、用户之间的资金流转,对于核心交易来说:第一重要的是稳定性,第二重要的还是稳定性。
美团点评智能支付核心交易系统的可用性实践(下)
|
消息中间件 XML 缓存
美团点评智能支付核心交易系统的可用性实践(上)
背景 每个系统都有它最核心的指标。比如在收单领域:进件系统第一重要的是保证入件准确,第二重要的是保证上单效率。清结算系统第一重要的是保证准确打款,第二重要的是保证及时打款。我们负责的系统是美团点评智能支付的核心链路,承担着智能支付100%的流量,内部习惯称为核心交易。因为涉及美团点评所有线下交易商家、用户之间的资金流转,对于核心交易来说:第一重要的是稳定性,第二重要的还是稳定性。
美团点评智能支付核心交易系统的可用性实践(上)

热门文章

最新文章