在本章中,我们将讨论如何管理 Mix 中的依赖项。
我们的 kv 应用程序已经完成,现在是时候实现处理我们在第一章中定义的请求的服务器了:
但是,我们不会向 kv 应用程序添加更多代码,而是将 TCP 服务器构建为另一个应用程序,即 kv 应用程序的客户端。由于整个运行时和 Elixir 生态系统都面向应用程序,因此将我们的项目拆分为可以协同工作的较小应用程序而不是构建一个庞大的单片应用程序是有意义的。
在创建新应用程序之前,我们必须讨论 Mix 如何处理依赖项。实际上,我们通常使用两种依赖项:内部依赖项和外部依赖项。Mix 支持同时处理这两种依赖项的机制。
外部依赖项
外部依赖项是与您的业务领域无关的依赖项。例如,如果您需要分布式 KV 应用程序的 HTTP API,则可以将 Plug 项目用作外部依赖项。
安装外部依赖项很简单。最常见的是,我们使用 Hex 包管理器,通过在我们的 mix.exs 文件中的 deps 函数内列出依赖项:
此依赖项指的是已推送到 Hex 的 1.x.x 版本系列中的最新版本的 Plug。这由版本号前面的 ~> 表示。有关指定版本要求的更多信息,请参阅 Version 模块的文档。
通常,稳定版本会推送到 Hex。如果您想依赖仍在开发中的外部依赖项,Mix 也能够管理 Git 依赖项:
您会注意到,当您向项目添加依赖项时,Mix 会生成一个 mix.lock 文件,以确保可重复构建。必须将锁定文件签入到您的版本控制系统中,以保证使用该项目的每个人都使用与您相同的依赖项版本。
Mix 提供了许多处理依赖项的任务,可在 mix 帮助中查看:
最常见的任务是 mix deps.get 和 mix deps.update。一旦获取,依赖项就会自动为您编译。您可以通过输入 mix help deps 以及 Mix.Tasks.Deps 模块的文档来了解有关 deps 的更多信息。
内部依赖项
内部依赖项是特定于您的项目的依赖项。它们通常在您的项目/公司/组织范围之外没有意义。大多数时候,您都希望将它们保密,无论是出于技术、经济还是商业原因。
如果您有内部依赖项,Mix 支持两种方法来处理它们:Git 存储库或伞状项目。
例如,如果您将 kv 项目推送到 Git 存储库,则需要将其列在您的 deps 代码中才能使用它:
但是,如果存储库是私有的,您可能需要指定私有 URL git@github.com:YOUR_ACCOUNT/kv.git。无论如何,只要您有适当的凭据,Mix 就能为您获取它。
在 Elixir 中,不鼓励使用 Git 存储库来处理内部依赖项。请记住,运行时和 Elixir 生态系统已经提供了应用程序的概念。因此,我们希望您经常将代码分解为可以按逻辑组织的应用程序,即使在单个项目中也是如此。
但是,如果您将每个应用程序作为单独的项目推送到 Git 存储库,您的项目可能会变得非常难以维护,因为您将花费大量时间管理这些 Git 存储库,而不是编写代码。
出于这个原因,Mix 支持“umbrella projects”。umbrella projects用于构建在单个存储库中一起运行的应用程序。这正是我们将在下一节中探讨的风格。
让我们创建一个新的 Mix 项目。我们将其创造性地命名为 kv_umbrella,这个新项目将包含现有的 kv 应用程序和新的 kv_server 应用程序。目录结构将如下所示:
这种方法的有趣之处在于,Mix 为处理此类项目提供了许多便利,例如,只需一个命令即可编译和测试应用程序内的所有应用程序。但是,即使它们都在应用程序内一起列出,它们仍然彼此分离,因此您可以根据需要单独构建、测试和部署每个应用程序。
让我们开始吧!
Umbrella projects
让我们使用 mix new 开始一个新项目。这个新项目将被命名为 kv_umbrella,我们需要在创建它时传递 --umbrella 选项。不要在现有的 kv 项目中创建这个新项目!
从打印的信息中,我们可以看到生成的文件少了很多。生成的 mix.exs 文件也不同。让我们看一看(注释已被删除):
这个项目与前一个项目的不同之处在于项目定义中的 apps_path:“apps”条目。这意味着这个项目将充当一个命名空间。这样的项目没有源文件也没有测试,但它们可以有自己的依赖项。每个子应用程序都必须在 apps 目录中定义。
让我们进入 apps 目录并开始构建 kv_server。这一次,我们将传递 --sup 标志,它将告诉 Mix 自动为我们生成一个监督树,而不是像前面章节那样手动构建一个:
生成的文件与我们第一次为 kv 生成的文件类似,但有一些不同。
首先,由于我们在 kv_umbrella/apps 中生成了这个项目,Mix 自动检测到了树状结构并在项目定义中添加了四行:
因为我们传递了 --sup 标志,Mix 自动添加了 mod: {KVServer.Application, []},指定 KVServer.Application 是我们的应用程序回调模块。KVServer.Application 将启动我们的应用程序监督树。
事实上,让我们打开 lib/kv_server/application.ex:
请注意,它定义了应用程序回调函数 start/2,而不是定义使用 Supervisor 模块的名为 KVServer.Supervisor 的主管,而是方便地内联定义了主管!您可以通过阅读 Supervisor 模块文档来了解有关此类主管的更多信息。
我们已经可以尝试我们的第一个umbrella子项了。我们可以在 apps/kv_server 目录中运行测试,但那没什么意思。相反,转到umbrella project的根目录并运行 mix test:
成功了!
由于我们希望 kv_server 最终使用我们在 kv 中定义的功能,因此我们需要将 kv 作为依赖项添加到我们的应用程序中。
Umbrella Project中的依赖关系
umbrella project中应用程序之间的依赖关系仍必须明确定义,而 Mix 可轻松实现此目的。打开 apps/kv_server/mix.exs 并将 deps/0 函数更改为以下内容:
上面的行使 :kv 可用作 :kv_server 中的依赖项,并在服务器启动之前自动启动 :kv 应用程序。
最后,将我们迄今为止构建的 kv 应用程序复制到新umbrella project中的 apps 目录中。最终的目录结构应与我们之前提到的结构相匹配:
我们现在需要修改 apps/kv/mix.exs 以包含我们在 apps/kv_server/mix.exs 中看到的umbrella条目。打开 apps/kv/mix.exs 并添加到 project/0 函数:
现在,您可以使用 mix test 从umbrella根目录运行两个项目的测试。太棒了!
合理使用Umbrella
umbrella project是一种便利,可帮助您组织和管理多个应用程序。虽然它在应用程序之间提供了一定程度的分离,但这些应用程序并未完全解耦,因为它们共享相同的配置和相同的依赖项。
将多个应用程序保存在同一个存储库中的模式称为“mono-repo”。umbrella project通过提供同时编译、测试和运行多个应用程序的便利来最大化这种模式。
如果您发现自己处于想要在每个应用程序中为相同的依赖项使用不同的配置或使用不同的依赖项版本的位置,那么您的代码库很可能已经超出了umbrella所能提供的范围。
好消息是,拆分umbrella非常简单,因为您只需将应用程序移出umbrella project的 apps/ 目录并更新项目的 mix.exs 文件以不再设置 build_path、config_path、deps_path 和 lockfile 配置。您可以通过多种方式依赖umbrella project之外的私有项目:
1.将其移动到同一存储库中的单独文件夹,并使用路径依赖项(mono-repo 模式)指向它
2.将存储库移动到单独的 Git 存储库并依赖它
3.将项目发布到私有 Hex.pm 组织
总结
在本章中,我们学习了更多关于 Mix 依赖项和umbrella project的知识。虽然我们可以在没有服务器的情况下运行 kv,但我们的 kv_server 直接依赖于 kv。通过将它们分解为单独的应用程序,我们可以更好地控制它们的开发和测试方式。
使用umbrella application时,在它们之间划出明确的界限非常重要。我们即将推出的 kv_server 必须仅访问 kv 中定义的公共 API。将您的umbrella application视为任何其他依赖项,甚至是 Elixir 本身:您只能访问公共和记录的内容。触及依赖项中的私有功能是一种不好的做法,最终会导致您的代码在新版本发布时崩溃。
umbrella application还可以用作最终从代码库中提取应用程序的垫脚石。例如,想象一个必须向其用户发送“推送通知”的 Web 应用程序。整个“推送通知系统”可以作为umbrella application中的单独应用程序进行开发,具有自己的监督树和 API。如果您遇到另一个项目需要推送通知系统的情况,则可以将该系统移至私有存储库或 Hex 包。
最后,请记住,umbrella project中的应用程序都共享相同的配置和依赖项。如果umbrella中的两个应用程序需要以截然不同的方式配置相同的依赖项,甚至使用不同的版本,那么您可能已经超出了umbrella带来的好处。请记住,您可以打破umbrella,同时仍能利用“mono-repos”背后的好处。
随着我们的umbrella project启动并运行,是时候开始编写我们的服务器了。