干货 | 高耦合场景下,Trip.com如何做支付设计与落地
干货 | 高耦合场景下,Trip.com如何做支付设计与落地 https://mp.weixin.qq.com/s/VR9NTR3RpKVfmUPcwgMABg
作者简介
Ryann Liu,携程高级软件工程师,负责中文版、国际版支付Android端的开发及维护工作。
一、业务背景
在电商平台进行在线支付时,通常我们直接使用银行卡或第三方商户直接进行付款,就结束了一个完整的购物流程。
但是实际上,支付页面上涵盖的支付业务内容广泛,在开发过程中我们面对的是琳琅满目的支付方式,包括多种银行卡、银行积分、三方品牌、Trip Coins、礼品卡等,并且部分支付方式间我们支持用户做混付。
而支付运营可以对不同的支付方式配置各自的优惠券以及服务费。在支付过程中,用户可能恰好遇到运营配置变动,在这种极端场景下,我们需要考虑数据更新以及视图更新。
如果对以上场景进行排列组合,就不难发现我们面临的是一个耦合性非常高的业务场景。
二、分治业务
支付模块中整体结构如下:
对于我们面临的这种高耦合的业务场景,如何解耦就显得尤为重要。
我们通过合理的架构将业务隔离,使得业务逻辑与Activity/Fragment解耦,比如说利用MVP + Clean Architecture就能达到很好的效果。但是将数据和业务逻辑简单抽离出视图,可能造成的另一个问题就是presenter层变的臃肿。
此时,我们可以引入一个经典的算法思想,即分而治之 (Divide and Conquer)。
2.1 分而治之
2.1.1 什么是分而治之?
把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
根据这种思想,再划分支付类目下的各边界,一直到base cases。
2.1.2 划分
在划分时主要依据SOLID中的单一功能原则作为划分,将支付页面中的每一个视图作为一个base case。
由于不同的支付币种支持的内容不同,这里仅截图了用户从酒店下单使用港币支付的一个场景,划分结果如下图所示:
自此,我们将支付业务层划分完毕,提供了支付环节中各单一业务上的闭合,可以支持基于现有能力的组合,达到降低接入成本,快速验证的效果。
2.1.3 小结
如果与module组件化类比,这种结构可以称之为视图组件化,每一个base case都是一个功能的封装,拥有着高复用的特性,同时由于边界拆分,使得维护性和扩展性得以提高。
在视图组件化后,再在每个base case中使用MVP + Clean Architecture会使得代码更为简洁优雅,同时每个组件都是一个完整的整体,可以进行单独的运行和调试。
2.2 数据流转
支付业务通过视图组件化分而治之后,代码及功能得到解耦,但是涉及到另外一个问题,即数据流转问题。
在前文中提到的,我们有Trip Coins、礼品卡和其他支付方式的混付,也有优惠券、服务费的再计算,这些使得我们的拆分并不能做到数据层面的完全隔离,所以需要再处理各base case间的数据流转问题。
在实现时首先考虑使用Jetpack中的LiveData组件来作为数据存储器类,配合Jetpack中的ViewModel使用,使得在系统配置发生改变时也可以对数据做保存。
这里对LiveData和ViewModel做个简单的介绍。
2.2.1 LiveData分析
LiveData 是一种可观察的数据存储器类。
与常规的可观察类不同,LiveData 具有生命周期感知能力,它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。
从LiveData源码中可以看到,设置的observer实际上会被绑定到Activity/Fragment的Lifecycle上,所以给LiveData赋予了感知生命的能力:
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
...
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
...
owner.getLifecycle().addObserver(wrapper);
}
基于这一能力,我们可以轻易的了解到它们的优势:
- 从此我们不需要新增繁琐的处理生命周期相关的代码;
- 由于LiveData被设计为粘性事件,在页面状态由非活动状态转为活动状态时,会接收到最新数据,使得我们接收的数据始终保持最新状态;
- 在更新数据到视图时,不会因为此时activity处于停止状态而发生crash;
- 在页面退出时,被绑定的Lifecycle会被销毁,与该Lifecycle绑定的LiveData会被清理,从而能够避免内存泄漏的发生;
2.2.2 ViewModel介绍
ViewModel 类负责为界面准备数据,支持共享作用域。
它注重生命周期的存储和管理界面相关的数据,让数据可在发生屏幕旋转等配置更改后继续留存。
在使用时,我们会绑定业务ViewModel到Activity/Fragment上,Android源码中可以看到,当设备的configuration发生改变时,会自动存储该model:
public final Object onRetainNonConfigurationInstance() {
...
ViewModelStore viewModelStore = mViewModelStore;
...
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.viewModelStore = viewModelStore;
return nci;
}
之后在需要使用到ViewModel时会自动恢复数据:
public ViewModelStore getViewModelStore() {
...
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
在页面被DESTROY时,model将被自动清理。
public ComponentActivity() {
...
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
...
}
2.2.3 数据流转应用
回到我们的项目中,我们可以在各base case中都定义一组自身关心的LiveData,并提供代理给到外部做数据更新操作。
这些LiveData最终加入到支付业务的ViewModel内,同时在base case中暴露统一的方法向外传递自身数据。此时base case的外部为activity/Fragment,如何避免activity/Fragment成为base case的controller?
用“计算机科学领域的任何问题都可以通过增加一个中间层来解决”这句话来说,我们可以再定义一个ViewHelper作为中间层,将base case与activity/Fragment视图绑定,集中处理base case之间的数据流转。
以上充分利用了Jectpack Architecture组件的生命周期自动管理机制,避免了许多的问题,但是这并不是一个一劳永逸的方法,针对一些特殊的需求,它仍留有一定改进空间:
比如说:
- 前文中有提到LiveData是一个粘性事件,页面由非活动状态转到活动状态,只能收到最后一次的数据,导致前序数据丢失,而某些业务场可能要求数据不丢失或非活动状态仍要接收数据,此时LiveData就不再满足需求。
针对这个问题,可以通过“事件包装类”和“反射干预LastVersion”的方式进行解决,github上已有很多开源的解决方案的实现。
- 我们可能在多个页面订阅了同一个LiveData,但是业务要求,仅在前台页面中一次处理该数据,其它页面无需再处理。
针对这个问题,需要对Observer和LiveData进行二次封装,设置标志位,决定是否需要向下传递。
2.3 测试
经过拆分后,单个视图可以独立运行展示,方便我们在开发阶段进行快速验证,做简单的自测。
三、总结
我们可以在熟悉业务背景以及代码结构的基础上,梳理出问题点,针对业务背景给出合适的解决方案。对于复杂的业务场景,不妨进行分解,会使得业务流程和代码更为清晰。
干货 | 高耦合场景下,Trip.com如何做支付设计与落地的更多相关文章
- Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%。再往后,每提高0.1%,优化难度成指数级增长了。哪怕是千分之一,也直接影响用户体验,影响每天上万张机票的销售额。 在高并发场景下,提供了保证线程安全的对象、方法。比如经典的ConcurrentHashMap,它比起HashMap,有更小粒度的锁,并发读写性能更好。线程安全的StringBuilder取代S
Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%.再往后,每提高0.1%,优化难度成指数级增长了.哪怕是千分之一,也直接影响用户体验,影响每天上万张机 ...
- 【转】记录PHP、MySQL在高并发场景下产生的一次事故
看了一篇网友日志,感觉工作中值得借鉴,原文如下: 事故描述 在一次项目中,上线了一新功能之后,陆陆续续的有客服向我们反应,有用户的个别道具数量高达42亿,但是当时一直没有到证据表示这是,确实存在,并且 ...
- 高并发场景下System.currentTimeMillis()的性能问题的优化 以及SnowFlakeIdWorker高性能ID生成器
package xxx; import java.sql.Timestamp; import java.util.concurrent.*; import java.util.concurrent.a ...
- HttpClient在高并发场景下的优化实战
在项目中使用HttpClient可能是很普遍,尤其在当下微服务大火形势下,如果服务之间是http调用就少不了跟http客户端找交道.由于项目用户规模不同以及应用场景不同,很多时候可能不需要特别处理也. ...
- 高并发场景下System.currentTimeMillis()的性能问题的优化
高并发场景下System.currentTimeMillis()的性能问题的优化 package cn.ucaner.alpaca.common.util.key; import java.sql.T ...
- C++高并发场景下读多写少的解决方案
C++高并发场景下读多写少的解决方案 概述 一谈到高并发的解决方案,往往能想到模块水平拆分.数据库读写分离.分库分表,加缓存.加mq等,这些都是从系统架构上解决.单模块作为系统的组成单元,其性能好坏也 ...
- C++高并发场景下读多写少的优化方案
概述 一谈到高并发的优化方案,往往能想到模块水平拆分.数据库读写分离.分库分表,加缓存.加mq等,这些都是从系统架构上解决.单模块作为系统的组成单元,其性能好坏也能很大的影响整体性能,本文从单模块下读 ...
- MySQL在大数据、高并发场景下的SQL语句优化和"最佳实践"
本文主要针对中小型应用或网站,重点探讨日常程序开发中SQL语句的优化问题,所谓“大数据”.“高并发”仅针对中小型应用而言,专业的数据库运维大神请无视.以下实践为个人在实际开发工作中,针对相对“大数据” ...
- 高并发场景下JVM调优实践之路
一.背景 2021年2月,收到反馈,视频APP某核心接口高峰期响应慢,影响用户体验. 通过监控发现,接口响应慢主要是P99耗时高引起的,怀疑与该服务的GC有关,该服务典型的一个实例GC表现如下图: 可 ...
随机推荐
- JAR-使用JAVA命令编译打包一个可执行jar包
一.开发一个演示项目 项目源代码开发 项目名称叫jar-package-example(其实只是一个文件夹, 用以将演示的所有文件夹和文件存放在其中, 没啥其它作用), 为了方便, 后文统一叫jar- ...
- 一个简单的java项目使用hibernate连接mysql数据库
实体类与表对应文件Customer.hbm.xml <?xml version="1.0" encoding="UTF-8"?><!DOCTY ...
- vue-element Form表单验证(表单验证没错却一直提示错误)
在使用element-UI 的表单时,发生一个验证错误,例如已输入值但求验证纠错: 代码如下所示: <el-form :model="correction" :i ...
- WebService 适用场合
适用场合 1.跨防火墙通信 如果应用程序有成千上万的用户,而且分布在世界各地,那么客户端和服务器之间的通信将是一个棘手的问题.因为客户端和服务器之间通常会有防火墙或者代理服 务器.在这种情况下,使用D ...
- FAAS -- Serverless
FAAS概念,无服务器运算,功能即服务,function-as-a-service 初创企业-大型企业.民间组织-政府机构 ===>>>> 上云 云计算第三代技术 -- Ser ...
- 阿里云centos7安装mysql8数据库
一.安装mysql 1. mysql官网查找仓库源镜像,选择downloads https://www.mysql.com/downloads/ 2. 找到社区版 3. 选择yum仓库 4. 选择对应 ...
- vue中的一些用法,持续更新中......
1.跳转用法 @1.在template模板中通常使用router-link to='url' @2.在js中 1.this.$router.push({path: ''/order/index''}) ...
- #2020征文-TV# Tab切换选项卡同时更换内容
Tab选项卡是应用程序中最最常用,也是最普遍存在的一种布局形态,无论是在PC端还是在移动端,都是一种清晰明了,层级关系简单的,能够使用户明确所处位置.Tab选项卡可以置于页面的底部,比如微信底部选项卡 ...
- Angular入门到精通系列教程(7)- 组件(@Component)基本知识
1. 概述 2. 创建Component 组件模板 视图封装模式 特殊的选择器 :host inline-styles 3. 总结 环境: Angular CLI: 11.0.6 Angular: 1 ...
- COW技术重置虚拟机
最近使用COW技术做虚拟机重置,结果成功了,特地来给大家分享一下这次的成果! 文章目录 一.COW技术是什么? 二.重置虚拟机步骤 总结 往期杂文 一.COW技术是什么? Copy On Write, ...