依赖性和伞式项目

本章是Mix和OTP指南的一部分,它取决于本指南的前几章。有关更多信息,请阅读简介指南或查看边栏中的章节索引。

在本章中,我们将讨论如何管理Mix中的依赖关系。

我们的kv应用程序是完整的,因此是时候实施服务器来处理我们在第一章中定义的请求:

CREATE shopping
OK

PUT shopping milk 1
OK

PUT shopping eggs 3
OK

GET shopping milk
1
OK

DELETE shopping eggs
OK

但是,我们不是将更多的代码添加到kv应用程序中,而是将TCP服务器构建为另一个作为应用程序客户端的kv应用程序。由于整个运行时间和Elixir生态系统都面向应用程序,因此将我们的项目分解为能够一起工作的小型应用程序是合理的,而不是构建一个庞大而庞大的应用程序。

在创建我们的新应用程序之前,我们必须讨论Mix如何处理依赖关系。实际上,我们通常使用两种依赖关系:内部和外部依赖关系。Mix支持与它们一起工作的机制。

外部依赖

外部依赖关系与您的业务领域无关。例如,如果您的分布式KV应用程序需要HTTP API,则可以使用Plug项目作为外部依赖项。

安装外部依赖关系很简单。最常见的情况是,我们使用Hex Package Manager,在我们的mix.exs文件中列出deps函数中的依赖关系:

def deps do
  [{:plug, "~> 1.0"}]
end

此依赖关系是指已推送至十六进制的1.xx版系列中最新版本的Plug。这由~>前面的版本号表示。有关指定版本要求的更多信息,请参阅版本模块文档

通常情况下,稳定版本会推送到Hex。如果您想依赖仍在开发中的外部依赖项,Mix也能够管理git依赖项:

def deps do
  [{:plug, git: "git://github.com/elixir-lang/plug.git"}]
end

您会注意到,当您向项目添加依赖项时,Mix会生成一个mix.lock可保证可重复构建的文件。必须将锁定文件签入到版本控制系统中,以确保使用该项目的每个人都将使用与您相同的相关性版本。

Mix提供了许多用于处理依赖关系的任务,可以在以下位置看到mix help

$ mix help
mix deps              # Lists dependencies and their status
mix deps.clean        # Deletes the given dependencies' files
mix deps.compile      # Compiles dependencies
mix deps.get          # Gets all out of date dependencies
mix deps.tree         # Prints the dependency tree
mix deps.unlock       # Unlocks the given dependencies
mix deps.update       # Updates the given dependencies

最常见的任务是mix deps.getmix deps.update。一旦获取,依赖关系会自动为您编译。您可以通过键入mix help deps以及在Mix.Tasks.Deps模块文档中阅读更多关于缩进的信息

内部依赖关系

内部依赖关系是特定于您的项目的依赖关系。他们通常在您的项目/公司/组织范围之外没有意义。大多数情况下,无论是出于技术,经济或商业原因,您都希望保持私密性。

如果你有内部依赖,Mix支持两种方法来处理它们:git仓库或伞形项目。

例如,如果将kv项目推送到git存储库,则需要将其列在代码中以便使用:

def deps do
  [{:kv, git: "https://github.com/YOUR_ACCOUNT/kv.git"}]
end

如果存储库是私有的,您可能需要指定私有URL 。无论如何,只要您拥有适当的凭据,Mix就可以为您取回。[email protected]:YOUR_ACCOUNT/kv.git

在Elixir中,使用git依赖关系来进行内部依赖是有点令人沮丧的。请记住,运行时和Elixir生态系统已经提供了应用程序的概念。因此,我们希望您经常将您的代码分解成可以逻辑组织的应用程序,即使在单个项目中也是如此。

但是,如果将每个应用程序作为单独项目推送到git存储库,那么您的项目可能会变得非常难以维护,因为您将花费大量时间管理这些git存储库,而不是编写代码。

出于这个原因,Mix支持“伞项目”。伞项目用于构建在单个存储库中一起运行的应用程序。这正是我们将在下一节中探讨的风格。

我们来创建一个新的Mix项目。我们将创造性地命名它kv_umbrella,并且这个新项目将包含现有的kv应用程序和新的kv_server应用程序。目录结构如下所示:

+ kv_umbrella
  + apps
    + kv
    + kv_server

关于这种方法的有趣之处在于,Mix为这些项目提供了许多便利,例如apps使用单个命令编译和测试所有应用程序的能力。但是,即使它们都在内部一起列出apps,它们仍然彼此分离,所以如果您愿意,您可以单独构建,测试和部署每个应用程序。

所以让我们开始吧!

伞项目

让我们开始一个新的项目mix new。这个新项目将被命名kv_umbrella,我们需要--umbrella在创建它时通过选项。不要在现有kv项目中创建这个新项目!

$ mix new kv_umbrella --umbrella
* creating .gitignore
* creating README.md
* creating mix.exs
* creating apps
* creating config
* creating config/config.exs

从打印的信息中,我们可以看到生成的文件少得多。生成的mix.exs文件也不同。让我们来看看(评论已被删除):

defmodule KvUmbrella.Mixfile do
  use Mix.Project

  def project do
    [
      apps_path: "apps",
      start_permanent: Mix.env == :prod,
      deps: deps()
    ]
  end

  defp deps do
    []
  end
end

这个项目与前一个项目不同的是apps_path: "apps"项目定义中的条目。这意味着这个项目将成为一把伞。这些项目没有源文件也没有测试,尽管它们可以有自己的依赖关系。每个子应用程序必须在apps目录内定义。

让我们进入apps目录并开始构建kv_server。这一次,我们要通过这个--sup标志,它会告诉Mix为我们自动生成一个监督树,而不是像前几章那样手动创建一个:

$ cd kv_umbrella/apps
$ mix new kv_server --module KVServer --sup

生成的文件与我们首次生成的文件类似,但kv有一些差异。开放吧mix.exs

defmodule KVServer.Mixfile do
  use Mix.Project

  def project do
    [
      app: :kv_server,
      version: "0.1.0",
      build_path: "../../_build",
      config_path: "../../config/config.exs",
      deps_path: "../../deps",
      lockfile: "../../mix.lock",
      elixir: "~> 1.6-dev",
      start_permanent: Mix.env == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger],
      mod: {KVServer.Application, []}
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
      # {:sibling_app_in_umbrella, in_umbrella: true},
    ]
  end
end

首先,由于我们在里面生成了这个项目kv_umbrella/apps,Mix自动检测伞形结构并在项目定义中添加了四行:

build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",

这些选项意味着所有的依赖项都将被检出kv_umbrella/deps,并且它们将共享相同的构建,配置和锁定文件。这确保了依赖关系将在整个伞状结构中被取出和编译一次,而不是每个伞状应用一次。

第二个变化是在application里面的函数中mix.exs

def application do
  [
    extra_applications: [:logger],
    mod: {KVServer.Application, []}
  ]
end

因为我们通过了--sup标志,所以Mix会自动添加mod: {KVServer.Application, []},指定这KVServer.Application是我们的应用程序回调模块。KVServer.Application将启动我们的应用程序监督树。

事实上,我们开放lib/kv_server/application.ex

defmodule KVServer.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  def start(_type, _args) do
    # List all child processes to be supervised
    children = [
      # Starts a worker by calling: KVServer.Worker.start_link(arg)
      # {KVServer.Worker, arg},
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: KVServer.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

请注意,它定义了应用程序回调函数,start/2而不是定义一个KVServer.Supervisor使用该Supervisor模块的管理程序,它可以方便地定义管理程序内联!您可以通过阅读Supervisor模块文档来了解更多关于这些主管的信息

我们已经可以试用我们的第一把雨伞了。我们可以在apps/kv_server目录中运行测试,但那不会很有趣。相反,转到伞项目的根目录并运行mix test

$ mix test

它行得通!

由于我们kv_server最终要使用我们定义的功能kv,因此我们需要将其kv作为依赖添加到我们的应用程序中。

在伞的依赖

一个综合项目中的应用程序之间的依赖关系仍然必须明确定义,Mix可以很容易地实现。打开apps/kv_server/mix.exs并将deps/0功能更改为以下内容:

defp deps do
  [{:kv, in_umbrella: true}]
end

上面的行:kv可以作为内部的依赖关系使用,:kv_server:kv在服务器启动之前自动启动应用程序。

最后,将kv我们迄今为止建立的应用程序复制到apps我们新的综合项目中的目录中。最终的目录结构应该与我们前面提到的结构匹配:

+ kv_umbrella
  + apps
    + kv
    + kv_server

我们现在需要修改apps/kv/mix.exs以包含我们看到的伞条目apps/kv_server/mix.exs。打开apps/kv/mix.exs并添加到该project功能:

build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",

现在,您可以使用伞根来为两个项目运行测试mix test。棒!

不要喝the kool aid

总括项目可帮助您组织和管理多个应用程序。虽然它在应用程序之间提供了一定程度的分离,但这些应用程序并未完全分离,因为它们被假定为共享相同的配置和相同的依赖关系。

将多个应用程序保存在同一个存储库中的模式称为“单声道回购”。伞项目通过提供便利来一次编译,测试和运行多个应用程序来最大化这种模式。

如果你发现自己想要在不同的应用程序中使用不同的配置,或者使用不同的依赖版本,那么很可能你的代码库已经超出了雨伞所能提供的范围。

好消息是,打破一个保护伞是非常简单的,因为您只需将应用程序移动到该保护伞项目的apps/目录之外。在最糟糕的情况下,您可以放弃伞项目和所有相关配置(build_path,和)config_pathdeps_pathlockfile通过将所有应用程序放在同一个存储库中,仍然利用“单声道回购”模式。每个应用程序都有自己的依赖关系和配置。这些应用程序之间的依赖关系仍然可以通过使用该:path选项来明确列出(与之相反:git)。

加起来

在本章中,我们学习了更多关于混合依赖和伞式项目的知识。虽然我们可能kv没有服务器运行,但我们kv_server直接依赖kv。通过将它们分解成单独的应用程序,我们可以更好地控制它们的开发和测试方式。

在使用伞式应用程序时,在它们之间建立清晰的边界很重要。我们即将到来的kv_server必须只访问在kv。中定义的公共API 。将您的保护伞应用视为任何其他依赖项或Elixir本身:您只能访问公开和记录的内容。在您的依赖关系中实现私有功能是一种糟糕的做法,最终会导致您的代码在新版本启动时中断。

Umbrella应用程序也可以用作最终从代码库中提取应用程序的垫脚石。例如,想象一个必须向用户发送“推送通知”的Web应用程序。整个“推送通知系统”可以作为一个独立的应用程序开发,具有自己的监督树和API。如果您遇到另一个项目需要推送通知系统的情况,系统可以移动到私有存储库或hex.pm包。

开发人员也可能使用保护伞项目将大型商业领域分开。这里的警告是确保域不依赖于彼此(也称为循环依赖)。如果遇到这种情况,这意味着这些应用程序并不像您原先想象的那样相互隔离,而且您还需要解决架构和设计问题。

最后,请记住,一个综合项目中的应用程序都具有相同的配置和相关性。如果您的伞中的两个应用程序需要以完全不同的方式配置相同的依赖项,或者甚至使用不同的版本,那么您可能已经超出了遮阳伞带来的好处。请记住,您可以打破这个保护伞,并仍然利用“单一回购”背后的好处。

随着我们的伞式项目的启动和运行,现在是编写我们的服务器的时候了。