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