2016年12月20日,来自Uber Engineering的家伙发表
了一篇有关新架构
的文章 (这是该文章在Hub上的
翻译 )。 我向您介绍文档主要部分的翻译。
RIBs的结构是什么?
RIBs是Uber的跨平台架构框架。 它是为具有大量嵌入式状态的大型移动应用程序设计的。
在开发此框架时,Uber工程师遵循以下原则:
- 支持在不同平台上开发的人们之间的协作: Uber应用程序的绝大多数复杂部分在iOS和Android上相似。 RIB为Android和iOS提供通用的开发模式。 使用RIB时,iOS和Android上的工程师可以共享一种共同开发的功能架构。
- 最小化全局状态和决策:全局状态更改可能导致无法预测的行为,并且可能无法知道程序代码中的这些或那些更改将导致什么。 基于RIB的体系结构鼓励在高度隔离的RIB的深层次结构中封装状态,以避免全局状态出现问题。
- 可测试性和隔离性:类应该简单以便能够编写单元测试,并且还具有隔离的原因(请参阅SRP )。 各个RIB类具有不同的职责(例如,路由,业务逻辑,表示逻辑,创建其他RIB类)。 此外,父RIB的逻辑基本上与子RIB的逻辑分开。 这样可以轻松测试RIB类并减少系统组件之间的依赖性。
- 用于生产性开发的工具:如果没有可靠的工具来支持体系结构,那么借用非平凡的体系结构模式可能会导致应用程序增长。 RIBs体系结构附带用于创建代码,静态分析和运行时集成的IDE工具,从而提高了大小团队的开发人员的生产力。
- 开放性-封闭性的原则:如果可能的话,开发人员应在不更改现有代码的情况下添加新功能。 使用RIB时,可以在许多地方看到此规则的实现。 例如,您可以附加或创建一个复杂的子RIB,该子RIB要求依赖于其父RIB,而父RIB几乎没有变化。
- 围绕业务逻辑构建:应用程序的业务逻辑结构不应严格反映用户界面的结构。 例如,为了促进视图的动画和性能,视图层次结构可能小于RIB层次结构。 或者,单个RIB功能可以控制在用户界面上不同位置出现的三个视图的外观。
- 确切的合同:必须使用在编译时检查的合同声明要求。 如果不满足自己的依赖关系以及来宾依赖关系,则不应编译类。 RIBs体系结构使用ReactiveX表示来宾依赖关系,使用类型安全的依赖关系注入( DI )系统表示类依赖关系,以及许多其他DI功能来帮助创建数据不变式。
组成元素RIB
如果您以前使用过
VIPER架构,那么组成RIB的类将对您来说很熟悉。 RIB通常包含以下元素,每个元素都在其自己的类中实现:

牵连器
交互器包含业务逻辑。 在此类中,订阅Rx通知,做出有关更改状态,存储数据和附加子RIB的决定。
在Interactor中执行的所有操作应限于其生命周期。 Uber创建了一个工具包,以确保仅通过主动交互才能执行业务逻辑。 这样可以防止停用交互器,但是Rx订阅仍然会触发并导致对业务逻辑或用户界面状态的不必要更新。
路由器
路由器监视来自Interactor的事件,并将这些事件转换为附加和分离子RIB。 存在路由器的原因很简单,其中三个原因:
- 路由器作为被动对象存在,从而简化了测试复杂交互器逻辑的过程,而无需为子交互器创建存根或以其他方式照顾它们的存在。
- 路由器在父子交互器之间创建了一个额外的抽象层。 这使交互器之间的同步通信更加复杂,并鼓励使用Rx通信而不是RIB之间的直接通信。
- 路由器包含简单且重复的路由逻辑,否则将在Interactor中实现。 将样板代码移植到路由器可帮助Interactor小型化,并更加专注于核心RIB业务逻辑。
建造者
为了为RIB中包括的所有类创建实例,以及为子RIB创建Builders实例,需要Builder。
在Builder中突出显示类创建逻辑将增加对在iOS中创建存根的功能的支持,并使其余的RIB代码对DI实现的细节不敏感。 Builder是RIB唯一需要了解项目中使用的DI系统的部分。 通过实现另一个Builder,您可以使用其他DI机制重用项目中的其余RIB代码。
主讲人
Presenter是无状态类,可将业务模型转换为表示模型,反之亦然。 它可用于促进测试模型视图转换。 但是,这种翻译通常很琐碎,以至于不能证明创建单独的Presenter类是合理的。 如果Presenter未完成,则视图模型的转换将由View(控制器)或Interactor负责。
视图(控制器)
视图创建并更新用户界面。 它包括创建和安排界面组件,处理用户交互,用数据填充用户界面组件以及动画。 视图被设计为尽可能“哑”(被动)。 它们只是显示信息。 通常,它们不包含任何应编写单元测试的代码。
组成部分
该组件用于管理RIB依赖性。 它可以帮助Builder实例化组成RIB的其他类。 组件提供对创建RIB所必需的外部依赖关系的访问,以及对RIB本身创建的自身依赖关系的访问,并控制从其他RIB对其的访问。 父RIB的组件通常嵌入在子RIB-Builder中,以使子RIB可以访问父RIB的依赖项。
国家管理
应用程序的状态主要由当前连接到RIB树的RIB管理和表示。 例如,当用户在简化的共同旅行应用程序中经历不同的状态时,该应用程序将附加和分离以下RIB:

RIB仅在其权限范围内做出国家决策。 例如,LoggedIn RIB仅决定在请求和OnTrip之类的状态之间进行转换。 当我们在OnTrip屏幕上时,他没有对系统的行为做出任何决定。
不能通过添加或删除RIB保存所有状态。 例如,当用户配置文件设置更改时,RIB不会绑定或断开连接。 通常,我们将此状态保存在不可变模型流中,该模型在更改零件时会重新发送值。 例如,用户名可以存储在ProfileDataStream文件中,该文件位于LoggedIn的权限之下。 只有网络响应才能对此流进行写访问。 我们正在传递一个接口,该接口提供对DI图下这些线程的读取访问。
RIB中没有什么是RIB状态的最终真理。 这与开箱即用地提供了更精巧的框架(例如React)的事实形成了对比。 在每个RIB的上下文中,您可以选择促进单向数据流的模式,或者可以使业务逻辑状态和视图状态暂时偏离规范,以便利用该平台的高效动画框架。
RIB之间的交互
当Interactor做出业务逻辑决策时,可能需要通知其他RIB有关事件,例如数据的完成和发送。 RIB框架不包括在RIB之间传输数据的任何单一方法。 但是,此方法旨在促进某些常见模式。
通常,如果与子RIB的连接断开,则我们将这些信息作为事件在Rx流中传输。 或者,可以将数据作为参数包含在子RIB的
build()方法中,在这种情况下,此参数在子生命周期内变为不变。

如果连接沿RIB树到达父RIB交互器,则此连接是通过侦听器接口进行的,因为父RIB的生命周期比子RIB更长。 父RIB或其DI图上的某些对象实现了侦听器接口,并将其放在其DI图上,以便其子RIB可以对其进行调用。 使用此模板向上游传输数据,而不是让父级RIB直接订阅其子级RIB的Rx流具有多个优点。 它可以防止内存泄漏,允许您编写,测试和维护父RIB,而无需知道将哪些子RIB附加到它们,并且还减少了附加/分离子RIB所需的操作。 Rx流或侦听器不需要使用此附加子RIB的方法注销或重新注册。

RIB工具包
为了确保RIB体系结构在应用程序中的顺利实现,Uber工程师创建了工具来简化RIB的使用,并使用通过实现RIB体系结构创建的不变式。 该工具包的源代码是部分开放的,并在
示例中进行了提及(请参阅右侧部分-大约每秒钟)。
该工具箱当前是开源的,包括:
- 代码生成器:用于创建新RIB和相关测试的IDE插件。
- NPE静态分析器(Android): NullAway是一种静态分析工具,可让您忘记NullPointerExceptions。
- 静态自动放置分析器(Android):防止RIB中最常见的内存泄漏。
Uber计划在未来开源的工具包:
- 静态分析器可防止RIB中的各种内存泄漏
- 在程序执行期间将RIB与内存泄漏检测器集成
- (Android)注释处理器,可简化测试
- (Android)RxJava静态分析器,为RIB提供与主线程相同的视图
聚苯乙烯
我们
sports.ru真的很喜欢Uber工程师的方法,因为 很多次,我们遇到了本文描述的所有体系结构问题。 尽管有合理性,RIB也有许多缺点,例如,进入体系结构的门槛很高。 在接下来的文章中,我们将详细分析架构的优缺点,至少计划了两项-iOS和Android。 对于那些想立即进入RIB的人,右侧的Wiki页面上有一列包含英语课程。 我个人注意到,体系结构显然是经过长时间的技术讨论而诞生的,并收集了为当前可用的移动应用程序构建体系结构的最佳实践。 最后,有点公关-我们在
sports.ru上也喜欢技术讨论,经常为同事举办技术研讨会,定期学习新技术,并且总的来说,我们气氛很好。 因此,如果您想成为我们团队的一员,
欢迎您 !