【需求】

在开发过程中遇到了一个提现的需求,大概的流程如下:
1.先去第三方的交易系统中申请转账,将钱转入平台的资金账户中
2.第1步成功之后,再将转入平台的资金提取到对应的商户银行卡里

  拿到需求的第一时间想的是一个接口逻辑直接搞定,因为需求这边的设计是转账提现一步到位。所以直接第一步用http请求第一个转账接口,然后成功了再进行第二个http请求提现接口,成功了就进行请求的数据存储并修改申请单的各种状态。
  但是稍微想想就会觉得,两步http请求过程中,第一步http成功第二步失败了怎么处理?第一步http成功,请求和响应数据的存储失败怎么处理?第二步请求http成功,但是存储数据或者更新申请单状态失败怎么处理?这么一想就会觉得这个逻辑可能会很麻烦,由于涉及到了第三方交易系统的两次http请求,所以失败之后的回滚就是一个很麻烦的事情;另外,就算http成功,但如果后续的系统db出现链接不上,或者数据库存储、更新错误等情况时,也会涉及如果善后的问题。

【方案】

所以,经过思考和讨论,有如下确定的观点以及最终的设计:
1.涉及到对外的http请求和响应的,尤其是涉及第三方系统,那必须将请求和响应的情况都记录下来。一旦出现问题,方便快速定位bug所在;
2.一般来说,涉及到这种对外系统数据交互,我总会觉得各种不保险。因为请求成功之后,数据库一旦出现错误,回滚是很麻烦的事情,所以总会去想着有没有绝对保险的做法。但是事实上,所有的方案都不会绝对的保险,可用性和成本就是一种矛盾的存在,是需要根据我们所拥有的资源以及对系统的稳定性的要求进行平衡的。另一方面,虽然说没有绝对保险的方案,但是我们还是要保证如果出现问题,我们能够快速定位,快速解决;
3.基于第2点,如果说系统中出现了失败的情况,而且无法自动善后。那么就需要第一时间通知到开发或者运维人员来进行补救,这就是告警的意义。如果要上监控和告警,那么我们做的每一个告警的出发点都是需要被告警的人员来进行手动补救处理。如果出现了问题,系统能够进行自动地补救,那就不需要第一时间通知到运维人员,可以根据系统的设计进行相关问题统计或者超过某个阈值再让人介入继而优化;
4.以上3点是这个过程中想到的,并且讨论确定的一些观点。回到这个需求,合理的做法是将第1步和第2步http进行拆分,分为2个接口。这么做的好处有两个:(1)对于现阶段来说,好像两个请求合并放在一起是比较合理的,但是考虑到后期的需求变化,有可能就不是自动提现,而是需要将它们进行拆分,整个过程按照申请单的每一个状态变化进行分步展示和交互,那这次的逻辑就会推翻重写;但如果我们将其拆分,与第三方交易系统的流程保持一致,就可以在需求变化时保持我们系统更改处理的灵活性,这也算是一种降低耦合,增加可维护性的体现;(2)降低逻辑的复杂度。如果是一个接口中处理这么多逻辑,那么复杂度势必会很高,出了bug也不好调试。拆分成2个,看似接口多了,系统的处理复杂了,但是好处在于每一个接口中我们需要考虑的问题更简单,只要处理好每一个接口,那么整个逻辑的流程我们就拿下了。
最终,这个需求的设计如下:

 

  这里的转账操作,如果申请失败,那么不需要考虑后续的回滚。如果DB记录失败,这里需要进行Log记录,并第一时间发出告警(可能是数据库出问题了)。如果发送消息到消息队列失败,不需要回滚(第三方交易系统的回滚会很麻烦),第一时间告警(可能是消息队列出现问题了)。
  进入到消息队列后,由业务系统消费消息并调用业务系统的提现接口。注意,此时为了防止重复调用(因为消息队列或者外部都可能会因为某些业务重试调用该接口)我们需要将这个接口设计为幂等接口。在查询申请单状态时,保证没有提现的接口才能进行提现处理,同时防止外部的短时间内的重复频繁调用。申请提现如果失败,这里不需要回滚,直接返回。如果DB存储提现记录以及更新申请状态时失败,那么第一时间发出告警,这里可能是DB出现了问题。逻辑处理完成后,整个消息算是全部处理完成。

  从技术角度,考虑了各种异常情况的处理(回滚或者告警);从需求的扩展性,也考虑到了后续的两步拆分,如果提现操作失败,那么后续还可能衍生出管理人员进行更改配置,再次发起提现或者消息队列重试等等。

  从这个需求中,也要想到如果出现比较复杂的业务,没办法很稳地一步到位,那么不妨将其拆分为多个步骤。一来技术上每一步都更好处理;二来业务上的可扩展、低耦合也能保证。

------------------------------------------------------------------------------------

【UML】

  以前不明白为什么有类图,流程图,时序图...总觉得很混乱。
  但是画这个需求的UML时,想到其实UML只是一种表达的手段。我们需要这个工具将一个逻辑过程或者时间顺序严谨地刻画出来。所以,捋需求的过程中,我们需要将角色、逻辑、时序进行表达;在看代码或者写代码时,我们需要将继承、组合、接口、类、成员变量、函数等的关系画成图便于整体印象;在考虑数据库的各种作业执行时,我们需要各个作业的执行顺序、表单的前后变化等等。所以,不同的目的、不同的标准、不同的使用人员(比如给客户看、给开发测试看、给自己看)都会对最终成图的形态产生影响,最终的目的,就是为了便于快速理解整个过程或者结构。
  从这个角度,衔接各个模块的描述反而更为重要,不必拘泥于一些细节的严谨(当然严谨很好,但是为了画图的细节,比如有没有对齐,是否对称等花费很多时间就不值得了),只要容易理解到准确的原意即可。

 

 

 

 

posted on 2021-09-06 21:53  长江同学  阅读(174)  评论(0编辑  收藏  举报