语雀 App 跨端技术架构实践

本文涉及的产品
mPaaS订阅基础套餐,标准版 3个月
简介: 语雀 App 跨端技术架构实践

🙋🏻‍♀️ 编者按:本文作者是蚂蚁集团前端工程师牧秦,介绍了语雀 App 在落地过程中的一些方案推演及架构设计,以及跨端场景下的一些同构开发实践。

  1. 语雀整体介绍

1.1. 简单介绍

首先介绍一下语雀的整体情况,语雀是蚂蚁集团推出的一款笔记与文档知识库的管理 & 协同工具,目前蚂蚁集团和阿里集团的员工大约有 10 万多人日常也在使用这个工具,同时也在对外提供服务。


如下图所示为语雀基于 Electron 推出的 Mac 和 Windows 桌面端,还有应对移动端工作场景的小程序。另外,从去年开始我们着手开发了移动端 App,并于今年 2 月份顺利发布上线。图中右侧展示的是 Android/iOS的 App 截图。

1.2. 语雀整体技术选型

接下来我将介绍一下语雀的整体技术选型。语雀在内部名称为 Skylark,它的整个项目位于一个大型的 Codebase 中。其中的服务端、桌面端、移动端等所有代码包含在这个 Codebase 中,如下所示:


其中服务端采用 Node.js、Egg.js,还有一些服务是基于 Java/Kotlin 开发的,但是这部分目前相对比较少。PC 端主要采用 React 技术栈,桌面端采用 React 和 Electron,小程序采用 React 以及 H5 离线包。大家可以发现,整个技术是基于 JavaScript 或者TypeScript来做的。

整个语雀技术团队没有区分前后端或客户端,大家都是全栈式地做产品研发。一个团队成员可能既要完成一个需求的服务端部分,同时也要完成前端的代码研发。在此基础上我们进行了移动端 App 的设计和开发,这部分内容我们接下来将详细介绍。

  2. 语雀 App 架构推演 & 设计

2.1. 阿里云 mPaaS

语雀 App 的架构是如何设计的呢?首先整个集团已经有一套比较稳定的移动端框架 —— mPaaS,该框架是基础的移动端开发框架,提供了丰富的移动端基础能力,如:配置开关服务、Push服务、基于长连接的 Sync 服务、H5 容器&离线包、移动分析和移动网关等基础服务。它现在在阿里云上也对外提供服务,也有很多三方公司都基于这个框架来开发自己的 App:

2.2. 移动端架构思路

在这样的技术品牌打底的基础上,我们进行了移动端的架构设计。首先选用阿里云上 mPaaS 作为底层的框架基础,然后客户端的 Native 框架层尽可能轻量、与业务解耦:


接下来针对渲染层,我们考虑动态或者Hybrid方案,比如要根据文档阅读页或编辑器的业务特性来选用具体的框架。最后还要考虑同构开发,因为现在已经有很多的多端业务模块在运行中了,如果能够实现同构开发,那么这些代码或者经验都可以被复用。

2.3. 移动端方案对比

下图为移动端的各种开发框架对比,相信大家已经在很多场合都看过了。其中基于 H5 Hybrid 的方案( H5 或者离线包),其性能稍微差一些。纯 Native 的性能虽然很高,但是开发成本也相应增加,上手也比较困难。现在一些公司也有自己内部的 Hybrid 开发方案。

2.4. 语雀的选择

那么语雀是怎么选择呢?首先我们希望语雀的体验是比较流畅的,能够贴近 Native App 的各种体验;其次希望能做到一次编写,双端运行,兼顾开发效率;然后就是能够兼具 Web 的研发体验,使得团队的 Web 技术栈的同学能够快速参与进来开发;最后是期望能够同构复用,这样之前沉淀的经验、模块可以直接复用。

综合各种因素,结合实际的业务需求考虑,我们最终选择了 ReactNative 作为业务层开发框架。

2.5. 语雀 App 架构图

接下来我为大家介绍一下语雀 App 的整体架构,如下图所示。最底层是 mPaaS 的基础设施,前面已经提过了,其中包含了一些通用基础组件。中间层是 Native 基础能力层,这一层提供了基础的账号管理、统一资源管理、动态化能力、各种 Bridge 以及其他服务。


接下来我们可以看到两个向上的箭头,RCTBridge 负责把基础服务通过 ReactNative Bridge 接口暴露给 ReactNative;JSBridge 则是将这些服务通过 H5 JSBridge 同步暴露给 H5。然后 ReactNative 和 H5 之间通过 RCTEvent 进行双向通信。

ReactNative 层主要实现页面生命周期、业务跳转、页面呈现等;H5 主要用来承载对更新速度等要求比较高的业务。再上层是 CI/CD 以及 DevTools、环境切换、单元测试等,充分支撑语雀的上层具体业务。

2.6. 三层架构


如图所示可以看出整体是一个三层架构。在 Native 层我们采用 Kotlin/Swift 来实现。ReactNative 层采用 TypeScript/同构 Web 方式来实现,这一层主要用来实现列表或者对流畅性、体验要求比较高的页面。而 H5 层主要结合语雀的具体业务特性来使用:比如要用ReactNative来实现一个富文本编辑器或者阅读器,那么它的代价是非常高的,这种情况就使用 H5 来实现;对页面加载速度要求比较高的 H5 页面,我们使用离线包来做页面资源加速。

2.7. 通用 JSBridge 设计

接下来介绍一下通用的 JS bridge 设计,如下所示:


在 Native 层,RN 和 H5 共用一份 JS Bridge 实现。底层的一些通用服务能力,分别通过两个 Bridge 暴露上去,可以让 H5 和 ReactNative 直接调用。上图右侧展示了 H5 和 ReactNative 调用 Bridge 的代码,其实除了第一行不一样之外,后面的 Bridge 调用和结果处理都是一致的。

2.8. ReactNative ↔ H5 双向消息流动

在业务开发中,不可避免会遇到 ReactNative 页面和打开的 H5 页面之间的交互和通信。我们研发了双向消息流动机制,可以在两侧均使用 postMessage/onMessage 来收发消息。

比如在进入语雀富文本编辑页面后,底部是一个 ReactNative View,上面嵌入了 WebView/WKWebView 用于展示编辑页。这就涉及到了 H5 和 ReactNative 的双向消息流动:

如上图所示,蓝色的箭头是 H5 调用 ReactNative,红色箭头是 ReactNative 调用 H5,双方通过 postMessage/onMessage 互通消息,并且支持 callback 处理回调。

  3. 跨端同构实践

前面有提到,语雀 App 技术选型的时候就考虑到同构开发。那么在语雀 App 中,有什么场景是涉及到同构的呢?我们在具体的业务研发中,遇到了几种典型的同构场景,接下来简单介绍一下同构的实践经验。

3.1. 基于 ViewModel 同构列表页

如下图所示,我们看到一个Web 页面,其中包含了一些列表,每个列表项包含操作选项入口,用户点击操作菜单后,可以对列表进行排序、筛选或者删除等操作。小程序和移动端App 也是这样,这三个页面的共性就是它们都是一个列表页,同时需要请求网络加载数据,另外可能还包括分页操作等。当然这里没有特别复杂的交互,点击操作菜单可以进行删除等操作。我们知道了这些共性,就可以方便的进行同构开发。


具体列表页怎么基于 ViewModel 进行同构开发呢?如下图,我们抽出通用的 ViewModel,在其中做 loading 管理、数据请求、用户操作管理等。网络请求管理可以通过 recentListAll 请求,通过 state 和 handlers 分别暴露数据和具体操作。这样最后返回的是状态集和一个操作列表:


在上层使用的时候,比如说我们使用这样的 ViewModel 渲染的最终页面时候,在各个平台上只需重写 UI 渲染部分,返回对应平台的 View/div 即可。通用的重逻辑、重请求的代码都是同构开发的。

3.2. Request 请求同构

语雀业务研发中的网络请求在接口层面是同构的,这样可以保证三端代码和开发模式的一致性。具体同构方式如下:

  • 在 isomorphic 中定义通用的接口文件,其中使用 fetch/get/post/... 等方法发送请求;
  • 在各个平台上,实现具体的 fetch 接口。如:Web 侧使用浏览器的 fetch 接口,小程序侧可能调用 AlipayJSBridge,ReactNative 侧则调用 LarkRCTBridge;
  • 在构建阶段,通过 alias 将 request.js 在各个平台进行重写。

3.3. 消息三端同构

接下来介绍一下另一个同构场景:消息通知三端同构。比如我们在 Web 侧有这样一个消息列表,只要别人关注了我或者评论了我的文档,就会产生一个消息。小程序和 App 侧的 UI 也是类似的。还有一个场景就是 App 消息推送,别人对我的文档进行操作时,要在移动端设备系统通知栏展示出来:

那么在这种情况下我们该怎么做同构开发呢?语雀现在有 70 多种消息,如果要为三端都写一份实现的话,可能要写 230 多次。这显然是不现实的。此时就可以基于同构的方案来做。同构部分包括各种消息的同构 Builder 生成器,分别通过模板 Notification Context 构建三端渲染结果:服务端得到消息字符串用于展示,在 H5 或者 Web 端渲染出原生的 div、span 等元素,在 ReactNative 侧渲染出 View、Text 等 View。

举一个例子,比如在下图所示的 Context 上有一个 buildActor、buildSubject 和 buildLink 来渲染用户头像或者链接等。接下来服务端、小程序和 ReactNative 提供三个 Context 实现就可以了:

上面介绍的一些典型的同构场景,在 App 开发中很好的验证了架构设计阶段提出的同构思路。在实际研发中,进行模块、代码的复用,对研发效率有很大提升。

  4. 子应用设计

在实际的业务研发中,有些业务模块对更新时效性有较强的要求,另外技术侧也希望一些业务模块能够做到独立维护、独立交付,同时能够按需加载,降低主应用负荷。在此基础上,我们进行了子应用的方案设计来满足这种场景。

基于 ReactNative 的动态加载特性,我们设计了子应用架构。使得 App 中对更新率要求较高的场景,进行子应用化动态加载。如下图,RN 侧主应用代码和子应用代码可以分别维护,子应用可以随 App 打包预置,也可以通过 CDN 进行动态下发加载,较好的满足了语雀的业务场景。

  5. 性能 & 稳定性 & 交付

5.1. 性能调优

我们在实际开发中,遇到了一些性能问题。首先是 App 启动速度优化,App 启动时一般需要展示闪屏页、隐私协议授权页、权限授予等,我们将闪屏页和主 Activity 合二为一,同时采用 Pipeline 方案定制启动细节,最终启动速度得到了较好的优化。

第二个就是小记编辑器启动速度优化。前面说到编辑器是利用 H5 来做的,但是如果用户有了灵感,想要做一些记录,如果编辑器启动慢,那么用户是很恼火的。为此,我们对小记做了一个单独的编辑器,通过离线包将其预置到客户端,或者通过动态下发的方式更新,能够第一时间快速拉起。

另外,我们还进行了 WebView 的加载优化,对一些通用 JS 和 CSS 做了一些资源包的预加速。结合 WebView 预创建 + 循环复用的方案,对 App 端内 H5 页面的打开速度,进行了较大幅度的提升。

5.2. WebView 预创建 + 循环复用

语雀 App 中,像文档阅读页、编辑页等复杂页面均是 H5 实现。用户一直反馈在端内打开速度很慢,经过我们实测,在 Android 较差的机型上,打开一篇文档耗时有时达到 5s 左右!我们结合业务特点,采用了 WebView 预创建 + 循环复用的方案,对 H5 的打开性能有了较大幅度的提升。

如下图所示,App 启动后,启动 WebView 预创建池,预创建出离屏 H5 骨架屏 —— 该骨架屏是一个单页应用,包含了阅读页、新建页、个人页等端内高频使用的页面。在用户打开相应页面时,直接从池中取出,识别出打开场景后,进行 replace 路由切换即可。由于页面所需的资源已经预先加载好,在打开阶段,只需拉取对应的业务数据渲染即可,大大提升了页面的打开速度。

在方案上线前,我们对主要的场景做了 FCP 耗时埋点作为优化基准,上线后 Android 平均耗时整体降低 67% 左右,iOS 平均耗时整体降低 70% 左右,整体效果还可以。不过有一些场景无法用到骨架屏,比如用户进入后立即访问 H5 页面,如果这时候骨架屏还没有 ready,那么体验会降级到未优化前,这块我们后面会继续想办法进行优化。

5.3. 稳定性监控

通过 mPaaS 埋点 SDK 统计埋点信息,我们通过 mPaaS 监控后台能够看到一些基础数据,同时会把这些埋点数据流到内部监控大盘、钉钉群日常播报,也可以通过语雀实时上报通道进行ErroeBoundary/JSException实时播报,具体如下所示:

5.4. 研发 & 交付效率

语雀 App 在三层架构在实践下,较好的保证了的研发效率和交付效率。我们每周会发布一个内测版本,小功能能够快速滚动内测发布,内测用户也能够积极反馈。同时,我们每两周会发布一个小版本,每个月可以发布一个大版本。

现在整个架构的 Native 侧是重服务轻UI:Android和 iOS Native 实现的模块,基本上全是服务型的代码,通过JSBridge 暴露给上层。ReactNative 侧则是重 UI 轻服务:它只是做一个单纯的渲染层,Native 服务层接口 & 能力稳定之后,很少变动,上层就可以用 ReactNative 随着业务快速滚动开发交付。

5.5. 交付质量

交付质量这块,作为 CI/CD 的一部分,我们的 QA 同学提供了非常充分的自动化测试,每天会进行自动化测试巡检,用最新的包进行滚动测试验证,同时出具报告,有问题的情况会第一时间通知开发同学修复。如下图是自动化测试报告的截图:

  6. 总结 & 后续计划

日常开发中,可能需要一些 Native 组件,常见的情况是,社区没有或者社区组件能力不匹配业务需求。比如语雀 App 底层的基础服务、UI 组件都是由 Native 提供的,语雀现在提供了 99 个Native JSBridge。还有一些 UI 组件需要 Native 提供双端的实现,比如 NebulaWebView、ImagePreviewer、PullToRefreshView 等。

最后做一个简单的小结,我们提出三层架构的最主要思路有三个具体的思考点:一是结合业务的特点和技术储备去设计整体架构;另外就是可以尽量复用手头或者已有的组件或者社区组件;第三个是结合语雀现有的代码情况和团队整体研发匹配情况。

在整个过程中,我们提出的同构设计思路和开发模式,在开发实践中得以充分使用,可以说到了较好的验证。后续我们将持续关注性能和体验,比如将跟进社区,升级 Hermes 引擎和 Fabric 架构,同时持续进行性能优化。欢迎下载语雀 App 进行体验!(地址:https://www.yuque.com/download


相关文章
|
8天前
|
消息中间件 API 数据库
构建微服务架构的后端实践
【7月更文挑战第7天】本文将深入探讨微服务架构在后端开发中的应用,从微服务的理论基础出发,逐步引导读者了解如何在实际项目中设计、部署和维护一套高效的微服务系统。我们将通过一个虚构的电商平台案例,展示微服务架构的搭建过程,包括服务拆分、数据库设计、通信机制选择、容错与服务治理等关键步骤,旨在为后端开发者提供一份实战指南。
83 4
|
4天前
|
运维 Kubernetes 监控
深入解析微服务架构的演进与实践
本文旨在探究微服务架构从诞生到成熟的发展历程,分析其背后的技术推动力和业务需求,并结合具体案例,揭示实施微服务过程中的挑战与解决策略。通过对微服务架构与传统单体架构的对比,阐明微服务如何优化现代应用开发流程,提高系统的可扩展性、可维护性和敏捷性。
14 0
|
1天前
|
运维 Cloud Native Devops
云原生架构的演进与实践
【7月更文挑战第14天】在数字化转型的浪潮中,云原生技术作为推动现代软件发展的新引擎,正引领着企业IT架构的变革。本文将探索云原生架构的核心概念、关键技术及其对企业IT策略的影响,同时通过案例分析,揭示云原生在实际应用中的成效与挑战,为读者呈现一幅云原生技术发展与应用的全景图。
14 4
|
12天前
|
运维 监控 Devops
深入理解微服务架构:从理论到实践
随着数字化转型的加速,微服务架构已成为现代软件开发的重要趋势。本文将通过数据导向和科学严谨的分析方法,探讨微服务架构的核心概念、优势与挑战,并结合逻辑严密的案例研究,揭示如何在实际项目中有效实施微服务。我们将引用权威研究和统计数据,深入解读微服务对企业技术栈的影响,同时提供一套完整的微服务实施策略,旨在帮助读者构建更加灵活、可维护的软件系统。
|
12天前
|
设计模式 安全 持续交付
探索微服务架构下的后端开发实践
在现代软件开发领域,微服务架构已成为一种流行的设计模式,它通过将应用程序分解为一组小的服务来促进敏捷开发和可扩展性。本文深入探讨了微服务架构的核心概念、技术选型、数据一致性挑战以及安全性考虑,旨在为后端开发人员提供一份全面的微服务开发指南。文章结合最新的研究成果和业界最佳实践,分析了微服务架构的优势和面临的挑战,并提出了相应的解决方案。读者将了解到如何在实际项目中应用微服务原则,以及如何克服实施过程中的技术和组织障碍。
|
13天前
|
存储 设计模式 监控
后端开发中的微服务架构实践与挑战
在数字化时代背景下,微服务架构作为现代软件工程的典范,被广泛应用于后端开发领域。本文将深入探讨微服务架构的核心概念、实施策略及其面临的主要挑战,同时提供一系列针对性的解决方案和最佳实践。通过引用最新的研究成果和行业案例,文章旨在为后端开发者提供一个全面的微服务架构指南,帮助他们在构建和维护复杂系统时做出明智的决策。
21 1
|
1天前
|
Cloud Native 安全 Devops
云原生架构的演进与实践
【7月更文挑战第14天】随着数字化转型的深入,企业对信息技术系统的灵活性、可扩展性及快速迭代能力的要求日益提高。云原生技术作为应对这些挑战的有效方案,其核心价值在于通过容器化、微服务架构、持续集成和持续部署等实践,实现应用的快速开发、部署与自动化管理。本文将探讨云原生架构的关键组成部分、实施策略以及面临的挑战,旨在为读者提供一份云原生技术的实践指南,帮助理解和应用这一前沿技术,推动企业的IT架构向更加灵活、高效和可靠的方向发展。
|
10天前
|
消息中间件 存储 监控
构建支持实时数据处理的返利App系统架构
构建支持实时数据处理的返利App系统架构
|
11天前
|
中间件 BI 测试技术
【实践篇】领域驱动设计:DDD工程参考架构
领域驱动设计(DDD)参考架构旨在为团队提供DDD实践的起点,强调业务与技术的分离,考虑多种架构风格如分层、六边形等。它包括多限界上下文结构,每个上下文内有应用层(不含领域逻辑)、领域层(含领域模型和事件)和网关层。接入层负责外部请求的处理,业务层协调不同上下文。组件包括Start(启动)、Common(通用)、API、Facade、Application Service、External API、Query、Domain和Gateway,各组件有明确的职责和依赖关系,如Gateway处理技术细节并作为系统与外部的接口。架构设计是多因素权衡,适应实际工程需求。
|
13天前
|
Cloud Native 持续交付 云计算
云原生架构的演进与实践
随着云计算技术的不断成熟,云原生架构逐渐成为企业数字化转型的核心驱动力。本文将深入探讨云原生架构的发展历程、关键技术组件以及在实际应用中的优化策略。通过分析最新的行业数据和案例研究,揭示云原生技术如何推动业务敏捷性、提升系统可靠性和降低运营成本。