Uber为何重头开始
Uber基于一个简单的概念:按一个按钮,就能叫车。该公司最初只是想要高档黑色轿车,现在却提供了一系列产品,协调数百个城市每天数百万次的出行。我们需要重新定义我们的移动架构,以反映和支持2017年和未来的现实。
但是从哪里开始呢?好吧,我们回到了2009年开始的地方:一无所有。我们决定彻底重写重新设计我们的骑手的应用。没有被我们广泛的CodeBase封锁,之前的设计选择给了我们我们否则会妥协的自由。结果是你今天看到的时尚新应用程序它在iOS和Android上实现了一个新的移动架构。继续读下去,了解为什么我们觉得需要创建这个名为rilet的新架构模式,以及它如何帮助我们实现我们的目标。
动机:去哪儿?
虽然将乘客与按需运输连接起来仍然是优步背后的驱动理念,但我们的产品已经发展成更大的产品,而我们最初的移动架构无法跟上。工程挑战和技术债务积累多年,因为我们扩展了附加应用程序,以适应新的功能。添加如Uberpool.那预定的游乐设施促销车辆视图引入了复杂性。我们的旅行模块变大,难以测试。结合小型变化耗尽了破坏应用的其他部分的可能性,使实验充满了抵押品调试,抑制了我们的未来增长的步伐。为了保持所有用户的高质量优质的优质体验,我们需要一种方法来重新夺回我们开始的地方的简单性,同时考虑到我们今天以及我们将来要去的地方。
新应用程序必须对乘客和优步工程师来说都很简单,他们每天都在开发改进和功能。为这些不同的群体重写应用程序,我们的两个主要目标是增加我们的核心车手体验的可用性,并允许在一组产品轨道内进行激进的实验。
可靠性是核心
从工程方面来说,我们正努力使优步的可靠性在99.99%的时间里成为我们的核心用户体验。达到99.99%的可用性意味着我们每年只能累计停机一个小时,每周停机一分钟,或者每10,000次运行出现一次故障。
为了实现这个目标,新的体系结构定义并实现了一个核心和可选代码的框架。核心代码——注册、执行、完成或取消旅行所需的一切——必须运行。对核心代码的更改和添加都要经过严格的审查过程。可选代码的审查不那么严格,可以在不影响优步核心业务的情况下关闭。这种鼓励代码隔离的做法允许我们尝试新特性,并在它们不能正常工作时自动关闭它们,而不会影响骑行体验。
面向未来的Rails
我们需要一个平台,在这个平台上,100个不同的程序团队和数千名工程师可以快速构建高质量的功能,并在不损害核心体验的情况下对骑手应用程序进行创新。所以我们给我们的新移动架构提供了跨平台兼容性,让iOS和Android工程师可以在一个统一的平台上工作。
从历史上看,在iOS和Android上运送最佳应用程序涉及架构,图书馆设计和分析的不同方法。但是,新的架构致力于在两个平台上使用相同的最佳模式和实践。这使我们能够利用两个平台的学习机会。而不是两次制作同样的错误,因为我们为每个平台有单独的团队,一个平台的课程可以先发制在另一个平台上的问题。因此,iOS和Android工程师可以更轻松地协作,并并行地进行新功能。
虽然存在平台可以并且应该发散(例如,UI实现)的情况,但IOS和Android移动平台都从一致的位置开始。平台分享:
- 核心架构
- 类名称
- 业务逻辑单元之间的继承关系
- 业务逻辑是如何划分的
- 插件点(名称、存在、结构等)
- 连锁反应性编程
- 统一平台组件
为了实现平台之间的共同蓝图,我们新的移动架构需要清晰的组织和业务逻辑、视图逻辑、数据流和路由的分离。这样的架构有助于克服复杂性,简化可测试性,从而提高工程生产率和用户可靠性。我们在其他架构模式上进行了创新以实现这一点。
从MVC到ribles
考虑到我们的两个目标,我们检查了可以改进旧体系结构的地方,并研究了向前发展的选项。我们从优步开始时继承的代码库MVC模式。我们特别研究了其他模式毒蛇,我们最终用它来制作排骨。rilet的核心创新在于路由是由业务逻辑(而不是视图逻辑)指导的。如果您不熟悉MVC和VIPER,请阅读一些关于现代iOS架构模式的文章,然后再回头看看优步采用这些技术的利弊。
MVC(模型-视图-控制器)
之前的骑手应用是四年前由几位工程师开发的。虽然MVC模式在当时是有意义的,但它在规模上是不可管理的。当我们之前的骑手应用程序和开发团队发展到只有几百人的时候,我们亲眼目睹了MVC是如何不能和我们一起发展的。具体来说,有两大问题领域:
首先,成熟的MVC架构经常面临斗争巨大的视图控制器。例如,由于处理太多责任并修改。
其次,MVC架构具有缺乏测试的脆弱更新过程。我们试验很多,向用户推出新功能。这些实验归结为否则陈述。每当有具有许多功能的类时,如果IF - else语句相互构建,使其附近不可能理解,更不用说测试。此外,作为RequestViewController和TripViewController等巨大的组成代码,对应用程序的更新成为一个脆弱的过程。想象一下,改变和测试嵌套如果else实验的所有可能组合。由于我们需要实验继续添加新功能并增长优步的业务,因此这种架构不可扩展。
沿途:VIPER
在考虑MVC的替代方案时,我们受到了VIPER的启发可以使用作为一个应用干净的架构到iOS应用程序。Viper为MVC提供了一些关键优势。首先,它提供了更多的抽象。Presenter包含呈现逻辑,将业务逻辑粘贴为View Logic。互操作器处理纯粹的数据操作和验证。这包括使服务呼叫对后端进行操作,例如登录并请求旅行。最后,路由器启动转换,例如将用户从家中获取到确认屏幕。其次,通过Viper方法,演示者和互动器是普通的旧对象,所以我们可以使用简单的单元测试。
但我们也发现了VIPER的一些缺点。它的ios特有结构意味着我们必须为Android做出权衡。它的视图驱动应用程序逻辑意味着应用程序状态由视图驱动,因为整个应用程序都锚定在视图树上。被认为是操作应用程序状态的交互者执行的业务逻辑总是要经过演示者,因此,泄漏了业务逻辑。最后,使用紧密耦合的视图树和业务树,很难实现只包含业务逻辑或视图逻辑的节点。
虽然VIPER为我们使用的MVC模式提供了显著的改进,但它并没有完全满足Uber的需求和目标,即一个具有清晰模块化的可伸缩平台。因此,我们回到了画板上,看看如何开发一种架构模式,既能抓住VIPER的优点,又能容纳VIPER的缺点。我们的结果是ribles。
riblets:优步的骑手应用程序架构
在我们的新架构模式中,逻辑类似地被闯入小型,独立可测试的作品,每个都具有单一的目的,遵循单一责任原理。我们使用RIBLET作为这些模块化件,整个应用程序被构造为RIBLET的树。
riblets及其组件
使用RIBLETS,我们将六种不同的组件授予六种不同的组件,以进一步抽象业务和视图逻辑:
肋骨与VIPER和MVC的区别是什么?路由是由业务逻辑而不是视图逻辑指导的。这意味着应用程序是由信息流和正在做出的决策驱动的,而不是由表示驱动的。在优步,并不是所有的业务逻辑都与用户看到的视图相关。我们可以为每一块业务逻辑都有不同的rilet,这给我们提供了有意义的逻辑分组,并且很容易进行推理,而不是将业务逻辑集中到MVC中的ViewController中或通过VIPER中的Presenter来操作应用程序状态。我们还设计了与平台无关的Riblet模式,以统一Android和iOS的开发。
每个Riblet都由一个人组成R外,我nteractor,B.uilder与其组件(因此名称)和可选的演示者和视图。路由器和互动器处理业务逻辑,而Presenter和View处理视图逻辑。
让我们以Product Selection Riblet为例,先确定每个Riblet单元负责什么。
建造者
Builder实例化所有主要的rilet单元并定义依赖项。在Product Selection Riblet中,这个单元定义了城市流(特定城市的数据流)依赖关系。
组件
组件获取并实例化一个Riblet的依赖项。这包括服务、数据流和其他一切不是主要的rilet单元。Product Selection Component获取并实例化城市流依赖项,将其与适当的网络事件挂钩,并将其注入到交互器中。
路由器
路由器通过附加和分离子riblets来形成应用程序树。这些决定由互动者传递。路由器还通过激活并在某些状态开关上取消激活互动器生命周期。路由器包含两件业务逻辑:
- 用于附着和分离路由器的帮助方法
- 用于确定多个子节点之间的状态的状态切换逻辑
产品选择排骨没有任何儿童排骨。它的父riplet的Router (Confirmation riplet)负责附加Product Selection的Router,并将其视图添加到View层次结构中。然后,一旦一个产品被选中,产品选择路由器就会关闭它的交互器。
互动者
交互人员执行业务逻辑。这包括,例如:
- 使服务呼叫启动操作,如请求乘坐
- 进行服务调用以获取数据
- 确定转换到下一个的状态。例如,如果缺少用户的身份验证令牌的根互动器通知,则它将请求发送到其路由器以切换到“欢迎”状态。
产品选择互动商占据了包含数据的城市流,包括该城市的服务产品,定价信息,估计的旅行时间和车辆视图。它将这些信息传递给演示者。如果用户从Uberpool点击uberx,则互操作器从演示者接收此信息。然后它收集相关数据以传回视图,以便显示uberx车辆并估计拾取时间。简而言之,互动器执行视图中呈现的所有业务逻辑。
视图(控制器)
视图构建和更新UI,包括实例化和布局UI组件、处理用户交互、用数据填充UI组件和动画。Product Selection riplet的视图显示了它从Presenter接收到的对象(产品选项、定价、eta、地图上的车辆视图),并传递回用户操作(即产品选择)。
主持人
演示者管理互动者和视图之间的沟通。从交互者到视图,演示者将业务模型转换为视图可以显示的对象。对于Product Selection,这包括定价数据和车辆视图。从视图到互动角色,演示者将用户交互事件(如点击按钮选择产品)转换为互动角色中的适当动作。
把碎片拼在一起
riblets只有一个路由器和互动器对,但它们可以具有多个视图部分。RIBLETS只处理业务逻辑,没有用户界面元素没有视图部分。因此,RIBLET可以是单视图(一个演示者和一个视图),多视图(一个演示者和多个视图,或多个播放器和视图),或VEGESSLE(没有赠送者而且没有视图)。这允许业务逻辑树结构和深度与视图树不同,这将具有更平坦的层次结构。这有助于简化屏幕转换。
例如,Ride Riblet是一个看不见的Riblet,它检查用户是否有一个活动的旅行。如果骑手这样做,它附加的旅行肋骨,将显示在地图上的旅程。如果没有,它将附加Request Riblet,它将显示在屏幕上,允许用户请求行程。像Ride Riblet这样没有视图逻辑的Riblet这样的Riblet可以分解驱动应用程序的业务逻辑,支持这种新体系结构的模块化方面,从而提供一个重要的功能。
riblets如何构建应用程序
rilet组成了应用程序树,经常需要进行通信,以便更新信息或将用户带到乘车的下一个阶段。在我们进入它们如何通信之前,让我们先了解数据在一个Riblet中是如何流动的。
数据在肋骨内流动
交互者拥有其范围的状态和驱动应用程序的业务逻辑。这个单元调用服务来获取数据。在新的体系结构中,数据是单向流动的。它从服务到模型流,然后从模型流到交互者。来自网络的交互者、调度程序和推送通知可以请求服务对模型流进行更改。模型流产生不可变的模型。这就加强了这样的要求,即交互者类必须使用服务层来更改应用程序的状态。
示例流:
- 从后端服务到视图:服务调用,如状态,从后端获取数据。这将数据放在不可变模型流上。侦听此流的互动器将新数据通告并将其传递给演示者。演示者格式化数据并将其发送到视图。
- 从视图到后端:用户点击一个按钮,比如登录,然后视图将交互传递给Presenter。Presenter调用交互式程序上的一个登录方法,该方法导致一个服务调用来实际登录。返回的令牌由服务在流上发布。一个听流的扶少团员切换到家的肋骨。
排骨之间的通信
当互动器进行业务逻辑决策时,可能需要通知另一个事件(例如,完成)并发送数据。为实现这一目标,使业务逻辑决策的互动器调用一个符合另一个RIBLET的互动器的接口。
通常,如果通信将RIBLET树上升到父RIBLET的互动器,则接口定义为侦听器。侦听器几乎始终由父RIBLET的互动器实现。如果通信向下向子RIBLET向下,则应将接口定义为委托,并由子Riblet的互动器实现。委托仅适用于RIBLET单元之间的同步直接通信,例如对子互动器的父互动器。
特别是向下通信,父Riblet可以选择向子Riblet的交互器公开一个可观察的数据流。然后,父Riblet的交互者可以通过这个流将数据发送给子Riblet交互者,作为委托方法的替代方案。在大多数向下发送数据的通信中,这应该是首选的通信方法。
例如,当假设的ProductSelectionEnterioner确定已选择产品时,它会调用其侦听器以通过所选择的车辆视图ID。侦听器由确认输动家实现。然后,确认情区存储车辆视图ID,因此可以在服务请求中发送,调用其路由器以分离,并解除产品选择性RIBLET。
通过以这种方式构建数据流,我们以这种方式确保正确的数据在右侧屏幕上的正确时间。因为riblets根据业务逻辑形成应用程序树,我们可以通过业务逻辑(而不是视图逻辑)路由通信。这对我们的业务有意义,并最终有助于鼓励代码隔离,使应用程序开发从不断变得过度复杂。
回到起点
当我们开始从头开始骑行应用时,我们希望通过提高应用的可靠性,重新关注核心骑行体验,并为未来的应用开发建立正确的轨道集。创建新的体系结构对于实现这两个目标至关重要。
我们如何增加核心骑手体验的可用性?
rilet有明确的职责分离,所以测试更简单。每个Riblet都是独立测试的。通过更好的测试,当我们推出更新时,我们可以对我们的骑行应用的可靠性更有信心。由于每个rilet服务于一个单一的职责,很容易将rilet及其依赖分离为核心(直接需要注册并乘坐uberPOOL或uberX乘坐)和可选代码。通过要求对核心代码进行更严格的审查,我们可以对我们的核心流的可用性更有信心。
我们还启用了核心流的全局回滚到有保证的工作状态。所有可选代码都在主特性标志下,如果部分有bug,可以关闭主特性标志。在最坏的情况下,我们可以关闭所有可选代码,默认只关闭核心流。由于我们在核心代码上有如此高的门槛,我们确保我们的核心流总是工作的。
我们如何为未来的骑手应用程序开发建立正确的铁路集合?
肋骨帮助我们尽可能地缩小和解耦功能。业务和视图逻辑分离的这种清晰性将有助于防止我们的代码库变得过于复杂,并使其易于工作。由于新的架构与平台无关,iOS和Android的工程师可以很容易地了解对方的发展,从对方的错误中学习,并共同推动优步向前发展。实验不太可能间接影响核心体验,因为rilet帮助我们将可选代码从核心代码中分离出来。我们将能够尝试新的功能,在Riblet架构中作为插件开发,而不用担心它们可能会意外地将uberX和uberPOOL体验置于错误的风险中。
由于Riblets提高了抽象和责任分离,并且对于数据流和通信的明确规定的路径,持续的开发很容易 - 这种架构将为我们服务多年。
准备好前进了
我们的新架构使我们在未来的道路上有了定位。这一最新的重写意味着完全重做附加应用的代码库,重新执行之前存在的内容,执行用户研究,案例研究,A/B测试,并编写像提要这样的新功能。雷竞技是骗人的除此之外,我们还想在全球范围内推广新应用,让用户更快地掌握新应用,所以我们从设计、功能、本地化、设备和测试角度考虑了世界各地的变化。虽然发布已经结束,但我们的新架构下的工作才刚刚开始。
在这个新的架构里,有一个可能的可能性,改善了新的骑手饲料,将这种架构扩展到了司机和Ubereats.应用程序,甚至为建筑商建造。事实上,我们花了几个月的时间构建原型,以确保我们做出了正确的权衡。现在,我们可以自信地认为我们的架构是正确的,可以进行大量的构建工作。如果这种工作让你兴奋,那就参与进来,改善每个人的Uber体验安卓和iOS.工程!
本文作者Vivian Tran与优步工程技术项目经理Yixin Zhu共同撰写。








