本文原刊登于《Increment》杂志,经原作者Gio Lodi许可,InfoQ中文网翻译并分享。
本文介绍了受微服务启发的新兴架构模式如何激发功能开发并加快开发人员的速度。
20 世纪末,Netflix 和 Amazon 等网络公司面临着大规模软件开发的挑战。为了最大限度地减少数百名贡献者对大型共享代码库进行更改时产生的摩擦,他们将软件划分为可在云端租用硬件上独立部署和扩展的服务。将单体应用程序分解为独立的微服务集群,使团队能够更快地行动,减少运营摩擦。以产品为导向的团队可以拥有单独的服务,并对其开发和运营进行细粒度的控制。
如今,移动开发团队面临着与上世纪末网络巨头相同的规模挑战。此外,他们还必须克服另一个障碍:应用程序以单个二进制文件的形式交付,用户可以在其设备中下载并运行。随着移动应用程序代码库的增长,编译成越来越大的二进制文件所需的时间也越来越长。
为了应对这些扩展挑战,SoundCloud、Just Eat 和 .au 等移动团队一直在探索类似微服务的方法来构建应用。通过将模块隔离在专用代码库中,他们发现可以避免冗长的构建时间,转而采用可提供更快反馈周期的专用、功能特定的应用。
采用微应用架构。
什么是微应用?
微服务将后端的各个区域划分为单独的部署。同样,移动开发人员可以将应用程序的不同核心部分(单个功能、共享业务逻辑和低级功能)移至独立的模块存储库。生成的模块彼此独立,并且独立于主应用程序代码库,团队可以自行处理这些模块。
这种架构不同于其他强调模块化的方法,即微应用,使用特定模块作为快速开发和测试的工具。团队可以构建一个或多个面向内部的微应用来满足他们的需求,其中仅包含他们正在开发的功能所需的模块。例如,负责电子商务应用程序结帐组件的团队可以构建一个测试微应用,该微应用列举付款方式、送货地址和汽车购买内容的组合。这样,他们可以更快地测试结帐流程,而无需在主应用程序中手动复制每个组合。
为特定功能配置专用微应用对于加快迭代速度具有巨大优势。微应用的构建速度比面向用户的应用程序更快,因为需要的编译代码更少。此外,由于微应用仅在内部使用,因此它们不需要复杂的用户体验,可以预先填充相关测试数据,并且可以绕过客户旅程的整个部分,例如登机。这大大减少了验证更改时的摩擦,再加上更快的构建时间,可以创建更快、更高效的开发流程。
因此,微应用的架构包括模块化设计,并辅以专门的应用程序(称为微应用)进行开发和测试,所有这些都是为了提高开发人员的速度。它更像是一个抽象框架,而不是 MVC(模型-视图-控制器)或 MVVM(模型-视图-控制器)等定义明确的框架,因为架构会根据特定应用程序的特征而有所不同。然而,虽然没有一刀切的方法,但所有成功的实现都是相同的。
微应用的基础知识
微应用的核心是松散耦合、高度内聚的模块网络,其中高级功能模块依赖于低级功能模块。这些模块通过薄协调层(面向用户的应用程序)连接,并由高级工具骨干提供支持。每个功能模块可以有一个或多个专用微应用,团队可以在开发和测试更改时快速获得反馈。
面向用户的应用程序
面向用户的应用程序的代码库包含独立的模块,并充当协调器,将它们整合在一起,形成统一的用户体验。它的实现应该尽可能精简,因为所有功能和业务逻辑都存在于专用模块中。它在启动时实例化功能模块,提供模块所需的服务,将相关信息从一个模块传递到另一个模块,并传播操作系统和应用程序生命周期事件。
功能模块
属于同一业务垂直领域的每个功能或功能组都存在于专用模块中。例如,在电子商务应用中,浏览库存可能与购物车管理存在于不同的模块中。在模块的代码库中,包含实现该功能所需的所有业务逻辑和自定义用户界面。
模块不直接实现低级功能(例如网络或持久性);相反,它们为所需的低级功能定义抽象,并依靠插入的应用程序提供具体实现。开发人员主要通过单元测试和构建专门的微应用程序来迭代功能模块。
用户界面模块
无论您的微应用是否具有设计系统,所有跨功能的 UI 元素和配置都应位于可导入功能模块的专用库中。这可以大大减少 UI 代码的重复,并有助于为用户提供一致的视觉体验。
基础及实践模块
基础模块和实用程序模块提供由功能模块和面向用户的应用程序共享的低级功能。
基础模块集中实现一些功能,例如与远程 API 交互或从设备存储加载数据。汇总所有与低级功能相关的逻辑可以在进行更改时实现更好的本地推理。当不同的功能模块使用相同的底层逻辑时,每个模块都可以从其他模块的改进中受益。回到我们的电子商务应用程序示例,库存浏览团队的开发人员可能希望通过加快网络响应解码来增加销售额。由于网络解码是基础模块的一部分,因此开发人员所做的更改将使应用程序中的所有请求都更快,而不仅仅是浏览功能模块的请求。
实用程序模块包含标准库扩展等逻辑,或定义明确的独立功能(例如自定义日期格式)。这些代码的变化速度往往比基础组件或功能组件慢得多,因此将其存储在专用库中意味着在构建消费者应用程序时无需重新编译。
工具
在微应用中,CI 可以检查每个变更集,识别修改后的模块及其下游依赖项,并仅在代码库的较小子集上运行测试,忽略未受更改影响的代码。这些构建将更快,因为表面积更小,开发人员可以更快地获得有关其更改的反馈。
以脚本或 Tuist 等高级代码生成工具的形式进行的自动化使将新模块集成到面向用户的应用程序中成为一项不容易出错的任务,使开发人员无需编辑包含数十个选项的配置文件,完全消除了对应用程序依赖关系树的全面心理表示的需要,也不必了解构建系统的神秘细节。微应用底层自动化的质量可能是争论发布所需的时间与任何人都可以触发的一步式流程之间的差异。
在微应用中,CI 和自动化至关重要,以确保快速测试和部署以及模块的无缝集成。
挑战与权衡
与任何架构模式一样,微应用方法也有其优缺点。微服务对微应用的架构影响很大,但两者之间存在一个关键区别。微服务是单独部署的,而组成微应用的模块则编译成同一个二进制文件。这种技术限制限制了各个团队在选择如何构建模块方面的自由。
以第三方库为例。在模块的独立环境中,团队可以自由选择不同的实施策略。但如果两个团队导入两个库来解决同一个问题,该怎么办?面向用户的应用程序将具有额外的“重量”,下载和更新成本将很高。相反,团队需要协调并商定一个可在整个应用程序中采用的第三方库的最小可行列表。
团队之间的有效沟通和协作是软件开发中普遍存在的挑战,但微应用对独立组件的重视加剧了这一挑战。让团队自由地在模块内操作,同时确保最终产品的一致性,这是一个难以实现的平衡,工程领导必须在帮助实现这一平衡方面发挥重要作用。定期举行内部会议并在模块之间轮换开发人员可以帮助打破知识孤岛,确保开发人员熟悉代码库的每个部分,并促进团队采用最佳编码实践。
模块化本身并不一定能加快开发速度。为模块绘制适当的边界是定义微应用架构的关键但具有挑战性的部分。高级模块边界应与您的组织结构保持一致。例如,电子商务公司可能有专门的库存和支付部门,其应用程序模块应根据这些业务特征进行区分。此外,业务需求驱动的变化需要更新多个模块,孤立的开发流程可能会开始崩溃。
微应用架构之路
采用微应用架构需要时间,并且需要大量的学习和实验。在最初几个模块提取过程中,请注意系统组件之间的边界、组件的隔离和迁移要求、代码库的组织方式,以及在应用程序完全模块化后,工具需要如何改进以支持构建、测试和部署应用程序。
应用程序中已被隔离或由多个功能共享的部分可以成为首次模块提取的良好候选。这几个模块只需要对代码进行一些简单的更改,让您的团队可以专注于提取过程本身并从中学习。示例包括基础组件(例如 API 客户端)、无处不在的用户界面元素(例如具有自定义样式的按钮)或没有上游依赖项的低级功能集群(例如标准库扩展)。
提取三到五个模块后,将所学知识转化为创建新模块的明确标准。这些标准应规定模块代码库的组织方式、如何将其集成到面向用户的应用程序中以及其 CI 设置。自动化应使任何人都可以生成构建新模块所需的“脚手架”。在学习、文档和工具方面的早期投资将为其余的迁移工作奠定坚实的基础。
微应用架构仍处于起步阶段,团队有很大空间在这些方法上进行迭代和创新。我期待更多开发人员尝试其实现细节,进一步突破其界限并分享他们的学习成果。我希望这次关于架构的可能性、特征和挑战的讨论可以成为一个有用的起点。
关于作者:
Gio Lodi 自称是一名极客,对测试、自动化和生产力充满热情。他是 Automattic 的远程移动基础设施工程师,也是《使用 Swift 进行测试驱动开发》一书的作者。
原文链接: