对中国开发者最具吸引力的科技企业有哪些?快来为你 pick 的企业投票! 了解详情
写点什么

Trip.com 机票 React Native 整洁架构 2.0 实践

2020 年 9 月 20 日

Trip.com 机票React Native整洁架构2.0实践

一、前言

2019 年上半年携程机票前台团队基于 clean architecture 思想,结合具体业务特点和复杂度,对 App 机票查询列表页进行了一次技术重构。重构后的机票列表页视图与逻辑分离,多个业务模块分治业务场景,降低整体业务复杂度,提升了页面的可维护性,可测试性。


在近一年的业务迭代过程中开发团队发现了新的问题,并在原有1.0版本架构上做进一步优化。


二、架构优化

软件架构是软件的基本结构,针对业务场景实现合适的软件架构是软件可维护、可拓展的重要因素之一。


随着业务发展,大量业务逻辑迁移至前端实现以减少请求服务的次数,带给用户更平滑、顺畅的使用体验,前端的业务复杂度大大提高。现阶段前端的主要业务场景可抽象为:


1)请求服务。


2)根据业务逻辑将服务数据转化为业务状态。


3)根据展示逻辑将业务状态转化为展示状态,并渲染至界面。


4)响应用户交互,根据展示逻辑更新展示状态,根据业务逻辑更新业务状态。


前端页面的复杂度在于业务逻辑、展示逻辑繁多复杂,且业务逻辑间、展示逻辑间存在大量联动关系。如下图,大量复杂的业务逻辑、展示逻辑互相关联,导致整个页面的复杂度指数级上升。



2.1 原有问题

原架构借鉴 clean architecture 思想,将页面拆分为多个同构的业务模块,业务模块间可以嵌套组合。单个业务模块使用 MVP 模式进行管理:


  • View - React 代码,只负责界面展示、样式和响应用户交互。

  • Presenter - 连接 View 和 Model,连接外部模块,不存在业务逻辑。

  • Model - 业务实体,封装了业务逻辑和展示逻辑供 Presenter 调用。


其架构如图:



对比原架构设计与实际业务场景,可以发现其设计存在不合理之处:


  • 业务逻辑实现在业务模块内,与展示逻辑强耦合。当界面不展示业务模块时,对应的业务逻辑也无法执行,容易出现程序 bug。

  • 业务逻辑与展示逻辑难以复用。

  • 页面内多个业务模块实现同一业务逻辑时,只能通过拷贝相关代码解决。

  • 跨页面复用模块时,由于不同页面间的业务逻辑存在差异,导致无法直接复用。

  • 模块间数据通信方式复杂,由于业务逻辑实现在不同业务模块内且业务模块在页面中呈树状结构,页面逻辑复杂时数据通信容易出现下图中的状态。



2.2 解决方案

新架构针对上述问题进行优化,核心改动点为:


  • 优化数据通信方式,模块只与 Service 通信,实现单向数据流。

  • 新增业务 Service 概念,承载页面业务逻辑,业务模块调整为只承载展示逻辑。


最新架构如图:



单向数据流


新架构下业务模块间无法通信,只可与业务 Service 通信,并且业务模块只是业务 Service 方法的调用方,业务逻辑的计算在业务 Service 实现,最终实现了单向数据流。


对于业务模块触发业务数据更新(例如用户交互),其流程如下:



对于业务数据更新触发业务模块刷新(例如请求返回), 其流程如下:



对于业务模块触发业务数据更新,同时联动引起其他业务模块刷新,其流程如下:



整体数据流如下:



业务 Service


新架构中,页面拆分为多个同构业务模块和多个业务 Service,业务模块根据界面展示内容进行划分,仍使用 MVP 模式进行管理,业务 Service 根据业务领域进行划分,使用面向对象方式进行管理。


业务模块中 View 职责不变,Presenter 不再与其他模块直接连接、新增与业务 Service 的连接,Model 不再负责业务逻辑,专注于展示逻辑。


业务 Service 则专注于特定业务领域的业务逻辑,为上层业务模块和其他业务 Service 提供支持。


拆分后的业务模块与业务 Service,更符合单一职责原则(SRP 原则),两者的可复用性也大大提升。跨页面复用业务模块时,只要其展示逻辑、交互逻辑相同即可直接复用。页面内涉及相同业务逻辑的业务模块,调用业务 Service 方法即可完成功能。


业务 Service 还能提取成为公用类库,不同平台(例如 h5、online、app)存在相似业务场景时,即使上层的界面展示、交互方式不同,采用的 UI 框架不同也能进行复用,降低跨平台开发的成本。


三、插件功能优化

前端页面中除了业务功能外,还需实现大量非业务性功能,例如用户行为埋点、线上监控等。


3.1 原有问题

原架构中这类非业务性功能通常散落在代码各处,自身缺乏收口方式,对正常业务代码侵入性强,严重影响代码的可读性、可维护性。


以最常见的埋点功能为例,假设现在需对页面内具有联动关系的展示数据进行监控,当数据间展示不同步时上送报错埋点。在原架构下我们的实现方式为:


// ModuleA/Presenter/index.tsexport class ModuleAPresenter {    private monitor: Monitor;    constructor(monitor: Monitor) {        this.monitor = monitor;    }
public updateView() { // 收集模块A的最新状态。 this.monitor.updateState(this.model.getViewModel(), 'ModuleA'); this.view.updateView(this.model.getViewModel()); }}// Monitor/index.tsexport class Monitor { private stateMap = new Map(); public updateState(state, moduleName) { this.stateMap.set(moduleName, state); // 检查模块A、模块B的状态是否同步。 this.checkState(); }}
复制代码



    上述代码有几个明显的问题:


    1)埋点代码直接侵入业务代码,两者互相强耦合,后续对埋点逻辑的改动很可能破坏业务代码,反之亦然。


    2)业务模块需持有埋点类的实例,增加了对 Monitor 类的依赖,降低了自身的可复用性、可测试性。


    3)对埋点逻辑的修改需要改动多个位置的代码,产生了”散弹枪式修改“的坏味道。


    3.2 解决方案

    基于面向切面编程的思想,在架构设计时预留”切面“并提供插件功能。用户可将非业务性功能封装在插件内维护与业务代码完全隔离,插件可通过切面获取如程序生命周期、特定用户行为等必要信息,无需入侵业务模块代码。同时业务模块也可访问插件实例,利用插件收集的数据完成特定功能。


    面向切面编程(Aspectoriented programming)旨在将业务主体与非业务性功能分离,以提高程序的模块化程度。它将代码逻辑切分为不同的业务功能集,每个功能集包含了多个功能点,部分功能点会在多个功能集中都有出现,它们被称为”切面“。非业务性功能利用切面进行封装、维护,使原本分散在整个页面中的逻辑变得可管理、可维护。


    上述例子使用插件改写后如下:


    // ModuleA/Presenter/index.tsexport class ModuleAPresenter {    public updateView() {        // 业务模块中不再有无关逻辑        this.view.updateView(this.model.getViewModel());    }}// Monitor/index.tsexport class MonitorPlugin implements IGrtPlugin {    private stateMap = new Map();
    // ”切面“方法 public onUpdateState(state, moduleName) { this.stateMap.set(moduleName, state); // 检查模块A、模块B的状态是否同步。 this.checkState(); }}
    复制代码


    改动后的代码业务功能与非业务性功能完全解耦,且埋点功能的相关逻辑完全收口在 Monitor 类内,代码的可读性、可维护性有效提升。


    四、小结

    新架构针对业务功能,优化了现有代码结构,使其能够更好地应对愈发复杂的业务场景,实现业务功能,同时保证实现代码的可维护性。


    针对非业务性功能,提出插件功能,利用面向切面编程思想,使非业务性功能收口在插件类内,不入侵业务模块代码。


    作者介绍


    佳璐、熠暘、文焕,携程国际部门机票 App 团队。


    本文转载自公众号携程技术(ID:ctriptech)。


    原文链接


    Trip.com 机票React Native整洁架构2.0实践


    2020 年 9 月 20 日 10:001119

    评论

    发布
    暂无评论
    发现更多内容

    功能测试用例设计方法分享

    行者AI

    测试

    请用思维导图画出架构师训练营所有技术知识点

    Jacky.Chen

    Rancher年终盘点丨历尽千帆,岁月可期

    RancherLabs

    rancher

    Angel图神经网络算法在推荐场景下的实践

    DataFunTalk

    大数据

    PlayStation@4功能介绍及测试应用

    行者AI

    测试

    2020年度国产数据库:openGauss

    墨天轮

    数据库

    LeetCode题解:347. 前 K 个高频元素,快速排序,JavaScript,详细注释

    Lee Chen

    算法 LeetCode 前端进阶训练营

    腾讯云加速构建云原生数据仓库,助力企业数字化转型

    小小的一朵云

    大数据 数据仓库

    程序员能靠比特币能致富(暴富)吗?

    陆陆通通

    比特币 区块链

    资深首席架构师预测:2021年云计算的8个首要趋势

    RancherLabs

    容器 rancher

    SRE灵魂之SLI和SLO

    勇往直前的胖子

    SRE SLO

    区块链数据存储与IPFS技术的融合应用

    CECBC区块链专委会

    区块链 数据存储

    区块链食品溯源系统搭建---赋能智慧农业

    135深圳3055源中瑞8032

    APICloud AVM 多端开发 | 外卖app开发案例教程(下)

    APICloud

    前端工程 Web Worker 前端进阶 前端教程 APICloud

    Windows Server 做网络转发

    wong

    Windows Server netsh

    浅谈机器学习模型推理性能优化

    ThoughtWorks洞见

    人工智能 机器学习

    区块链脱虚向实 市场教育基本完成

    CECBC区块链专委会

    区块链

    架构师训练营11W作业

    Geek_f06ede

    一文彻底吃透MyBatis源码!!

    冰河

    架构 mybatis 架构设计 框架 源码解析

    谷歌被反垄断诉讼后,美国互联网会再度繁荣吗?

    脑极体

    Java达到什么样的水平才能通过阿里社招?

    Java架构师迁哥

    城市智慧社区智能化建设,平安小区管理系统

    135深圳3055源中瑞8032

    元旦在家撸了两天Seata源码,你们是咋度过的呢?

    冰河

    分布式事务 分布式数据库 分布式存储 数据一致性 seata

    腾讯云大数据发布数据生态战略,构建开源开放数仓生态

    小小的一朵云

    大数据 数据仓库

    架构师训练营技术知识点

    业哥

    Apache Pulsar 12月月报:Pulsar 2.7.0 发布!

    Apache Pulsar

    大数据 开源 pulsar Apache Pulsar 消息系统

    华为交换机恢复出厂设置的三种方法

    音视频技术入门基础

    赖猫

    c++ 音视频 ffmpeg 实时音视频

    在onelogin中使用OpenId Connect Authentication Flow

    程序那些事

    权限系统 OAuth 2.0 程序那些事 权限架构 OpenConnect

    从技术视角看考拉海购的云原生之路

    阿里巴巴中间件

    云计算 云原生

    ClickHouse的实践之路

    DataFunTalk

    数据库

    滴滴 Logi 日志管理与分析平台

    滴滴 Logi 日志管理与分析平台

    Trip.com 机票React Native整洁架构2.0实践-InfoQ