引言
时代演进, 技术也随之发展. 到今天, App 已然成为绝大多数互联网企业用来获取用户的核心渠道. 与此同时, 伴随着业务量的增长, 愈来愈大, 愈来愈多的 App 也在不断地, 持续地挑战着每一个移动端研发人员的知识深度, 而我们的移动端技术人员也在这个不断接受挑战的过程中, 成就了今天的移动互联网时代. 饿了么移动 App 就是这样一个挑战, 多用户量, 多业务量, 在接受着更多更挑剔用户的同时, 默默地, 不断地演进着移动端的架构.
1 MVC
我们常说, 脱离业务谈架构就是纯粹的刷流氓. 饿了么移动 App 的发展也是其业务发展的一面镜子.
在饿了么业务发展的早期, 移动 App 经历从无到有的阶段. 为了快速上线抢占市场, 传统移动 App 开发的 MVC 架构成了 "短平快" 思路的首选:
图 1 MVC 架构
这种架构以层次结构简单清晰, 代码容易开发而被大多数人所接受.
在 MVC 的体系架构中, Controller 层负责整个 App 中主要逻辑功能的实现; Model 层则负责数据结构的描述以及数据持久化的功能; 而 View 层作为展现层负责渲染整个 App 的 UI. 分工清晰, 简洁明了; 并且这种系统架构在语言框架层就得到了 Apple 的支持, 所以非常适用于 App 的 startup 开发.
然后, 这种架构在开发的后期会由于其超高耦和性, 从而造就庞大 Controller 层, 而这也是一直被人所诟病. 最终的 MVC 都从 Model-View-Controller 走向了 Massive-View-Controller 的终点.
2 Module Decoupled
"短平快" 的 MVC 架构帮助饿了么移动 App 快速抢占了市场. 而随着代码量的不断增加, 臃肿的 Controller 层也在渐露头角; 而业务上, 饿了么移动 App 也从单一 App 发展为多 App 齐头并进的格局. 这时候, 如果降低耦合, 复用已有模块成了架构的第一要务.
架构中, 模块复用的第一要求便是代码的功能组件化. 组件化意味着拥有独立功能的代码从系统中进行抽象并剥离, 再以 "插件" 的形式插回原有系统中. 这样剥离出来的功能组件, 便可以供其他 App 进行使用, 从而降低系统中模块与模块之间的耦和性; 也同时提高了 App 之间代码的复用性.
饿了么移动对于组件有两种定义: 公有组件和业务组件. 公有组件指的是封装得比较好的一些 SDK, 包括一些第三方组件和自己内部使用的组件. 如 iOS 中最著名的网络 SDK AFNetworking,Android 下 OKHttp, 都是这类组件的代表. 而对于业务组件, 则定义为包含了一系列业务功能的整体, 例如登录业务组件, 注册业务组件, 即为此类组件的典型代表.
对于公有组件, 饿了么移动采取了版本化的管理方式, 而这在 iOS 和 Android 平台上也早有比较成熟的解决方案. 例如, 对于 iOS 平台, CocoaPods 基本上成为了代码组件化管理的标配; 在 Android 平台上, Gradle 也是非常成熟和稳健的方案. 采用以上管理工具的另一个原因在于, 对企业开发而言, 代码也是一种商业机密. 基于保密性的目的, 支持内网搭建私有服务器成为了必需. 以上的管理工具都能够很好地支持这些操作.
对于业务的组件化, 我们采取了业务模块注册机制的方式来达到解耦合的目的. 每个业务模块对外提供相应的业务接口, 同时在系统启动的时候向 Excalibur 系统注册自己模块的 Scheme(Excalibur 是饿了么移动用来保存 Scheme 与模块之间映射的系统, 同时能根据 Scheme 进行 Class 反射返回). 当其他业务模块对该业务模块有依赖时, 从 Excalibur 系统中获取相关实例, 并调用相应接口来实现调用, 从而实现了业务模块之间的解耦目的.
而在业务组件, 即业务模块的内部, 则可以根据不同开发人员的偏好, 来实现不同的代码架构. 如现在讨论得比较火的 MVVM, MVP 等, 都可以在模块内部进行而不影响整体系统架构.
这时候的架构看起来更像是这样:
图 2 EMC 架构
这种 E(Excalibur)M(Modules)C(Common) 架构以高内聚, 低耦合为主要的特点, 以面向接口编程为出发点, 降低了模块与模块之间的联系.
该架构的另外一大好处则在于解决了不同系统版本的兼容性问题. 这里举 iOS 平台下的 webView 作为例子来进行说明. Apple 从 iOS8 系统开始提供了一套更好的 Web 支持框架 --WebKit, 但在 iOS7 系统下却无法兼容, 从而导致 Crash. 使用此类架构, 可以在 iOS7 系统下仍然注册使用传统的 WebView 来渲染网页, 而在 iOS8 及其以上系统注册 WebKit 来作为渲染网页的内核. 即避免了 Apple 严格的审核机制, 又达到了动态加载的目的.
3 Hybrid
移动 App 的开发有两种不同的路线, Native App 和 Web App. 这两种路线的区别类似于 PC 时代开发应用程序时的 C/S 架构和 B/S 架构.
以上我们谈到的都属于典型的 Native App, 即所有的程序都由本地组件渲染完成. 这类 App 优点是显而易见的, 渲染速度快, 用户体验好; 缺点同时也十分突出: 出现了错误一定要等待下一次用户进行 App 更新才能够修复.
Web App 的优点恰好就是 Native App 的缺点所在, 其页面全部采用 H5 撰写并存放在服务器端. 每次进行页面渲染时都从服务器请求最新的页面. 一旦页面有错误服务器端进行更新便能立刻解决. 不过其弊端也容易窥见: 每次页面都需要请求服务器, 造成渲染时等待时间过长, 从而导致的用户体验不够完美, 并且性能上较 Native App 慢了 1-2 个数量级; 与此同时还会导致更多的用户流量消耗. 另一个缺点则在于, Web App 在移动端上调用本地的硬件设备存在一定的不便. 不过这些弊端也都有相应的解决方案, 如 PhoneGap 将网页提前打包在本地以减少网络的请求时间; 同时也提供一系列的插件来访问本地的硬件设备. 然而, 尽管如此, 其渲染速度上还是会稍微存在一定的差距.
Hybrid App 则是综合了二者优缺点的解决方案. 饿了么移动对于此二类 App 的观点在于, 纯粹展示性的模块会更适合使用 Web 页面来达到渲染的目的; 而更多的数据操作性, 动画渲染性的模块则更适合采用 Native 的方式.
基于之前的 EMC 架构, 我们将部分模块重新进行了架构:
图 3 Hybrid-EMC 架构
Hybrid-EMC 架构中, Web 作为一个子模块, 注册加入到整个系统中, 从而实现让业务上需要快速迭代的模块达到实时更新的效果.
4 React-Native & Hot Patch
经过这些年的业务发展, Hybrid 提供的展示界面更新方案也逐渐地无法满足 App 更新迭代的需要. 因此越来越多的动态部署的方案被提了出来, 比如 iOS 下的 JSPatch, waxPatch,Android 下的 Dexpose,AndFix, ClassLoader, 都是比较成熟 Hot Patch 动态部署解决方案. 这些方案的思路都是通过下载远程服务器的代码来动态更新本地的代码行为.
React-Native 则属于另一种动态部署的方案, 其核心原理在于通过 JavaScript 来调用本地组件进行界面的渲染.
而饿了么移动 App 发展到今天, 各个 App 综合用户量已经过亿. 因此一个非常小的 Bug 所带来的问题都可能会直接影响到几万人的使用. 为了保证 App 的稳定性和健壮性, Hot Patch 方案也就成了当下最有待解决的问题.
根据 80% 的用户访问 20% 页面这一 80/20 原则, 保证这 20% 访问最频繁的页面的稳定性就是保证了 80% 的 App 的稳定性. 因此, 饿了么移动对于部分访问最频繁的模块进行了 React-Native 备份. 当这部分页面出现问题时, App 可以通过服务器的配置, 自动切换成 React-Native 的备份页面; 而与此同时开发人员开发一个小而精的 Hot Patch 来修复出现的问题. 当 Hot Patch 完成修补后, 再切换回 Native App 的原生功能.
这时候的架构看起来会像是这样:
图 4 HotPatch-EMC 架构
HotPatch-EMC 的架构主要目标在于解决移动 App 的稳定性问题. 通过 RN 与 Native 的主备, 可以减少系统 App 出错带来的失误成本.
5 结语
我们都知道, 对于软件工程来说, 这世上没有银弹. 对于架构而言其实也非常的适用. 业务的不断更新, 带来了饿了么移动 App 架构的不断演进.
架构没有真正的好坏之分, 只要适用于自己的业务, 就是好的架构!
来源: https://juejin.im/post/5bae32cfe51d450e5e0c8c8f