FCon7折倒计时最后一周:日程已上线70%!查看详情>>> 了解详情
写点什么

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

  • 2020-09-20
  • 本文字数:2956 字

    阅读完需:约 10 分钟

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-09-20 10:001525

    评论

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

    Flutter RichText支持图片显示和自定义图片效果,经典Android开发教程

    android 程序员 移动开发

    Flutter 入门与实战(十三),安卓framework层开发

    android 程序员 移动开发

    Flutter 中的 JSON 解析(1),androidsdk环境配置

    android 程序员 移动开发

    Flutter 中的 JSON 解析,事件分发机制Android

    android 程序员 移动开发

    web技术分享| 一人一天一个可移植的实时聊天系统

    anyRTC开发者

    大前端 Web 音视频 实时通信 实时聊天

    Flutter 跨平台框架应用实战-2019极光开发者大会,音视频开发面试

    android 程序员 移动开发

    从厂商主张到客户主见,4个变化揭示 Serverless 的不同

    望宸

    Serverless 容器 云原生 k8s

    Flutter Android 端 FlutterInjector 及依赖流程源码分析

    android 程序员 移动开发

    Flutter _ 日志还能这么打印,太秀了!,android移动应用基础教程

    android 程序员 移动开发

    Flutter 仿掘金微信图片滑动退出页面效果,写给程序员的Flutter详细教程

    android 程序员 移动开发

    Flutter之FutureBuilder的学习和使用,Android2021面试题

    android 程序员 移动开发

    Flutter之撸一个漂亮的登录界面的总结,Android性能优化之启动优化实战篇

    android 程序员 移动开发

    Flutter动手实战,大佬手把手教你如何仿写出大厂的APP,Android软件开发面试题

    android 程序员 移动开发

    Flutter图表库fl_chart的使用解析(二)-折线图,android webview

    android 程序员 移动开发

    下一代信息技术论坛云操作系统介绍

    架构 操作系统

    Flutter 与 Compose怎么选?小孩子才做选择,kotlinwindows桌面开发

    android 程序员 移动开发

    Flutter 官方尝试放只“鸽子”来简化Native插件开发,复习指南

    android 程序员 移动开发

    Flutter之全埋点思考与实现,精心整理

    android 程序员 移动开发

    Flutter仿钉钉考勤日历,html5移动端

    android 程序员 移动开发

    Flutter原理:三棵重要的树(渲染过程、布局约束,android开发框架介绍

    android 程序员 移动开发

    华云大咖说 | 安超DCM运维场景解决方案

    华云数据

    Flutter Android 工程结构及应用层编译源码深入分析,Android面试题及答案2020

    android 程序员 移动开发

    Flutter 仿掘金推特点赞按钮,kotlin中文版

    android 程序员 移动开发

    Flutter-系列(四)基础UI实践,从外包月薪5K到阿里月薪15K

    android 程序员 移动开发

    Flutter中http请求抓包解决方案,揭秘今年Android春招面试必问问题有哪些

    android 程序员 移动开发

    架构实战营 模块三作业

    felix

    架构实战营

    新一代云上基础技术和架构分论坛

    阿里云 架构 基础设施 科技 云栖大会

    Flutter Candies 一桶天下,kotlin编程软件

    android 程序员 移动开发

    Flutter 入门与实战(九),android软件开发前景

    android 程序员 移动开发

    Flutter 如何发布安卓应用?,flutter文档发布组件

    android 程序员 移动开发

    Flutter基础(三)Dart快速入门,下血本买的

    android 程序员 移动开发

    • 扫码添加小助手
      领取最新资料包
    Trip.com 机票React Native整洁架构2.0实践_软件工程_佳璐_InfoQ精选文章