【译】开发大型 Angular 应用的12条架构清单

2,995 阅读13分钟

原文链接:12 Things to Help Large Organizations Do Angular Right

在 Nrwl,我们帮助财富500强公司用正确的方式使用 Angular 平台开发。这些公司很少存在小型应用,大多是多个团队使用多个共享库构建的多个应用程序。经历过此种情况的开发者就知道,如果处理不当它很快就会演变成一个多对多的卷积噩梦。

在本文中,我将会讨论:

  • 为什么开发大型 Web 应用在大公司很难
  • 为什么 Angular 是大公司的绝佳选择
  • 怎么做

文章的末尾——在讨论了一些相关因素和关注点之后——我将列出一份大型组织中架构师的12条清单,以提高他们团队的工作效率。

大型组织面临的挑战

从表面上看,大型和小型组织实际上都有同样的关注点:

  • 他们关注一致性。如果每个团队构建代码的方式不统一,则代码将难以复用和集成。
  • 他们需要编写健壮,经过验证的代码:包括错误处理,竟态条件等。
  • 他们需要编写可维护的,自文档化的代码。
  • 他们希望能够放心地更改代码。如果开发人员可以快速验证更改是否安全,那么他们更有可能保证代码库拥有更少的bug。

大型组织的不同之处在于,他们拥有数百名 Angular 工程师,并且构建了数十个应用程序,拥有大量的代码,从而导致了新的挑战。

  • 虽然十名开发人员可以通过午餐聊天达成最佳实践的共识,但当开发人数达到五百名时就不现实了。你必须建立最佳实践,团队标准并使用工具来推广它们
  • 随着代码量和复杂性的提升,代码所有权概念变得非常重要。例如,只有三个项目的情况下,大家往往都知道团队内谁是 review PR 的最佳人选,但三十个项目的时候就不是这样了。你必须明确定义所有权模型并使之自动化
  • 例如,在只有三个项目的情况下,开发人员很容易知道在代码变更后需要重新测试哪些内容,但有三十个项目时,这不再是一个简单的过程。管理变更的非正式规定将不再适用于大型团队,多团队和多项目协作的情形。你必须依赖自动化 CI 流程

换句话说,小型组织通常可以通过非正式的临时流程来实现这些目标,而大型组织却不能。临时流程也会使得团队中新加入的开发人员更加困惑且容易出错。

Angular 是让大型组织受益的框架

作为 Angular(2.x及更高版本)的主要贡献者之一,不出意料地我肯定认为 Angular 是各种规模,无论大小的公司的绝佳选择。但是,我想强调一下为什么该框架特别适合大型组织的几个理由。

没有碎片

Angular 社区并不分散。几乎所有应用程序都是由 Angular CLI 构建的,使用 Angular router,许多应用程序使用 NgRx(或计划使用它)来管理状态和副作用。 这种统一性导致高度的一致性。因此,当新的开发人员加入团队时,他可以在几天内就能变得高产,他也可以在组织内换岗的同时保持高效。

语义化版本控制

Angular 每六个月发布一个新版本。如果推出重大变更,你将有一年的时间来更新你的应用。虽然这会使得像我这样的框架贡献者的工作更加艰难(例如,我无法立马修复路由设计中的错误),但是能让像我一样的 Angular 用户更容易帮助公司业务获得成功。

Angular 💗 TypeScript

TypeScript 是 JS 生态系统中最棒的发明之一,它为开发人员提供了开发时警告和错误提示,它还能够帮助我们阐明代码意图并消除歧义(阅读这里了解更多)。 Angular 并不是唯一适用于 TypeScript 的框架——大多数现代框架都可以。但它是整个生态系统与 TypeScript 配合良好的唯一主流框架:每个工具和每个库。因此,typings 定义永远不会过时,API 与类型一起使用时开发体验很棒,代码操作工具背后使用了 TypeScript 编译器 API。

使用 TypeScript 作为通用语言让 Angular 与其他框架有着很大的区别。

自动化

大型组织依赖于工具和自动化。这通常意味着源代码必须是可静态分析的。而 Angular 就是以此为基础构建的,它清晰地分离了应用程序的静态和动态部分,使你能够编写可靠的工具来操作 Angular 源代码。此外,开发人员可以利用工具来构建,测试,运行和打包 Angular 应用程序,而无需开发人员配置任何内容。

怎么做

对你所在的组织来说,开发大型应用影响最大的四个主要因素分别是:

  1. 代码管理,
  2. 依赖管理,
  3. 落地最佳实践,以及
  4. 自动化。

我们来详细阐述一下。

代码/依赖关系管理

正如我上面提到的,大型组织存在有很多代码,很多开发人员对代码,包,部署等进行过更改。因此,弄清楚如何托管它,如何有效地对其进行修改,如何在不同项目之间建立依赖关系,如何构建和发布它们,是你需要尽早给出方案的问题。一旦开始研究这些问题,你将发现并非所有项目/模块都类似。

在典型的项目配置中,有四类项目/模块:

  1. Apps,这些是你交付的业务产品。
  2. 特定于 App 的 lib,这些是可以独立开发和测试的 app 部分。
  3. 可复用的 lib,这些是你在多个不同 App 中使用的组件,服务和工具。
  4. 第三方库和工具。

在这个模式下,开发人员应该能够:

  • 创建 app 特定的 lib
  • 提取可复用的 lib
  • 验证更改可复用 lib 相关的代码不会破坏任何依赖它的 app 和 lib
  • 同时重构多个 app 和 lib
  • 确定 app 和 lib 的负责 Owner

开发人员完成所有这些工作的难易度,对团队的迭代速度和项目的代码质量有很大影响。

如果创建一个新的可复用库(需要新建一个 repo,配置 CI,发布到内部 npm 仓库)需要耗费一天的时间,那么很少有人会愿意这样做。因此,开发人员会不自觉地复制粘贴代码,或者把代码放到不恰当的地方。 如果能在几分钟内(而不是几天)构建和配置可复用库...那么才会促进开发人员建立和维护可重用的库。

如果多个 app 依赖于多个 lib,则在 lib 更改时,回归测试会变得非常困难。如果无法自动验证可复用库的更改会不会破坏任何 app,那么开发人员将害怕进行代码更改并编写防御性的脆弱代码。

如果开发人员无法横跨不同的 app 和 lib 进行重构,他们就不会改进 API。

当开发人员无法独立构建和测试 app 部分时,他们将会创建内部模块紧密耦合的单体应用。当他们能够(使用 app 特定的 lib)做到时,他们就会编写更多可维护的代码并改进他们的应用程序架构。

这主要是因为他们必须明确定义一个特性的依赖以及它的输出,这个过程必须仔细斟酌,其结果类似于六边形体系结构,减少了项目之间的耦合。

Angular CLI 扩展 (Nrwl/Nx)

Nrwl Nx 是一个用于企业级 Angular 应用的开源工具包,基于 Angular CLI 构建,它解决了很多上面提到的问题。 通过使用 monorepo 的方式,把多个 app 和 lib 托管在同一个 repo (在 这里阅读更多相关信息)。

借助 Nrwl / Nx 开发人员可以:

  • 快速创建 app 特定的 lib
  • 快速提取可复用的库
  • 验证可复用 lib 的代码变更不会破坏任何依赖它的 app 和 lib(Nx 附带增量构建和测试仅受 PR 影响的 app 的命令)
  • 同时重构多个 app 和 lib

Nx 强制你使用第三方库的单一版本(虽然必要时可以绕过它),这一点很重要。如果你的 app 和 lib 依赖于 TypeScript 的不同版本,则它们可能无法被复用。而且使用不同的库组合可能会导致难以调试的问题。即使你不使用 Nx,我依然建议创建一个名为 third-party 的软件包,列出主要的第三方依赖项,并使其他 app 和 lib 依赖于它。

Nx 并不处理代码的所有权问题,因为这取决于代码的托管方式。如果使用 GitHub,则可以利用 CODEOWNERS 文件

无论你是否选择使用 Nx,请确保明确定义上述五个操作的工作流程,记录在文档里,衡量下开发人员执行这些操作的速度。

落地最佳实践和自动化

Code review 和口耳相传是落地小团队最佳实践的好方法。但是,组织越大,此过程就越耗费资源,容易出错,并且很难统一。这时我们就需要使用工具来解决此问题。

创建小型可复用 lib

往往最简单的事情可能会产生最大的影响。

例如,创建可复用的小型 lib。即使只把 30 行代码提取到可复用的 lib 中,你也可以提高团队的工作效率并节省数周的工作量。

例如,Nrwl 团队观察到每一个单独的应用都存在很多竟态条件。其中一些是显而易见的,而另一些是非常微妙的,很难溯源。即使是经验最丰富的高级工程师也难免会忽视微妙的竟态条件。

在帮助许多客户解决这些类型的问题后,我们不得不问自己:“我们能否建立一个小工具来消除(或避免)产生竟态条件的代码?”

因此,我们实现了一个 50 行代码的 service,该 service 与 NgRx Effects 集成,消除了 Web 应用中出现的许多竟态条件。这个功能的代码量非常小,你可以阅读它的源码并能很快了解它的作用。我们把它变成了 Nx 的一部分。你应该鼓励你团队的开发人员也这样做,可以从导致最多问题的三个方面开始:测试,状态管理和路由。

创建 Schematics 实现代码生成

Angular CLI 使用 schematics 库进行代码生成。 Nrwl / Nx 基于 Angular CLI 构建,它通过定制一个自定义的 schematics 集合,为多 app/多 lib 项目定制 CLI 功能。企业团队应该更进一步,创建一个你所在组织中使用的 schematics 集合。例如,如果在集成测试中使用模拟数据,请创建 schematics 以生成相应填充素材。

通过衡量开发人员实现简单代码生成器所需的时间,只有在花费仅需几分钟的时候,人们才会这样做。

创建自定义 Lint 规则

TSLint 擅长两件事:确保开发人员不会犯下低级错误,并使代码风格更加统一。TSLint 可以在 CI上 运行,并且还可以与所有主要编辑器和 IDE 集成。

引入这个工具,做好相关配置,并通过文档记录执行 TSLint 检查的流程,以便开发人员可以快速执行。

使一切自动化(使用代码格式化工具)

令人惊讶的是,很少有组织会配置代码自动格式化等流程。很大程度上,是因为许多人认为这不是开发的重点。如果需要数周的时间才能配置正确,那么我同意。但是,实际上,它可能只需要30分钟,并且具有惊人的效果:它使代码风格更加统一,并且消除了一大堆问题。

团队使用格式化工具协作的最大好处是利于代码提交和代码审查。代码的变更作为整个仓库的增量,不应该引入混乱的格式来影响一些关键代码的修改。

Nrwl Nx 附带了一些命令来在本地和 CI 中设置代码自动格式化。

TLDR/架构清单

这里列出了一份大型组织里架构师的清单列表,可以帮助他们的团队提高工作效率。

  1. 定义用于构建 Angular 应用(CLI,Nx,NgRx)的工具集。开发人员喜欢搭建自定义构建工具,但使用标准工具往往是更好的选择,至少从长远来看是这样。创建一个使用这些工具构建的示例仓库。
  2. 定义创建/提取新库的过程。 衡量下需要多长时间。
  3. 定义一个流程,用于验证代码更改为可复用 lib 不会破坏任何依赖它的 app 和 lib,区分本地开发和 CI 环境。衡量下需要多长时间。
  4. 定义重构多个 app 和 lib 的流程
  5. 定义分配和检查模块所有权的流程。没有良好的所有权模式,开发就会产生混乱。
  6. 定义分支管理策略:基于主干的开发或基于功能的开发,试着让所有 app 和 lib 都统一。
  7. 定义显式地管理第三方依赖关系的策略
  8. 定义状态管理和副作用管理最佳实践,使之尽可能自动化。
  9. 定义测试最佳实践,使之尽可能自动化。
  10. 从一开始就创建一个特定于组织的 tslint 包。这是推广最佳实践的好方法。
  11. 从一开始就创建一个特定于组织的 schematics 包。这是推广最佳实践的好方法。
  12. 自动化所有可自动化的内容(例如,配置代码自动格式化)。

不出所料,Nrwl Nx 帮助我们实现了很多其中的功能。毕竟,这就是我们创造它的原因。但是这与大多数架构的决策一样,并不是说仅仅使用某个工具就好,而是要仔细地选择工具和流程。

了解更多