Mix 和 OTP-依赖和伞型项目

  1. 外部依赖
  2. 内部依赖
  3. 伞型项目
  4. 伞型项目里的依赖
  5. 总结

本章是Mix和OTP教程的一部分,它依赖这个教程的前面章节。要获得更多信息,请阅读本教程的第一章,或者查看本教程的章节索引。

在本章,我们将讨论用Mix如何管理依赖。

我们的 kv 应用已经完成,所以是时候实现一个服务器,它将处理在第一章我们定义的请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 项目作为一个外包依赖。

安装外部依赖是简单的。最常见的,我们通过在我们的mix.exs文件里的deps函数内列出依赖来使用 Hex包管理器

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

这个依赖指向已经发布到Hex的Plug的1.x.x版本系列的最新版本。这由版本号数字前的 ~> 符号表示。有关指定版本要求的更多信息,请参见版本模块的文档

通常,稳定版本被发布到Hex。如果你想依赖一个还处于开发中的外部依赖,Mix也能够管理git依赖:

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

你将注意到,当你增加一个依赖到你的项目,Mix生成一个mix.lock文件来保证可重复构建。这个lock文件必需被提交到你的版本控制系统,以便保证所有使用这个项目的人将使用和你一样的依赖版本。

Mix提供很多任务来处理依赖,这些任务可以用 mix help 来列出来:

1
2
3
4
5
6
7
8
$ mix help
mix deps # 列出依赖和它们的状态
mix deps.clean # 删除给定的依赖文件
mix deps.compile # 编译依赖
mix deps.get # 获取所有过时的依赖
mix deps.tree # 打印依赖树
mix deps.unlock # 解锁给定的依赖
mix deps.update # 更新给定的依赖

最常用的任务是 mix deps.get 和 mix deps.update 。一旦被获取到,依赖被自动编译。你可以通过输入 mix help deps 和在 Mix.Tasks.Deps 模块文档里读到更多关于依赖的内容。

内部依赖

内部依赖项是特定于你的项目的依赖。它们通常在你的项目/公司/组织范围之外是没有意义的。大多数时候,你想让他们私有化,无论是由于技术,经济或商业原因。

如果你有一个内部依赖,Mix支持两种方法与它一起工作:git仓库和伞型项目。

例如,如果你推送 kv 应用到一个git仓库,你将需要在你的依赖代码里列出它以便使用它:

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

如果仓库是私有的,您可能需要指定私有URL git@github.com:YOUR_ACCOUNT/kv.git 。在任何情况下,Mix将能够把它拿来给你,只要你有适当的凭据。

在Elixir里不鼓励使用git依赖作为内部依赖。记住:运行时和Elixir生态已经提供了应用这个概念。因此,我们希望你经常将你的代码分解成可以被逻辑地组织的应用,即使在单个项目中也是如此。

然而,如果你把每个应用作为一个独立的项目放入一个git仓库,那么你的项目可能非常难以维护,因为你将花费大量时间来管理那些git仓库而不是写你代码。

因为这个原因,Mix支持“伞型项目”。伞型项目允许你创建一个项目,它拥有许多项目,而且保持它们在一个单独源码仓库里。这正是下一节我们将要探索的风格。

让我们创建一个新的Mix项目。我们将创造性地命名它为 kv_umbrella ,这个新项目里面将拥有已经存在的 kv 应用和新的 kv_server 应用。目录结构将如下所示:

1
2
3
4
+ kv_umbrella
+ apps
+ kv
+ kv_server

关于这个方法令人感兴趣的事情是,Mix有许多遍历措施来处理这样的项目,诸如用一个命令将apps里的所有应用都编译和测试的能力。但是,尽管它们都在apps目录里,它们依然是彼此解耦的,所以,如果你想的话,你就可以独立地构建、测试和部署每一个应用。

所以让我们开始吧!

伞型项目

让我们用 mix new 开始一个新项目。这个新项目将被命名为 kv_umbrella ,并且在创建它的时候,我们需要传递 --umbrella 参数。不要在已经存在的 kv 项目里创建这个新项目!

1
2
3
4
5
6
7
$ mix new kv_umbrella --umbrella
* creating .gitignore
* creating README.md
* creating mix.exs
* creating apps
* creating config
* creating config/config.exs

从打印出来的信息我们可以看到生成的文件少了很多。生成的mix.exs文件也不同。让我们看一看这个文件(注释已经被删除):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
defmodule KvUmbrella.Mixfile do
use Mix.Project

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

defp deps do
[]
end
end

使得这个项目与前面的项目不同的是在项目定义里有一个 apps_path: “apps” 条目。它的意思是这个项目将表现得像一把伞。这样的项目既没有源码文件也没有测试用例,但是它们可以有它们自己的依赖。每一个子应用必须定义在apps目录里。

让我们进入apps目录里开始构建 kv_server 。这一次,我们将传递 --sup 标志,它将告诉Mix自动为我们生成一颗监督树,替代在前面章节里如我们所做的手工构建监督树:

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

生成的文件与我们第一次为 kv 所生成的相似,有一些不同。让我们打开 mix.exs :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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.4",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps]
end

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

defp deps do
[]
end
end

首先,因为我们在kv_umbrella/apps里创建这个项目,Mix自动监测到伞型结构并且增加四行到项目定义里:

1
2
3
4
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",

这些选项的意思是,所有选项将被放置到 kv_umbrella/deps ,并且它们将共享同样的构建、配置和锁文件。这确保为整个伞型结构,依赖将被获取并被编译一次,而不是每一个伞型应用一次。

第二个改变是在 mix.exs 里的 application 函数中:

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

因为我们传递了 --sup 标志,Mix自动增加了 mod: {KVServer.Application, []} ,指明 KVServer.Application 是我们的应用回调模块。KVServer.Application 将启动我们的应用监督树。

事实上,让我们打开 lib/kv_server/application.ex :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
defmodule KVServer.Application do
# See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications
@moduledoc false

use Application

def start(_type, _args) do
import Supervisor.Spec, warn: false

# Define workers and child supervisors to be supervised
children = [
# Starts a worker by calling: KVServer.Worker.start_link(arg1, arg2, arg3)
# worker(KVServer.Worker, [arg1, arg2, arg3]),
]

# See http://elixir-lang.org/docs/stable/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,而不是使用 Supervisor 模块定义一个名为 KVServer.Supervisor 的监督者,它便捷地在行内定义了监督者!你可以通过阅读Supervisor模块文档获得更多这样的监督者的内容。

我们已经可以试试我们第一个伞型子应用。我们可以在 apps/kv_server 里运行测试,但是那样不好玩。相反,到伞型项目的根目录,运行 mix test :

1
$ mix test

它正常运行!

因为我们想让 kv_server 最终使用我们在 kv 里定义的功能,因此我们需要将 kv 作为一个依赖加入我们的应用。

伞型项目里的依赖

Mix支持一种简单的机制使得一个伞型子应用依赖于另一个伞型子应用。打开 apps/kv_server/mix.exs 并且按如下所示修改 deps/0 函数:

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

上述代码使得 :kv作为 :kv_server 里的一个可用依赖并且在启动 :kv_server 前自动启动 :kv 应用。

最后,拷贝我们已经构建好的 kv 应用到我们新的伞型项目的 apps 目录里。最终的目录结构和我们前面提到的结构匹配上:

1
2
3
4
+ kv_umbrella
+ apps
+ kv
+ kv_server

我们现在需要修改 apps/kv/mix.exs 让其包含我们已经在 apps/kv_server/mix.exs 所见到的伞型相关条目。打开 apps/kv/mix.exs 并将如下内容加入 project 函数:

1
2
3
4
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",

现在你可以从伞型项目根目录用 mix test 运行两个应用的测试。太棒了!

请记住,伞型项目是一个方便工具用来帮助你组织和管理你的应用。apps 目录里的应用仍然是彼此解耦的。它们之间的依赖必须明确地列出来。这允许它们被一起开发,但是如果需要的话,独立地编译、测试和部署。

总结

在这一章,我们学到了更多关于Mix依赖和伞型项目的内容。我们决定构建一个伞型项目,因为我们认为 kv 和 kv_server 是内部依赖的,其重要性只存在于这个项目的上下文里。

在将来,你将写应用并且你将注意到它们可以被提取到一个简洁的单元,这个单元能被不同的项目使用。在这种情况下,使用Git或Hex依赖是要走的路。

这里有几个问题,当你使用依赖的时候你可以问自己。问题是:这个应用在这个项目之外有意义吗?

  • 如果没有,则使用一个有伞型子应用的伞型项目。
  • 如果有,则这个项目在你的公司或组织外部可以被共享吗?
    • 不可以, 则使用一个私有git仓库。
    • 如果可以,则推送你的代码到一个git仓库并且用Hex做频繁的发布。

我们的伞型项目构建好并运行了,是时候开始写我们的服务器了。

原文链接: http://elixir-lang.org/getting-started/mix-otp/dependencies-and-umbrella-apps.html