工程可扩展,孤立的移动功能与插件在优步

0
工程可扩展,孤立的移动功能与插件在优步

随着优步的发展,我们继续完善我们的方法设计移动应用程序以支持我们的业务规模。当应用不断发展时,在不破坏其他功能的情况下添加新功能,尝试现有功能,并决定新功能应该整合到哪里将变得更加困难。我们的投资肋骨架构并在过去18个月内插入工具针对这些缩放问题,并且允许优步工程师衡量其生产力,并且在许多情况下,双倍。

在本文中,我们突出了我们目前的四个关键方面,为Android和iOS构建时插件工具的四个关键方面:(1)为什么我们的移动框架以及如何使用插件执行代码隔离,(2)我们如何使用插件来鼓励构造结构应用程序作为一小部分功能集成点和优势,(3)我们如何使用插件来缓解实验标志的问题,并为此类型的插件系统提供理想用户。

最终,插件使我们能够快速有效地构建和发布功能,而不管规模大小。我们希望其他人能从我们的经验中受益,并在使用这个强大的工具时吸取教训。

强制更多的代码隔离

隔离特性开发是扩展应用程序开发的基本原则。因此,一周提交数百次的单屏幕应用的架构需要提高代码隔离。

优步工程对我们的爱充满诗意肋骨,鼓励代码隔离的体系结构。如果我们已经知道肋骨能很好地处理隔离,为什么我们还在谈论它?

  1. 隔离不包含在肋骨内的特性是有用的;例如,将多个位置搜索数据源彼此隔离,允许对复杂数据源进行并行试验。
  2. 肋间进一步隔离是可能的,也是有益的。

下面,我们将讨论一些原因,说明为什么在肋骨之间进一步隔离可以为移动架构开发带来巨大的好处。

肋骨给了我们什么

RIB建筑是围绕建筑的前提而设计的应用程序作为业务逻辑范围的深层树,一个年代如图1所示,如下所示:

图1:在骑手应用程序的RIB树(上图)的简化版本中,用户通过常见状态转换。当用户执行这些操作时,肋骨从树附加和分离。

这种设计提供了显著的隔离优势。例如,在我们的附加应用架构中,机场改进、位置改进和确认肋骨必须彼此独立编写,因为它们不能访问彼此范围拥有的任何国家。

骑手应用程序的确认屏幕、位置优化屏幕和机场门优化屏幕的设计受益于肋骨提供的功能隔离。

那我们错过了什么隔离?

考虑如图1中的请求细化步骤示例。在我们的真实应用程序中,有30多种不同的可能性孩子们在细化步骤。肋条强制子节点之间的隔离,但不强制父节点细化步骤肋条和子节点之间的隔离,例如机场门选择肋条。

图2:细化步骤RIB及其子RIB之间的层次结构包含了一个单一的隔离层。

细化步骤肋骨负责附加和分离它下面的子肋骨,在这些子肋骨之间执行排序,并向它们传递特定的数据。当成功执行时,细化步骤肋与它的子肋完全分离,允许单个子肋发展并释放到生产中,而不影响其他子肋。

然而,在高速环境中,大多数工程师都有自然的倾向,旨在最小化复杂性和开发时间。例如,对于个别工程师来说,为单个工程师添加应属于机场门选择肋骨内部的细节,更方便,更快地添加到细化步骤RIB中,以支持从机场门选择肋到位置细化肋的过渡动画。这对夫妻改进了步骤肋骨给孩子。一旦细化步骤肋骨耦合到特定的儿童,它的孩子也通过延伸耦合在一起。然而,这种方法会导致工程组织失去信心,改变一个孩子不会打破其他孩子。

在优步,我们希望在父节点和子肋骨之间强制执行隔离,因此我们需要一个超过的架构使能够易于特征隔离(肋);它还需要确保功能隔离。

强制隔离父肋骨和子肋骨

良好的解决方案不应需要对我们构建肋骨结构和模块关系的方式进行重大变化(例如,依赖性反转),造成巨大的性能成本,或打破类型的安全。我们的解决方案很简单:添加一个额外的层来强制子RIB的实现细节不能被父肋骨引用。

我们通过定义可以由父ROB引用的插件点类(基本上是一个花哨的功能工厂)来执行此操作。返回到更新步骤RIB示例,我们允许插件点参考各个细化步骤肋。接下来,我们使用构建工具来确保细化步骤无法直接引用任何插件,只需插件点,如图3所示:

图3:插件和非插件代码之间的强制分离确保了细化步骤RIB不能再直接引用Airport Door Selection,现在强制根据从插件点返回的接口处理每个子RIB。

我们使用不同的工具来保证在Android和IOS上,如下所述概述:

安卓 iOS
  • 插件代码和非插件代码通过包名来区分。
  • 定制的短绒和容易出错的检查器确保非插件代码不能引用插件类。在插件代码和非插件代码之间架起桥梁的唯一方法就是使用插件点。
  • 插件代码和非插件代码的区别在于它们是在哪个框架中编写的。
  • 非插件框架不能直接引用插件框架,所以插件点框架填补了这一空白。此外,还禁用了传递框架依赖项。

类似于我们如何在父母和子女之间强制隔离,我们使用基本工具来强制插件之间的分离。我们鼓励在自己的构建目标(Android模块或iOS框架)内的功能插件的开发,并强制执行该插件构建目标不能彼此引用(见图4)。这种独立构建目标的发展增加了小程序员开销,因为无论如何都是肋骨自然隔离的,同时提供隔离益处和编译时间改进。

图4:基本工具防止插件间构建目标依赖项,这提供了隔离优势和编译时间改进。

从特性代码中分离胶水

在许多应用程序,胶水代码与特征代码混合。例如,当使用包含数百个功能的应用程序进行此方法 - 例如,我们有很多有用的区域特定的功能和优化 - 您将开始观察以下内容:

  • 添加新功能的难度。工程师需要通过CodeBase捕猎,找到添加其功能的正确位置。
  • 不可能对应用程序进行推理。为了理解应用程序的高级结构,您必须阅读/理解应用程序的特性代码和粘合代码。凡人是不可能一下子记住这一切的!
  • 不稳定。将应用程序粘合在一起的代码与引入新功能并行相变。
  • 对产品可扩展性的限制。工程师开始在最方便的地方集成特性。因此,功能可以以多种方式集成到同一个屏幕中。这将创建如下示例所示的屏幕:
图5:优步应用中的一个理论屏幕,功能在最方便的地方集成。这与我们过去的现实相去不远。

我们如何分离胶水?

确保应用开发和产品体验长期保持理性的一种方法是在应用中建立坚固的轨道来指导新功能的开发。

随着优步不断完善现有的应用程序并创建新的应用程序,我们鼓励尽可能多地将功能整合到现有的插件点中。每个插件点就像一个集成“轨道”。因此,优步应用程序的应用程序层现在有80%都在插件中。其余20%的代码将应用程序粘合在一起,如下面的图6所示:

图6:肋骨树由特性肋骨(米色)和粘合代码(蓝色)的组合组成。

我们根据目录结构区分插件代码和非插件代码,并在所有涉及非插件代码的代码审查中添加额外的代码审查器。这鼓励工程师在现有的插件点而不是特定的位置编写插件内的代码。

因此,我们的骑行应用程序以50种不同的方式集成了数百种功能,而不是数百种。这实现了:

  • 易于特性开发。添加501个功能只需要我们考虑大约50个位置来找到正确的集成点,而不是评估整个代码库。
  • 易于理解的程序架构。将应用程序粘合在一起的20%的代码可以独立于应用程序的特性进行推理。为了理解应用程序的高层架构,这是你唯一需要理解的代码。
  • 稳定。将应用程序粘合在一起的代码很少更改。当它发生变化时,更多的人(来自内部代码审阅者)会关注它。
  • 产品的可伸缩性。鼓励工程师在添加新功能时重用应用程序中现有的集成点,可以鼓励重用现有的产品设计,使开发更快更容易。

使用插件点:两个轨道示例

我们可以将应用程序中的大部分特性构建到现有的产品或技术轨道中。以下是来自骑手应用程序的两个例子:

示例a:位置搜索

上面显示的位置搜索屏幕是围绕一个返回的插件点构建的LocationRowProvider对象。

当集成新的日历事件功能进入位置搜索屏幕,全新LocationRowProvider子类型被创建并添加到LocationSearchPluginPoint。因为插件架构鼓励将位置搜索粘合代码从位置搜索功能中分离出来,所以不需要在应用核心中添加额外的数据流,也不需要考虑重大的产品变化,如下图6所示:

图7:我们的位置搜索屏幕和相应的插件点界面展示了我们如何使用插件创建定义的应用内特性。

如果位置搜索屏幕不是从一开始就以这种方式构建的,那么新的数据提供者就会以多种方式之一被集成。添加新数据源可能需要更改代码在多个地点,包括修改代码以添加一个新的数据源,礼物的代码行视图当给定一个数据视图模型,视图模型得到的位置行排序和合并,视图模型和数据类型的位置。

例B:限定范围的不可见工作

插件适用于肋骨,但并非所有插件都需要成为肋骨。应用程序中最通用的铁路很简单ScopedWorkrails。例如,假设一个工程师想要在骑车应用程序的任何时候执行代码LoggedInRIB附加到应用程序的肋骨树。工程师可以将插件集成到LoggedInScopedWorkPluginPoint,如下图所示:

图8:LoggedInScopedWorkPluginPoint使开发人员能够轻松集成插件。

的LoggedInScopedWorkrail在rider应用程序中有大约30个集成LoggedInInteractor.java会超过一千行,很难修改,也很难理解。

有利于实验和安全

每当我们推出新功能或对应用进行更改时,我们都会运行A/B测试,以确保这些更改能够提高稳定性和业务指标,但由于每周都要编写数百个更改,我们在维护嵌套实验时可能会遇到问题。不一致的A/B测试策略意味着代码库将充满条件,使得代码更难推理。

我们通过使用插件来缓解这个问题。每个插件都是通过A/B测试发布的,这就减少了在代码库的其余部分编写条件A/B测试分支的需要。由于插件的集成是一致的,我们可以在A/B插件完全推出后将其标记留在代码库中,而不会增加其复杂性。这使我们能够远程禁用应用程序中的每个特性。例如,如果在启动动画中遇到广泛的生产崩溃,我们可以通过远程禁用动画来解决这个问题。实现插件使我们能够使用这种策略快速有效地解决多个大型产品崩溃。

我们有信心禁用任何插件是安全的,因为每次提交更改时,我们都会运行UI测试,在禁用所有插件的情况下运行应用程序的核心流。虽然不是很漂亮,但为这些测试禁用插件的功能非常强大。如果我们采用插件背后的原则(例如,抽象工厂模式和依赖倒置的积极使用)而不使用统一的插件框架,我们将无法强制这种形式的安全性或轻松执行这些UI测试。

图9:我们可以禁用插件来测试新特性,这是这类工具非常有用但很乏味的功能。

你应该插件吗?

统一的插件架构对于具有许多特征集成点和缩放意图的大型应用程序具有有价值的。然而,在决定进入跳跃之前,请考虑这些福利是否超过了您团队的挑战。

插件系统的优点对于小型工程操作可能不值得。如果只有少量的特性集成点,那么即使是大型应用程序也无法从正式的静态插件系统中获益。例如,主要以feed为中心的应用程序在应用程序的粘合和特性代码之间有一个自然的划分,不管您是使用临时模式、插件系统还是依赖倒置将特性卡注入到卡片框架中。因此,一个正式的插件模式对于具有许多功能集成点的大型应用程序最有用,例如,包含多个状态和显示在地图顶部的子屏幕的应用程序:Trulia网站,谷歌地图,超级驱动应用程序

一旦您决定将应用程序构建为一组插件点,您将不得不花时间考虑问题:“App的菜单是单肋插件吗?”或者“插件与哪个框架与?”答案取决于您是否希望将集成到菜单中的屏幕能够快速和/或独立地演变(可能,是),以及是否要劝阻更改菜单脚手架本身。

这些讨论是重要的决定最好的软件设计适合你的应用。鼓励我们的团队讨论他们的设计在早期的可伸缩性的原因之一是我们的移动工程师现在两倍生产在我们重写骑手应用老骑士应用。事实上,我们的组织对融合了肋骨和插件的架构的认可增加了一倍多。

进入我们的外卖

Uber的正式插件系统提供了许多好处,我们决定整合它们背后的原则是广泛适用的,例如:

  • 区分应用的功能代码和将应用粘合在一起的代码是有益的。
  • 提供代码隔离的正式系统对于可扩展性和安全试验是有用的。

当我们的工程师在插件化的应用程序上构建功能时,他们的生产效率更高,发布速度更快。

有兴趣扩展技术发展最快的移动架构之一吗?申请一个角色优步产品平台团队的成员。

Brian Attwell最初将这篇文章作为演讲呈现旧金山的Uber Mobility

想要了解更多关于这个话题的信息,请查看软件工程师Manu Sridharan的技术演讲咖喱在关于插件等架构模式如何使静态分析更有效。

图片标题:插入它的海葵康纳·梅尔沃德的《家》,印度尼西亚。

评论