Uber’s RIB中文翻译
前言
我们现在的团贷App是使用VIPER框架进行开发,之前的VIPER学习中接触过Uber的VIPER框架,它是业务驱动型的。随着Uber的框架的开源,我们可以跟随大厂学习他们是怎么使用以及优化VIPER框架,从而加深自己的VIPER框架理解。
RIB的产生
此段文字翻译取自Uber官方博客。参考链接 Engineering the Architecture Behind Uber’s New Rider App
为什么Uber重新开始
Uber基于一个简单的概念:按下按钮,乘坐。一开始是请求优质黑车,现在提供一系列的产品,协调着每天成千上万的搭车在上百城市的车。我们需要重新定义我们的移动框架来响应和支持2017以及将来的真实情况。
但是从哪里开始呢?很好,我们回到我们开始的时候2009:一无所有,我们决定完全重写以及重新设计我们的app。不被我们以前的庞大代码库以及设计来约束我们,我们有了很大的自由,而不需要妥协。结果就是你现在看到的新app,同时实现了跨iOS和android平台的移动框架。来学习我们的框架,看我们为什么需要它,来看它怎样达到我们的目标的!
动机:到哪里
连接搭车者到请求的运输路线依然是我们Uber的驱动思想,我们的产品已经演化的越来越大,我们原有的移动框架已经很难支撑。工程方面的挑战以及技术债务已经在我们扩展app适应新特性的过程中积累了很多年。我们的路程模块变得越来越大,可测试性变得很差。
一些小更改有可能会破坏App的其他部分,试验是会包含很多隐藏的故障和问题,这对我们未来扩展会有抑制作用。为了保持高质量的用户体验,我们需要重新出发,为我们当下负责,思考未来。
新的App应该对用户简单,对每天开发新功能以及改进应用的工程师简单。怎么为这些不同的群组重写App呢,我们的两个主要目的是,提高我们核心搭车者的可行性体验,同时允许在一系列产品线上进行追本溯源的试验。
RIB是用来做什么的
RIB是Uber跨平台的架构框架。这个框架是专门为了包含大量状态的大型App设计。
当为Uber设计这个框架的时候,我们遵循一下原则:
- 鼓励跨平台合作
- 最小化全局状态和决策
- 可测试性以及独立性
- 开发人员生产力工具
- 开闭原则
- 围绕业务逻辑构成
- 显式协议
RIB的组成部分
Interactor
interactor 包含业务逻辑。这里你需要完成RX订阅,状态更改,存储数据,确定哪些RIB子模块需要加上 。
所有interactor的操作必须只限于它的生命周期。我们创建了工具来确保业务逻辑的执行仅在interactor活动的时候。这防止了一些未处于活动状态下的场景,触发一些不必要的业务逻辑更新以及UI状态更新等。
Router
router听从于interactor和转换它的输出来做添加以及删除子RIBS.Router存在主要有三个原因:
- routers作为一个谦卑的角色,很容易测试复杂的interactor逻辑,而不需要mock子interactors,或者是关心他们的存在
- routers创建一个额外的抽象层在父interactor和子interactor之间。这使得interactors之间的同步通讯比较难,鼓励使用响应式通讯,而不是直接绑定RIBS
- routers包含简单的和重复的路由逻辑,它原本被interactor实现。提出这个标准有助于保持interactor轻巧以及更专注于RIB的主要业务逻辑。
Builder
Builder的责任是组合所有RIB组成类部分,以及每个子RIB的Builder。
在Builder中分离类创建逻辑,添加Mock支持,这使得剩下的RIB代码与依赖注入实现的细节没什么不同。Builder是项目中唯一需要注意依赖注入系统使用的部分。通过完成一个不一样的Builder,它是可以使用不同的DI策略来重用RIB剩下的代码。
Presenter
Presenter是将业务逻辑模型转化成视图模型以及其他。它们可以用来促进视图模型测试的转换。一般来说,这样的转换是比较琐碎的,因为它不保证专用的Presenter创建。如果省去了Presenter,转换视图模型变成了View(Controller)或者Interactor的责任
View(Controller)
ViewController创建和更新UI,这个包含实例化以及布局UI元素,处理用户的交互,填充数据到UI以及动画等。视图是尽可能设计得”愚蠢”。它们仅只是展示信息。一般来说,它们并不包含任何需要单元测试的代码。
Component
组件是用来管理RIB依赖。协助Builder实例化其他用来组成RIB的单元。组件提供那些需要被用来创建RIB的外部依赖访问以及拥有通过自身创建RIB的依赖,并控制其他RIBS访问权限。父RIB的组件经常用来注入到子RIB的Builder来提供访问父RIB中的依赖。
状态管理
应用程序状态是被当前添加到RIB树上的RIB来管理和展现的。例如,用户在一个简洁易用共享搭车切换状态,应用的添加或者删除以下的RIB。
RIB只有在他们的范围内做状态决策。例如,LoggedIn RIB只做 Request和 OnTrip两者间的状态决策。当我们在OnTrip模块的时候它并不作任何相关响应。
并不是所有状态都能够存储,当我们添加或者删除RIB的时候。例如,当用户个人信息更改的时候,没有RIB添加或者删除。一般来说,我们在可变模型中存储这个状态,然后重新分发值等详情修改的时候。例如,用户名存储在ProfileDataStream,它是在LoggIn的范围内。只有网络响应可以写入该流。我们传递这些流的可读接口到DI图下。
RIB并没有强制使用一种方式。相对于其他可选框架,例如React,我们已经集成。在每一个RIB的上下文,你可以选择其他模式来提升非单向数据流,或者你可以允许业务状态和视图状态临时传递从而更有效的使用平台动画框架。
RIB之间的通讯
当Interactor做出一个业务逻辑决定,它可能需要通知其他RIB事件,例如完成或者传递数据。RIB并不包含一条单一路径来在RIB之间传递数据。尽管如此,它能够应对常见的模式。
一般来说,如果通讯是下发到子RIB,我们传递信息作为分发点到RX流中。或者来说,数据可以作为子RIB Build方法的参数,在这种情况下,参数作为一个不变量贯穿于子RIB的生命周期中。
如果通讯是RIB 树往上到父RIB的interactor,这个通讯是通过一个监听接口来完成,这样父节点可以与子节点脱离。父RIB,或者其他在DI图的对象,通过实现监听接口以及放置到它的DI图,这样子RIB能够触发它。使用这个模式来向上传递数据而不是通过父RIB来订阅子RIBRx流的方式有着很大的效益。它防止内存泄露,允许父节点重写,测试,以及维护,而不需要知道哪个子节点附加了,可以减少一系列需要添加、删除子RIB的动作。使用这种方式,没有Rx流或者监听者需要注册或者重新注册。
RIB例子学习
tutorial 1
…
tutorial 2
…
tutorial 3
…
tutorial 4
…
总结
…
Tips
父模块与子模块通讯
向上通讯,子模块回调父模块 (Listener)
向下通讯,父模块监听子模块 (Rx)
Builder模式
https://zh.wikipedia.org/zh-hans/%E7%94%9F%E6%88%90%E5%99%A8%E6%A8%A1%E5%BC%8F
观察者模式
https://zh.wikipedia.org/zh-hans/%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F
依赖注入
https://zh.wikipedia.org/wiki/%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5
依赖反转
https://zh.wikipedia.org/wiki/%E4%BE%9D%E8%B5%96%E5%8F%8D%E8%BD%AC%E5%8E%9F%E5%88%99
参考
https://juejin.im/post/5a9f88466fb9a028e46e308a#comment
https://eng.uber.com/new-rider-app/
https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html