Uber致力于为全球用户创造更安全、更可靠的交通解决方案。为了实现这一愿景,移动登录(应用程序的入口点)必须快速、无痛且易于导航。
在我们发布之前优步的新骑手应用2016年11月,我们的工程团队为特定市场量身打造了独特的骑行体验,因此,移动车载的职责由多个团队分担。尽管成功地满足了区域需求,但这些重复的工程工作导致了一个分散的系统,增加了开发人员的整体复杂性,并抑制了快速的特性迭代。
作为骑手应用程序重新设计而且重写很快,分担新用户入职责任的团队都承认,有必要简化移动入职流程,以满足未来的产品需求。为了实现这一目标,我们选择完全从头开始。
在本文中,我们将讨论如何通过重新定义系统设计、修改支持后端服务和重构客户端框架来统一新的骑手应用程序的移动登录体验,以满足全球用户日益增长的需求。
设计一个零件工具箱
在我们实施我们的新解决方案之前,我们首先通过进行详尽的用户研究,以深入了解阻碍新用户和现有用户开始使用应用程序的障碍,从而定义了一系列影响现有产品的挑战。在收集了几个地区的反馈后,我们的团队对移动分析事件进行了分析,并对类似的上车体验进行了全面的行业审计,以补充这些发现。雷竞技是骗人的有了这些深刻的认识,我们的团队确定了现有产品中导致次优UX的几个低效率因素,并开始简化核心设计,以减少认知过载,提供上下文可提供性,促进快速实验,并支持动态国际化。
消除造成优柔寡断的障碍
在我们的用户研究和移动分析事件的检雷竞技是骗人的查之间,我们发现了我们产品入口点的一个微妙但暴露的缺陷。在用户开始登录过程之前,我们的用户体验要求他们有意识地在两者之间进行选择登录而且注册选项,如图1所示:
数据检查显示,用户在尝试任何一种方法之前,经常在两种选择之间来回切换。更糟糕的是,我们的团队还发现,相当多的用户经常选择错误的选项,导致更高的错误率、更低的转换指标、更高的流失率和整体较差的登录体验。
我们并没有增加这组选项的清晰度,而是通过要求一个独特的个人身份信息:一个手机号码来完全消除这些选项。通过删除选项并提前请求数据,我们的产品可以智能而谨慎地引导用户通过各种入职场景。一旦我们为新用户提供了优雅的简化和更受欢迎的设计,我们的团队就致力于在每个新用户入职过程中扩展这种新的用户体验。
减少杂乱和混乱
在完善了我们产品的入口点之后,我们的团队重新评估了我们在后续步骤中收集信息的技术。随着Uber服务的发展,现有的方法变得越来越混乱,缺乏直接和无摩擦的进展所必需的支持。我们的研雷竞技是骗人的究表明,现有的一次性收集多个不相关信息的方法,如图2所示,会引入认知过载。
为了防止用户混淆,在此过程中提供可理解的指令,并提供足够的指导,我们开发了一个系统设计,将数据请求分离为其单独的组件,如下图3所示:
通过这样做,我们不仅最小化了复杂性,而且还建立了支持产品需求的基础,包括持续验证、快速实验和可变的国际行为。
经常验证输入
我们的研雷竞技是骗人的究还表明,那些提供了不充分或无效信息的用户在登录时遇到了困难,这是输入验证不频繁和模糊的、不可执行的反馈的直接结果。由于我们现有解决方案的性质,只有在收集了所有必需的数据并发出了提交该信息的网络请求之后,才对信息进行验证。
不幸的是,现有的后端作为快速故障系统运行,在识别无效输入时返回一个错误。提交多个无效数据点的用户随后被要求逐个纠正每个错误。由于额外的网络请求和复合延迟,在初始输入验证期间未能聚合所有错误,导致用户更正错误的时间比输入输入的时间要多。此外,一旦用户选择了登录或注册选项,现有产品无法自动重定向它们时,所提供的信息是为替代。
通过设计一个请求用户信息的产品入口点,我们的团队保证输入验证会尽快发生。类似地,通过一次请求一个东西,我们可以确保经常进行输入验证。将信息收集请求分离为单独的组件还提高了我们的产品以连贯和易于理解的格式显示错误的能力。此外,产品需要更频繁地验证输入,聚合错误消息,并在不同的登录场景中被动地引导用户,这就需要对支持现有产品的后端服务进行进化。
动态协调经验
从一开始,我们就努力将多种产品体验整合到一个单一的移动平台框架中,我们的团队专注于两个关键的弱点:实验和国际化。在设计探索过程中,我们强调设计一个系统来增强这些缺陷,并从最终产品中获得更高的支持。
在我们现有的实现中,评估假设的实验通常会生成复杂的特征标志组合和复杂的控制流,其中竞态条件偶尔会产生令人费解的行为。当在这些参数范围内操作时,添加、删除或重新排序该步骤序列会降低可靠性。此外,在国际市场推出产品需要建立不同的流程,以适应文化差异和某些技术(如信用卡和电子邮件)的区域采用实践。编译多个特定于区域市场的入职经验会对应用程序二进制大小产生不利影响。幸运的是,通过将数据请求分离为旨在收集不同信息单元的模块化组件(如图3所示),我们的简化设计使我们的产品能够根据实验或市场特征的需要添加、删除和排序信息收集步骤。
从过去的错误中吸取教训,我们认识到过度使用功能标志和重复创造独立体验不会随着时间的推移而得到很好的扩展。评估我们扩展的产品需求以结合多个团队的经验,很快就否定了仅仅由客户驱动的工程架构的可能性。此外,我们现代化的系统设计远远超过了为现有产品提供动力的后端服务所提供的功能。为了满足产品需求,我们的团队选择完全重写后端服务,从而支持服务器驱动的移动入职体验编排。
演进后端
在我们的移动产品重新设计之前,我们的产品在整个信息收集过程中提供了稀疏的输入验证,因为后端只在接收到作为入职体验一部分提交的网络请求时才检查数据。类似地,我们的产品在提交无效数据后提供了不充分的错误消息,因为后端对每个响应只提供一个错误,而不是每个响应提供多个错误。
此外,新的国际市场的推出需要后端黑客,以绕过传统的要求,必须提供非重要的信息(例如,支付配置文件、促销代码和个人资料照片)。由于后端是无状态的,所以它无法控制业务逻辑,因此也无法提供实际的方法来根据用户输入在各种场景之间切换。结果,产品实验反复要求客户端更改、新的构建裁剪和应用程序二进制发布。
所以,我们决定完全用去和卡珊德拉数据库管理系统。定义精确的客户端-服务器契约对于成功地满足雄心勃勃的产品需求、简化设计和振兴用户体验是必要的。通过创建一个能够进行服务器驱动的客户端编排的新的有状态后端服务,我们的团队有了必要的基础来修剪非必要的数据集合,频繁验证输入,加快实验,并优化国际市场的动态体验。
用问答的方式进行交流
为了满足我们的产品需求和项目期限,我们与一个团队合作,为入职体验构建先进的web前端机制,以定义严格的客户端-服务器合同。我们的集体努力产生了一个基于后端和客户端之间的问答循环(Q-A循环)的直接通信方案,以高级形式表示,如下图4所示:
为了支持服务器驱动的信息收集步骤的客户端编排,后端向客户端提供一个问题,指定状态机转换所需的数据类型列表。一旦客户提交了包含用户收集的与这些数据类型相对应的数据的答案,输入就会由后端本身或通过公司内部的许多服务进行验证。当答案被认为有效时,后端状态机将根据其服务中定义的客户端流和在以前的答案中接收到的输入进行转换。如果后端没有积累足够的信息,无法完全满足特定市场用户的需求,它就会发出一个新问题,请求额外的数据类型,然后循环往复。
客户端在失败的状态机转换中提交无效数据的实例,这会提示后端响应一系列错误,客户端需要在继续之前纠正这些错误。在收集了足够的数据以将用户转换为在线状态之后,后端将会话传输到客户机,从而结束循环。
使用上下文参考的风格
除了请求的数据类型外,后端提供的问题还包含基于先前接收的输入的当前入职场景的上下文引用。由于每个问题都包含这个参考框架,我们的产品通过适当地设置样式来重新使用这些信息收集步骤,如下面的图5所示:
通过为不同的入职场景设计数据收集步骤,我们的移动入职框架通过提供可重用组件来减少冗余,并确保对应用程序二进制大小的影响最小。提供上下文还可以通知客户,当后端修改入职场景以响应先前收到的答案时,即从登录来帐户恢复处理忘记密码。
提供灵活的指导
除了参考入职场景外,后端准备的问题还包括详细说明如何收集特定数据类型的显式客户端说明。在特定的情况下,当后端发出一个问题,请求对数据类型组合(例如,名字、姓氏、电子邮件和手机号码)的答案时,它可能会指示客户端通过单个步骤或一系列步骤来收集这些数据类型,其中后一个步骤如下图6所示:
通过在Q-A周期内调整后端提供的细节来适应我们产品行为的能力极大地提高了我们在国际市场上快速有效地试验的能力。
根据性能调整系统
为了提供额外的功能灵活性,我们还加入了一组可选的备用问题,当用户无法为原始问题提供有效数据时(例如,忘记密码或语音移动验证),客户端可以回答这些问题。向备选问题提交答案将后端状态机转换为新的入职场景,Q-A循环按照前面所述进行。
我们还为客户在问题中加入说明,使用单个或多个信息收集步骤收集数据类型,在收集所有必需的数据后生成单个答案。后端服务通过批处理支持动态调整网络请求频率的能力极大地增强了用户体验和我们的产品在网络连接差或网络延迟高的市场中的适应能力。
重新构建移动平台
有了全新的用户体验,包括经过改进的设计和新的后端服务来支持我们的产品需求,我们的团队开始使用我们的产品来构建下一个移动上网迭代肋骨架构.下面几节将描述我们如何应用它开源架构模式来构建基本的入职组件,如图7所示。同样,接下来的部分将详细说明关键组件使用客户机-服务器契约执行服务器驱动的客户端编排时必须承担的职责。
条目
在应用程序启动期间,我们的移动登录框架尝试检索保存在用户设备上的存储会话。在没有发现会话的情况下(例如,新应用下载,用户注销等),骑手应用程序不认为用户已登录,因此继续显示我们的产品的UX以获得一个。通过附加入口肋骨,骑手应用程序放弃控制,直到收到会话。
Entry最重要的功能是根据用户交互在产品的非活动状态和活动状态之间进行切换。在附加骑手应用程序后,入口通过附加欢迎肋骨将我们的产品导航到非活动状态。为了完成这项任务,Entry通过执行图8所示的操作来构建一个适用的Welcome:
如图8的左侧所示,Entry从使用Welcome创建的可能性列表中选择第一个适用的Welcome插件点,在这种情况下。当没有插件可用时,如图8的右侧所示,Entry转而使用骑手应用程序提供的默认欢迎。通过利用插件点,我们的移动入厂框架授权Entry显示欢迎肋骨,提供另一种外观和独特的功能,提高我们试验和适应区域市场的入厂体验的能力。
在附加一个适用的Welcome之后,我们的产品将一直处于非活动状态,直到用户交互提示Entry通过附加Orchestrator RIB将移动上机框架导航到活动状态。类似地,当用户决定取消他们的入局尝试时,Entry通过导航到先前选择的适用的Welcome,将我们的移动入局框架返回到非活动状态。
欢迎
虽然Welcome最主要的职责是显示用户界面(UI),迫使用户进行吸引人的、愉快的交互,但它的基本功能是将客户端定义的问题转发到Entry,如下图9所示:
这个客户定义的问题随后被传递给Orchestrator,并用于启动特定的入职流程。为了保持快速启动性能,我们的移动登录框架避免发出阻塞网络请求以从后端获取初始问题。因此,沟通客户端定义的问题是Welcome的一项不可缺少的任务,因为Orchestrator没有其他方法来收集信息,为后端准备初始答案。
因此,通过在入口中使用Welcome插件点,我们的产品可以很容易地评估使用不同初始问题对转换度量的影响,并使我们能够根据具体情况定制这些问题。例如,rider应用程序为我们的产品提供了一个默认的Welcome,指定了一个初始问题,要求回答一个包含手机号码的问题。但是,Welcome插件可能会定义替代问题,指导Orchestrator在提交答案之前通过请求替代数据类型(例如,电子邮件地址或社交网络)来开始登录过程。
协调器
尽管入口肋骨和欢迎肋骨提供了有意义的关注点分离,但毫无疑问,Orchestrator在移动入职框架中执行最重要的任务。以下部分描述Orchestrator如何使用改进的客户机-服务器契约管理与后端通信,以处理问题表示、监督错误纠正和管理回答准备。
回答问题
一旦Orchestrator以上面描述的客户端定义的形式或响应先前的网络请求的形式接收到一个问题,它的主要目标就是向后端提交一个相关的答案。接下来,我们将说明发生的事件链,并详细说明Orchestrator用于执行此任务的内部组件。
为了收集请求的数据类型,Orchestrator将收到的问题转发给一个组件,负责将它们转换为InfoStep rib列表,以便从用户那里收集数据,如下图10所示:
为了完成它唯一的功能,Transformer从Orchestrator提供的问题中提取上下文引用和客户端指令。使用这些数据,Transformer通过执行图11所示的操作来构建InfoStep rib。所提供问题的转换指导Transformer构建InfoStepA、InfoStepB和InfoStepC rib。Transformer使用InfoStep插件点启动这个过程,创建一个可信的InfoStep插件列表,并将那些被认为适用于内部框架默认值的插件放在前面。然后,Transformer通过迭代提取的客户端指令,按类型筛选构造的列表,并通过提供提取的上下文引用来构建第一个类型匹配的InfoStep,从而编译InfoStep rib列表。
如图11所示,InfoStep插件点评估InfoStepB和InfoStepD插件是否适用,因此将其添加到默认列表中。因此,在选择过程中,InfoStepB插件被构建在了默认的位置,并且由于没有适用的InfoStepA或InfoStepC插件,它们的默认对应项由Transformer构建。或者,当没有可用的插件时(如图11的右侧所示),Transformer编译完全根据其默认列表构建的InfoStep rib列表。
如前所述,向Transformer提供InfoStep插件点可以让我们的移动入网框架更容易、更安全地测试我们的信息收集方法。通过创建InfoStep插件来覆盖默认值,我们的团队可以评估新设计、测试ux并验证假设,从而在不损失可靠性或性能的情况下改善移动设备的入职体验。
一旦转换完成,Orchestrator将接收到的问题和Transformer返回的InfoStep rib列表交付给负责维护这些项之间映射的组件,如下图12所示:
通过将问题映射到InfoStep rib列表并保留索引,Sequence为Orchestrator提供了围绕要呈现的InfoStep的上下文,并安排将答案提交到后端。在接收到输入后,Sequence根据其索引位置在其映射中追加或替换项,如下图13所示:
在本例中,Sequence将提供的问题与批处理中的最后一个InfoStep相关联,并调整其索引以引用此批处理中的第一个项,从而将其空映射替换为接收到的输入。一旦映射完成,Orchestrator通过附加由Sequence的索引引用的InfoStep(在本例中为InfoStepA)继续收集信息,并推动UX向前发展。
当InfoStep成功收集数据时,用户交互会触发产品的前向导航,并提示将此信息传输到Orchestrator,后者随后代理到数据存储组件,如下图14所示:
除了存储Orchestrator提供的信息外,Info还缓存脏数据类型,以提高框架网络交互的效率。在Info中集成此级别的功能可确保Orchestrator仅在为特定问题所需的数据类型存储了新数据时才尝试提交答案。
在将收集到的数据发送到Info后,Orchestrator将查询Sequence以确定是否应该回答某个问题或是否应该附加InfoStep。如图14所示,Sequence返回一个InfoStepB插件,指示在Orchestrator向后端提交答案之前需要更多信息。这个附加InfoStep rib并在Info中存储它们收集的信息的循环重复,直到Sequence的索引引用带有映射问题的InfoStep,即图13中的InfoStepC。
假设Orchestrator在查询Sequence时收到了一个问题,它要求Info通过提供所获得的问题来形成一个答案,如下图15所示:
如果Info不存储信息或其脏数据类型缓存不包括任何请求的数据类型,则不会形成答案。但是,当Info成功生成一个答案时,Orchestrator将返回的答案提交到后端,并等待响应以继续服务器驱动的客户端编制。
如前所述,来自Orchestrator的答案提交可能导致需要进一步收集信息的其他问题、需要用户更正的一系列输入验证错误,或者表示用户成功登录的会话。如果Orchestrator从后端接收到其他问题,则重复这些事件。否则,Orchestrator将执行以下部分中描述的操作。
处理错误
在Orchestrator获得一组错误以响应先前提交的各种答案之后,它的主要目标是将这些错误分布到相关的InfoStep rib中,以便在将新答案分配到后端之前纠正无效输入。
在InfoStep rib显示相关的错误消息并请求用户更新无效输入之前,Orchestrator首先通过将错误集传递给Sequence来确定哪些答案处于错误状态,如下图16所示:
由于Sequence在接收到的问题和InfoStep rib的转换列表之间进行映射,因此非常适合管理错误分布。在接收到一组错误时,Sequence的索引引用当前批处理中关联的最后一个InfoStep和问题,如下图17所示:
序列通过从其索引向后迭代来分配错误,直到所有错误都被适当的InfoStep rib声明或遇到前一批错误。当InfoStep声明对应于其负责的数据类型的错误时,Sequence将调整其索引以反映该InfoStep实例。Sequence通过引用最早处于错误状态的InfoStep,可以有效地倒带信息收集过程。例如,尽管InfoStepB插件和InfoStepC都处于错误状态,Sequence仍然修改其索引以引用最早出现的InfoStep,在本例中是InfoStepB插件。
一旦Sequence完成了错误分发,InfoStep rib刷新了用户界面以显示错误消息,Orchestrator将向后导航到Sequence调整后的索引所引用的InfoStep,并恢复信息收集过程。
采购会议
当Orchestrator获得一个会话时,我们的移动登录框架就完成了它的主要目的,从而终止了与后端的进一步通信。本节描述了Orchestrator对导致产品执行结束和骑手应用程序主要体验开始的事件的监督。
在Orchestrator与应用程序共享会话之前,它首先通过执行类似于“入口”和“回答问题”小节中描述的任务来编排一系列适用的ConfigureStep插件(例如,支付、促销代码、推送通知权限等)。与InfoStep rib不同,ConfigureStep插件主要由其他团队创建,这样他们就可以在用户生命周期中启动关键的早期体验。InfoStep rib完全由我们的团队构建和维护。通过提供这个平台扩展,我们确保通过删除用户入职不必要的数据请求来简化信息收集过程的努力不会对这些团队造成任何影响。
Orchestrator使用ConfigureStep插件点来创建一个预期的ConfigureStep插件列表,这些插件使用实验数据、区域市场偏好和在安装过程中收集的详细信息(例如,安装场景、移动国家代码等)来评估其适用性,如下图18所示:
如图18的左侧所示,ConfigureStepA和ConfigureStepC插件被认为是适用的,并且随后准备好呈现给用户。为了显示ConfigureStep插件,Orchestrator通过提供接收到的会话和适用的ConfigureStep插件列表来利用Sequence的映射功能,Sequence在其中为未来导航调整索引。
因为ConfigureStep插件是由外部框架提供的自包含组件,所以它们不提供存储信息,只在必要时发出自己的网络请求,并在任务结束时简单地通知Orchestrator,这样就可以提高入行体验。当最后一个ConfigureStep插件完成其相关任务时,Sequence将接收到的会话返回给Orchestrator,就像到达InfoStep批处理的末尾时返回一个问题一样。一旦从Sequence接收到会话(或当没有ConfigureStep插件被认为适用时的后端,如图18所示),Orchestrator立即与应用程序共享会话。一旦接收到此会话,在我们团队的移动入局框架中构建的产品体验将被分离和释放,应用程序将用户启动到其主要体验中。
构建一个与应用程序无关的框架
经过几个月与公司各部门的合作,我们的团队在新的骑手应用程序的紧迫期限内完成了我们雄心勃勃和细致的产品要求。我们的结果有四个方面:
- 我们简化了信息收集过程的核心设计,减少了认知过载,改善了整体用户体验。
- 我们设计了一个新的后端服务来提供服务器驱动的客户端编排,从而增强了实验并支持动态国际流。
- 我们构建了新的客户端框架,利用方便的客户端-服务器契约来统一移动入职体验;因此,工程工作没有重复,而是立即跨区域共享。
- 我们构建了一个平台,扩展了我们的移动入职框架,为外部团队提供了一个集成点,以实现他们自己的下线体验。
在产品发布后,我们的团队观察了产品的性能和框架的可靠性,考虑是否可以在统一优步其他产品的移动上网体验方面做得更多。在见证了我们的客户端框架在骑手应用中的成功后,我们对改进它们以为Uber的移动应用提供一个不可知的集成的挑战感到兴奋。从理论上讲,创建一个与应用程序无关的移动入职框架将进一步减少冗余的工程工作,并使其他团队能够重新分配资源。一旦这个想法在产品团队中获得了关注和兴趣,我们就投入了大量的精力来发展这个计划。
为了完成这一壮举,我们的团队开始在我们的移动入职框架中编译骑手应用程序特定依赖项的列表。因为我们的实现依赖于其他移动应用程序中无法实现的具体实例,所以我们必须将它们包含在内,而不管使用集成。通过使用依赖倒置和依赖符合协议的实现而不是具体的类,我们的团队消除了这些依赖,并为应用程序提供集成所需的组件提供了一个流程。
由于我们的产品需要一个默认的Welcome RIB,并允许提供可选的、备用的Welcome、InfoStep和ConfigureStep插件点,因此我们的团队为潜在的消费者构建了一套广泛的定制工具。通过必须使用默认的Welcome, Entry可以显示唯一的非活动UX,从而保证Orchestrator按照集成的产品需求需求开始数据收集过程。
为了调整默认InfoStep rib的外观,使其与消费应用程序相匹配,我们允许集成提供一个由首选项(例如,颜色、字体、大小等)组成的主题,这些首选项可以改变信息收集步骤的处理方式。此外,虽然InfoStep rib提供默认的外观和功能,但是通过提供可选的InfoStep插件点,使用集成可以完全覆盖ui或信息收集行为。类似地,如果消费应用程序打算在Orchestrator从后端接收到会话后显示额外的体验,它们可以通过提供可选的ConfigureStep插件点来实现。
在导入我们的移动入局框架并注入遵循协议的实现之后,消费集成在遇到注销状态后只需附加Entry。通过后端定义的流程描述各自的用例,我们的移动入职框架可以处理所有优步产品中新用户入职所需的繁重工作。
在移除骑行应用的所有依赖项并添加了一系列定制技术后,公司各团队开始将我们的平台集成到他们的应用中。
规划未来
在优步开发了客户端框架以统一移动入职体验之后,我们已经开始考虑对我们的服务器驱动平台进行前瞻性改进。虽然我们目前的实现是稳定可靠的,但我们会继续迭代解决方案,使其性能更好,尽管我们在实验和国际化方面已经取得了很大的进步,但还可以做更多的工作来进一步减少客户端修改。
在扩展后端以生成额外的动态上下文引用(例如,副本、图像等)和提供客户指令,包括首选的转换渲染引擎(例如,原生、web、动态原生表单等)之间,Uber统一的移动入局体验的未来很快就会到来。
凯尔·加布里埃尔(Kyle Gabriel)是优步Rider Growth团队的软件工程师。他是负责跨平台手机架构的技术主管之一新员工培训框架,并帮助定义新的后端服务的客户机-服务器契约。在业余时间,他喜欢举重,参加斯巴达赛跑,参加音乐会。
如果你对帮助Uber工程师创造更无缝的用户体验感兴趣,可以考虑一下应用我们团队的一个职位。





