干货 | 高耦合场景下,Trip.com如何做支付设计与落地 https://mp.weixin.qq.com/s/VR9NTR3RpKVfmUPcwgMABg

原创 Ryann Liu 携程技术 2020-09-17
 

作者简介

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赋予了感知生命的能力:

@MainThreadpublic 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如何做支付设计与落地的更多相关文章

  1. Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%。再往后,每提高0.1%,优化难度成指数级增长了。哪怕是千分之一,也直接影响用户体验,影响每天上万张机票的销售额。 在高并发场景下,提供了保证线程安全的对象、方法。比如经典的ConcurrentHashMap,它比起HashMap,有更小粒度的锁,并发读写性能更好。线程安全的StringBuilder取代S

    Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%.再往后,每提高0.1%,优化难度成指数级增长了.哪怕是千分之一,也直接影响用户体验,影响每天上万张机 ...

  2. 【转】记录PHP、MySQL在高并发场景下产生的一次事故

    看了一篇网友日志,感觉工作中值得借鉴,原文如下: 事故描述 在一次项目中,上线了一新功能之后,陆陆续续的有客服向我们反应,有用户的个别道具数量高达42亿,但是当时一直没有到证据表示这是,确实存在,并且 ...

  3. 高并发场景下System.currentTimeMillis()的性能问题的优化 以及SnowFlakeIdWorker高性能ID生成器

    package xxx; import java.sql.Timestamp; import java.util.concurrent.*; import java.util.concurrent.a ...

  4. HttpClient在高并发场景下的优化实战

    在项目中使用HttpClient可能是很普遍,尤其在当下微服务大火形势下,如果服务之间是http调用就少不了跟http客户端找交道.由于项目用户规模不同以及应用场景不同,很多时候可能不需要特别处理也. ...

  5. 高并发场景下System.currentTimeMillis()的性能问题的优化

    高并发场景下System.currentTimeMillis()的性能问题的优化 package cn.ucaner.alpaca.common.util.key; import java.sql.T ...

  6. C++高并发场景下读多写少的解决方案

    C++高并发场景下读多写少的解决方案 概述 一谈到高并发的解决方案,往往能想到模块水平拆分.数据库读写分离.分库分表,加缓存.加mq等,这些都是从系统架构上解决.单模块作为系统的组成单元,其性能好坏也 ...

  7. C++高并发场景下读多写少的优化方案

    概述 一谈到高并发的优化方案,往往能想到模块水平拆分.数据库读写分离.分库分表,加缓存.加mq等,这些都是从系统架构上解决.单模块作为系统的组成单元,其性能好坏也能很大的影响整体性能,本文从单模块下读 ...

  8. MySQL在大数据、高并发场景下的SQL语句优化和"最佳实践"

    本文主要针对中小型应用或网站,重点探讨日常程序开发中SQL语句的优化问题,所谓“大数据”.“高并发”仅针对中小型应用而言,专业的数据库运维大神请无视.以下实践为个人在实际开发工作中,针对相对“大数据” ...

  9. 高并发场景下JVM调优实践之路

    一.背景 2021年2月,收到反馈,视频APP某核心接口高峰期响应慢,影响用户体验. 通过监控发现,接口响应慢主要是P99耗时高引起的,怀疑与该服务的GC有关,该服务典型的一个实例GC表现如下图: 可 ...

随机推荐

  1. JAR-使用JAVA命令编译打包一个可执行jar包

    一.开发一个演示项目 项目源代码开发 项目名称叫jar-package-example(其实只是一个文件夹, 用以将演示的所有文件夹和文件存放在其中, 没啥其它作用), 为了方便, 后文统一叫jar- ...

  2. 一个简单的java项目使用hibernate连接mysql数据库

    实体类与表对应文件Customer.hbm.xml <?xml version="1.0" encoding="UTF-8"?><!DOCTY ...

  3. vue-element Form表单验证(表单验证没错却一直提示错误)

    在使用element-UI 的表单时,发生一个验证错误,例如已输入值但求验证纠错:       代码如下所示: <el-form :model="correction" :i ...

  4. WebService 适用场合

    适用场合 1.跨防火墙通信 如果应用程序有成千上万的用户,而且分布在世界各地,那么客户端和服务器之间的通信将是一个棘手的问题.因为客户端和服务器之间通常会有防火墙或者代理服 务器.在这种情况下,使用D ...

  5. FAAS -- Serverless

    FAAS概念,无服务器运算,功能即服务,function-as-a-service 初创企业-大型企业.民间组织-政府机构 ===>>>> 上云 云计算第三代技术 -- Ser ...

  6. 阿里云centos7安装mysql8数据库

    一.安装mysql 1. mysql官网查找仓库源镜像,选择downloads https://www.mysql.com/downloads/ 2. 找到社区版 3. 选择yum仓库 4. 选择对应 ...

  7. vue中的一些用法,持续更新中......

    1.跳转用法 @1.在template模板中通常使用router-link to='url' @2.在js中 1.this.$router.push({path: ''/order/index''}) ...

  8. #2020征文-TV# Tab切换选项卡同时更换内容

    Tab选项卡是应用程序中最最常用,也是最普遍存在的一种布局形态,无论是在PC端还是在移动端,都是一种清晰明了,层级关系简单的,能够使用户明确所处位置.Tab选项卡可以置于页面的底部,比如微信底部选项卡 ...

  9. Angular入门到精通系列教程(7)- 组件(@Component)基本知识

    1. 概述 2. 创建Component 组件模板 视图封装模式 特殊的选择器 :host inline-styles 3. 总结 环境: Angular CLI: 11.0.6 Angular: 1 ...

  10. COW技术重置虚拟机

    最近使用COW技术做虚拟机重置,结果成功了,特地来给大家分享一下这次的成果! 文章目录 一.COW技术是什么? 二.重置虚拟机步骤 总结 往期杂文 一.COW技术是什么? Copy On Write, ...