Capital One的基于插件的体系结构和扩展iOS开发

企业域中最大的移动组织之一如何每隔一周发布一次其旗舰应用程序的新版本而又不影响质量? 拥有数千万客户的活跃用户群,并且在同一iOS代码库上工作的大约100位软件和质量检查工程师; 每年交付更多功能,并且更频繁地进行操作,这无疑是一项不小的成就。

当前iOS应用的历史可以追溯到2014年,当时我们决定内部构建一个完全本机的服务应用。 我们重新设计了开发基础架构,引进了世界一流的人才,并专注于在后端使用现代API基础架构为我们的用户打造出色的移动体验。 我们的努力取得了成果,2015年,我们发布了旗舰iPhone和iPad应用程序以及Apple Watch扩展程序。

到2016年,这两个应用程序都受到了用户的热烈欢迎,并且我们开始为我们的工作赢得全行业的赞誉。 在技​​术,团队构成和应用程序规模方面也发生了很多变化。 我们将iPhone应用程序转换为通用应用程序,并淘汰了独立的iPad应用程序。 我们的组织规模每年增加一倍,我们面临着许多其他成长中的iOS组织所面临的许多相同挑战:构建时间,合并冲突以及工程师互相碰撞。 凭借超过350,000行的代码库,我们开始碰壁。

今天,我想谈谈一种通过使我们能够以最小的回归风险并行构建和测试功能来帮助我们克服这些问题的体系结构。 一年多来,我们已经在该架构上成功构建并快速迭代某些功能,同时极大地增加了对该平台的贡献者数量。

查看下面的代码提交数量与项目时间表的比较,您可以看到我们在进入2015年中期的同时,开始向应用程序的第一个版本投入生产。 在iPad应用发布之前,今年晚些时候还会出现另一次增长。 然后,我们会看到圣诞节和除夕前后出现下降,从历史上看我们没有生产版本。 进入2016年,会有更多的提交流,因为我们都有更多的人为代码库做出了贡献,并且因为我们正在合并iPhone和iPad代码库。 然后,我们会看到一些平稳状态,这是出于两个原因。

首先,我们用Swift编写应用程序的赌注获得了回报。 它不仅帮助我们吸引和雇用了最优秀的人才,而且由于其类型安全性和编译时间检查,该应用程序比以前构建的任何应用程序都更稳定。 但是,与此同时,构建时间花费的时间比预期的要长。 一些开发人员报告在干净的构建上等待30分钟,而任意Xcode崩溃也是一个问题。 Swift不是向后兼容或ABI兼容的,因此每次我们不得不迁移到新版本时,都需要花费大量的精力。 如果您仔细观察一下该图,您将不会在2016年圣诞节假期中看到下跌,因为一群雄心勃勃的工程师正在努力将我们的代码库从Swift 2.3移植到Swift 3.0!

这并不是说Swift是唯一起作用的因素。 在同一代码库上拥有近100名员工,这意味着当某件事发生故障时,每个人都将其破坏。 还有更多的合并冲突需要解决。 更多功能意味着现在需要运行更多回归测试,这需要更多时间。 不幸的是,每个新版本都添加了更多的回归。

面对这些挑战,举手投足并不容易。但是,随着工程团队的扩大,我们决心保持速度并提高代码质量。 我们开始尝试一些实践。 我们使用了所有可用的监视工具来确定哪些功能未按时交付,交付多晚以及延迟的原因是什么。 这样,我们可以通过确定常见的痛点(例如API可用性和合同合规性,建立标准,与后端团队合作并获得他们的支持)以及将运行状况检查机制集成到我们及其软件交付流程中来改善估算值。

我们还投资了我们的构建基础架构-增加容量,优化构建脚本并减少从PR提交到完成所需测试之间的时间。 我们通过在CI / CD周期中开发和维护用于不同目的的多套测试来重新集中测试自动化的工作。 以这种方式,我们所谓的“烟雾测试”使用模拟API响应,并由每次PR提交触发。 关键测试每天在主分支上运行,我们的任务是在同一天修复表面缺陷。 然后,我们有一组更全面的测试,每晚在主分支和单个团队分支上运行。 通过将它作为度量标准集成到每个PR提交中,我们甚至提高了单元测试的覆盖率,甚至自动创建了一些测试(例如可访问性测试)。 我们甚至深入研究了如何改善Swift的构建时间,并提出了一些有趣的观察和解决方法。

即使我们的努力产生了显着的影响,我们仍开始观察到证据表明,仅靠这些措施是不够的。 如果趋势持续下去,我们可能无法保持速度和质量,而无法满足向移动组织添加更多功能和工程师的需求。 我们开始重新考虑我们的移动开发方法。 我们需要一种新的机制来支持移动组织在未来几年的发展,提供护栏并增强实验能力。 我们需要先思考并主动解决问题,然后再解决问题。

我们制作了一些创意的原型,在实践中进行了测试,评估了优缺点。 经过无数次迭代,我们最终开发了基于插件的体系结构以及能够满足我们目标的伙伴关系模型。 这种架构具有可伸缩性和稳定性,使我们能够创建有价值的内容并将内部和外部开发团队集成到软件开发生命周期中,而不会影响速度或质量。

这是高层的工作方式。

可以将旗舰应用视为一个容器,并将其作为独立开发的独立插件来使用。 在应用程序和插件之间有一个定义明确的界面。 通过遵循多种协议,插件可与应用程序通信,而无需了解其实现细节。 它是应用规模的封装。

我们的平台架构团队为功能团队提供了一个入门项目,其中包含所有必需的核心网络,持久性,UI库,单元和UI测试以及必要的元数据。 入门项目已经符合该协议,并具有其功能的存根实现。 该项目编译为框架,因此在准备发布功能时,所有者团队将发布其框架的新版本。 然后,我们的团队每周一次提取所有插件的新版本,执行自动测试,静态分析,安全扫描,然后将这些插件集成到旗舰应用程序中。 该过程中的所有步骤都被广泛自动化。 该功能与远程配置相关,我们可以即时打开或关闭它。 该功能发布后,我们将监控指标并收集反馈。 然后,功能团队对功能进行迭代,然后逐步将其逐步发布给更大的用户群。

在过去的18个月中,我们已经成功开发并尝试了该体系结构的许多功能。 因为我们能够以节制的方式快速发布功能,并且将分析流引入到我们的管道中,所以我们立即开始分析用户参与模式。 这有助于我们反复考虑我们的想法,并且我们发布了许多工具,可帮助我们的用户更好地控制自己的财务生活。

让我们看一下这些插件在旗舰应用程序生命周期中如何与之交互。 该应用程序的主屏幕具有一个收集视图,该视图显示了客户的帐户以及各种优惠。 或类似消息之类的东西。 插件通过数据源和委托模式在同一集合视图中公开。 在设置阶段,将分析所有框架,以确定其中哪些是插件。 然后初始化这些插件,并检查其权限,真实性和完整性。 规则引擎确定组件的显示顺序,然后插件数据源会汇总插件和帐户信息,并将其输入到集合视图中。

现在,集合视图知道要显示多少个项目,并且已经识别并初始化了我们的插件,现在我们可以查看如何渲染每个单元格。

插件数据源要求每个插件提供一个视图作为它们的入口点。 为此,我们有一个框架,可提供可根据需要定制的模板化切片。 然后将这些图块注册到集合视图。 当集合视图为特定插件请求单元格时,插件数据源会通知插件,以便它可以配置其图块并在必要时开始预取数据。 一些插件在其图块上动态显示数据,并且它们可以请求集合视图以通过插件数据源更新其UI。

现在,插件可见了,它们可以开始接收用户事件了。 他们可以选择处理UI手势以提供自定义行为,例如水平滚动或轻击。 默认情况下,每个用户事件都将记录为一个事件,并向组件通知轻击手势。 该组件可以通过请求呈现自定义视图控制器来响应轻敲,此后该组件拥有用户体验。 我们提供标准控件,以便用户在交互的任何时候都可以导航回应用程序的主屏幕。

那么,这种架构如何支持我们组织的成长?

通常,团队进行为期两周的冲刺,所有功能都进入一个回购中,每个团队都拥有该回购的一个分支。 在冲刺期间,每个团队都会在团队中成长,进行审查和测试。 在冲刺即将结束时,团队存储库中已验证的代码将合并到单个存储库中,在其中执行集成和回归测试。 该模型的缺点是,由于每个团队都在同一个代码库上工作,因此添加更多的团队会使我们陷入困境。

旗舰团队在相同的代码库上进行为期两周的冲刺。 插件团队在独立的代码库上进行为期一周的冲刺。

另一方面,插件体系结构允许团队在隔离的代码库上工作。 插件团队基于我们为其提供的独立工具。 在此工具中,我们介绍了通用功能,例如核心网络,数据管理,实用程序库,UI控件和设计组件。 插件团队进行为期一周的冲刺,并在每周结束时发布其功能的语义版本作为框架。 核心团队对代码执行自动化分析,安全扫描,测试和审查。 在下周末,该功能将与我们的依赖项管理器合并到旗舰应用程序中。

这使得功能的开发和测试可以以分布式方式并行进行,因此有助于在加快交付速度的同时扩展组织规模。 另外一个好处是,由于所有功能都被沙盒化,并且必须通过定义明确的界面与彼此和应用程序进行交互,因此该架构可防止出现大量的控制器和违反层的情况。

让我们看一下如何降低风险并照顾功能团队的工作,以便他们将时间和精力集中在为用户创造价值上。

设计方

在设计方面,我们进行了许多协作会议,在此期间我们的设计师提供了初步的设计咨询。 这些对话强调了设计准则,并提倡重用设计组件框架和UI设计资源。 有些功能需要独特的设计模式,有时这会导致功能团队贡献新的设计标准,然后供其他功能团队重复使用。 一旦设计成熟并被批准,开发人员便可以开始实施功能,并带来用户体验。

工程方面

在工程方面,这需要三个阶段。

  • 首先,我们加入了功能团队,并开始了他们的项目。 在内部,我们实践开源模型,并建立了许多核心框架,包括网络,数据持久性,UI控件,设计组件,分析,功能标记和帮助程序功能。 我们立即提供了此功能,并提供了一个模板项目,该项目已与我们的移动业务流程层集成在一起。
  • 其次,在开发阶段,我们通过帮助确定依赖项周围的复杂性,后端集成以及鼓励软件最佳实践(例如干净的体系结构,防御性编码原则,可伸缩性和安全性)来提供支持。 我们经验丰富的DevOps管道可将整理,静态分析,性能测试,安全扫描和代码覆盖率指标自动化到代码审查过程中,从而降低了人为错误的风险。
  • 在最后一个阶段,当功能投入生产时,我们会密切检测和监视功能的性能和运行状况指标。 我们还拥有适当的系统,以确保该功能由可用的,可访问的且符合合同的API提供动力。 通过使用许多内部工具以及各种软件分析服务,我们在客户端和后端都可以做到这一点。 然后,我们分析趋势,并在必要时让自动化系统提醒利益相关者。

每个功能都作为假设开始。 我们认为我们的用户有问题,我们确定此问题值得解决。 我们知道有多种解决方法,但是我们不知道哪种方法最好。 但是,我们已经进行了内部和外部用户测试,因此我们有一些好主意。 我们还定义了成功的样子。 然后,我们实施其中一种解决方案,将其呈现给我们的用户,收集数据,对其进行分析,做出以数据为依据的决策,然后迭代我们的解决方案,直到我们确信自己已经创造了用户既需要也想要的体验。

该模型如此成功的主要原因之一就是我们对以用户为中心的决策的承诺。 开箱即用的工具包括由后端上的远程配置API支持的功能开关SDK。 通过分阶段推出这些功能,我们可以将功能发布给部分用户群,并观察用户参与度。 我们有内置的分析SDK,可以近乎实时地安全记录和报告用户事件,因此我们可以分析更多指标并微调流量。 因此,我们能够在工程师,设计师和用户之间创建一个主动的反馈环-导致更快的迭代以创造更好的用户体验。

到目前为止,我们已成功运用我们的指导原则,通过雇用和保留最佳人才来建立世界一流的工程组织; 了解,采用并添加行业最佳实践; 并保持工具和技术的领先地位。 当我们的代码库变得如此之大以至于我们开始观察到其在功能上快速迭代的能力下降时,我们构建了一个体系结构,该体系结构封装了我们的代码库的复杂性,并定义了新的流程,这些流程有助于将新开发人员和合作伙伴集成到软件交付生命周期中。 多年来,我们通过并行开发和测试新功能来帮助扩大组织的规模。 我们还完善了经验丰富的工具和自动化流程,以使功能测试和用户参与度指标的收集商品化。 通过授权的试验,现在可以更快地测试想法,使其变得更加成熟,并且更具成本效益。

这是我有幸与之合作的最有潜力的技术,体系结构和团队。 看到我们接下来将要实现的目标,我感到非常兴奋!

  • 在边缘设计微体验
  • 迭代交付和基于合同的实施
  • 移动编排—边缘创新
  • Capital One的Mobile Edge工程团队利用4种弹性模式

公开声明:这些观点是作者的观点。 除非在本帖子中另有说明,否则Capital One不与任何提及的公司有关联或认可。 使用或显示的所有商标和其他知识产权均为其各自所有者的所有权。 本文为©2018 Capital One。