在我以前的文章中,我们讨论了各种Elixir术语,并编写了大量代码。 但是,我们尚未讨论的是如何组织和组织代码,以便于维护和发布。
应用程序对于Erlang和Elixir来说非常常见,并用于构建充当独立单元的可重用组件。 一个应用程序可能具有自己的监视树和配置,并且可以依赖本地或某个远程服务器上可用的其他应用程序。 总而言之,使用应用程序并不是那么复杂,例如,来自Ruby世界的人会发现许多熟悉的概念。
在本文中,您将学习什么是应用程序,如何创建它们,如何指定和安装依赖项以及如何提供环境值。 在本文的结尾,我们将进行一些练习并创建一个基于Web的计算器。
我将在本文( 几个月前发布)中使用Elixir 1.5,但所有解释的概念也应适用于1.4版。
应用程序?
有人可能会说“应用程序”一词不是很合适,因为在Erlang和Elixir中,它实际上是指一个组件或一些具有一系列依赖关系的代码。 该应用程序本身也可以用作依赖项-在Ruby世界中,我们将其称为“宝石”。
总而言之,应用程序在Elixir中非常常见,可以让您制作可重用的组件,同时还提供简单的依赖关系管理。 它们由一个或多个模块组成,这些模块具有零个或多个依赖关系,并由应用程序资源文件描述。 该文件包含有关应用程序名称,版本,其模块,依赖项和其他内容的信息。 您可以手动创建资源文件,但是使用混合工具可以更轻松地完成该操作,该工具还将为您准备正确的文件夹结构。
因此,让我们看看如何创建一个新的Elixir应用程序!
新申请
要创建一个新的应用程序,您需要做的就是运行以下命令:
mix new app_name
我们还可以提供--sup
标志来为我们创建一个空的主管。 让我们以这种方式创建一个名为Sample
的新应用程序:
mix new sample --sup
此命令将为您创建一个示例目录,其中包含少量文件和文件夹。 让我快速指导您完成这些操作:
- config文件夹包含一个唯一的文件config.exs ,您可以猜到它为应用程序提供配置。 最初它有一些有用的注释,但是没有配置。 请注意,顺便说一下,此文件中提供的配置仅限于应用程序本身。 如果您将应用程序作为依赖项加载,则将有效地忽略其config.exs 。
- lib是应用程序的主文件夹,其中包含sample.ex文件和带有application.ex文件的示例文件夹。 application.ex定义了一个带有
start/2
函数的回调模块,该模块创建一个空的主管。 - test是包含应用程序自动测试的文件夹。 我们不会在本文中讨论自动化测试。
- mix.exs是包含有关应用程序的所有必要信息的文件。 这里有多种功能。 在
project
功能内,您提供应用程序的名称(作为原子),版本和环境。application
功能包含有关应用程序模块回调和运行时依赖项的信息。 在我们的例子中,Sample.Application
设置为应用程序模块回调(可以视为主入口点),并且它必须定义一个start/2
函数。 如上所述,此功能已经由mix
工具为我们创建。 最后,deps
函数列出了构建时依赖性。
依存关系
区分运行时依赖性和构建时依赖性非常重要。 构建时依赖项在编译期间由mix
工具加载,并且基本上已编译到您的应用程序中。
例如,可以从诸如GitHub之类的服务或从hex.pm网站(用于存储Elixir和Erlang数千个组件的外部程序包管理器)中获取它们。 运行时依赖项在应用程序启动之前启动。 它们已经被编译并可供我们使用。
有两种方法可以在mix.exs文件中指定构建时依赖性。 如果您想使用hex.pm网站上的应用程序,只需说:
{:dependency_name, "~> 0.0.1"}
第一个参数始终是代表应用程序名称的原子。 第二个是需求,即您希望使用的版本,它是由Version模块解析的。 在此示例中, ~>
表示我们希望至少下载0.0.1
或更高版本,但小于0.1.0
。 如果我们说~> 1.0
,则意味着我们要使用大于或等于1.0
但小于2.0
。 也有==
, >
, <
, >=
和<=
等运算符可用。
也可以直接指定:git
或:path
选项:
{:gettext, git: "https://github.com/elixir-lang/gettext.git", tag: "0.1"}
{:local_dependency, path: "path/to/local_dependency"}
还有一个:github
快捷方式,它允许我们仅提供所有者的名称和存储库的名称:
{:gettext, github: "elixir-lang/gettext"}
要下载并编译所有依赖项,请运行:
mix deps.get
如果您没有Hex客户端,它将安装一个Hex客户端,然后检查是否需要更新任何依赖项。 例如,您可以将Poison(一种解析JSON的解决方案)指定为依赖项,如下所示:
defp deps do
[
{:poison, "~> 3.1"}
]
end
然后运行:
mix deps.get
您将看到类似的输出:
Running dependency resolution...
Dependency resolution completed:
poison 3.1.0
* Getting poison (Hex package)
Checking package (https://repo.hex.pm/tarballs/poison-3.1.0.tar)
Fetched package
毒药现已编译并可以在您的PC上使用。 此外, mix.lock文件将自动创建。 该文件提供了启动应用程序时要使用的依赖项的确切版本。
要了解有关依赖关系的更多信息,请运行以下命令:
mix help deps
再次行为
应用程序是行为,就像我们在前面的文章中讨论过的GenServer和Supervisor一样。 如前所述,我们通过以下方式在mix.exs文件中提供了一个回调模块:
def application do
[
mod: {Sample.Application, []}
]
end
Sample.Application
是模块的名称,而[]
可能包含要传递给start/2
函数的参数列表。 必须实现start/2
功能才能使应用程序正常启动。
application.ex包含如下所示的回调模块:
defmodule Sample.Application do
use Application
def start(_type, _args) do
children = [
]
opts = [strategy: :one_for_one, name: Sample.Supervisor]
Supervisor.start_link(children, opts)
end
end
start/2
函数必须返回{:ok, pid}
(具有可选状态作为第三项)或{:error, reason}
。
值得一提的另一件事是,应用程序实际上根本不需要回调模块。 这意味着mix.exs文件中的应用程序功能可能会变得非常简单:
def application do
[]
end
这样的应用程序称为库应用程序 。 它们没有任何监督树,但仍可以被其他应用程序用作依赖项。 库应用程序的一个示例是Poison,我们在上一节中将其指定为依赖项。
开始申请
启动应用程序的最简单方法是运行以下命令:
iex -S mix
您将看到类似于以下内容的输出:
Compiling 2 files (.ex)
Generated sample app
_build目录将在示例文件夹内创建。 它将包含.beam文件以及一些其他文件和文件夹。
如果您不想启动Elixir Shell,则可以运行另一个选项:
mix run
但是,问题在于start
功能完成工作后,应用程序将立即停止。 因此,您可以提供--no-halt
键,以保持应用程序在需要的时间内运行:
mix run --no-halt
使用elixir
命令可以实现相同的目的:
elixir -S mix run --no-halt
但是请注意,一旦关闭执行此命令的终端,应用程序将立即停止。 通过以分离模式启动应用程序可以避免这种情况:
elixir -S mix run --no-halt --detached
应用环境
有时,您可能希望应用程序的用户在实际启动应用程序之前设置一些参数。 例如,当用户应该能够控制Web服务器应监听的端口时,此功能很有用。 可以在作为简单的内存键值存储的应用程序环境中指定此类参数。
为了读取某些参数,请使用接受应用程序和密钥的fetch_env/2
函数 :
Application.fetch_env(:sample, :some_key)
如果找不到密钥,则返回:error
原子。 还有一个fetch_env!/2
函数会引发错误,而get_env/3
可能会提供一个默认值。
要存储参数,请使用put_env/4
:
Application.put_env(:sample, :key, :value)
第四个值包含选项,不需要设置。
最后,要删除密钥,请使用delete_env/3
函数 :
Application.delete_env(:sample, :key)
启动应用程序时,我们如何为环境提供价值? 好了,使用--erl
键可以通过以下方式设置此类参数:
iex --erl "-sample key value" -S mix
然后,您可以轻松获取值:
Application.get_env :sample, :key # => :value
如果用户在启动应用程序时忘记指定参数怎么办? 好吧,最有可能我们需要为此类情况提供默认值。 您可以在两个地方执行此操作:在config.exs内部或mix.exs文件内部。
第一个选项是首选选项,因为config.exs是实际上旨在存储各种配置选项的文件。 如果您的应用程序具有很多环境参数,则绝对应该坚持使用config.exs :
use Mix.Config
config :sample, key: :value
但是,对于较小的应用程序,可以通过调整应用程序功能在mix.exs内部提供环境值:
def application do
[
extra_applications: [:logger],
mod: {Sample.Application, []},
env: [ # <====
key: :value
]
]
end
示例:创建基于Web的CalcServer
好的,为了查看应用程序的运行情况,让我们修改已经在我的GenServer和Supervisors文章中讨论过的示例。 这是一个简单的计算器,允许用户执行各种数学运算并非常轻松地获取结果。
我要做的是使该计算器基于网络,以便我们可以发送POST请求以执行计算,并发送GET请求以获取结果。
使用以下内容创建一个新的lib / calc_server.ex文件:
defmodule Sample.CalcServer do
use GenServer
def start_link(initial_value) do
GenServer.start_link(__MODULE__, initial_value, name: __MODULE__)
end
def init(initial_value) when is_number(initial_value) do
{:ok, initial_value}
end
def init(_) do
{:stop, "The value must be an integer!"}
end
def add(number) do
GenServer.cast(__MODULE__, {:add, number})
end
def result do
GenServer.call(__MODULE__, :result)
end
def handle_call(:result, _, state) do
{:reply, state, state}
end
def handle_cast(operation, state) do
case operation do
{:add, number} -> {:noreply, state + number}
_ -> {:stop, "Not implemented", state}
end
end
def terminate(_reason, _state) do
IO.puts "The server terminated"
end
end
我们将仅添加对add
操作的支持。 其他所有数学运算都可以用相同的方式进行介绍,因此,为了使代码更紧凑,这里不再列出它们。
CalcServer
利用GenServer
,因此我们会自动获取child_spec
并可以从如下的回调函数启动它:
def start(_type, _args) do
children = [
{Sample.CalcServer, 0}
]
opts = [strategy: :one_for_one, name: Sample.Supervisor]
Supervisor.start_link(children, opts)
end
0
是初始结果。 它必须是一个数字,否则CalcServer
将立即终止。
现在的问题是我们如何添加网络支持? 为此,我们需要两个第三方依赖项: Plug (将充当抽象库)和Cowboy (将充当实际的Web服务器)。 当然,我们需要在mix.exs文件中指定这些依赖项 :
defp deps do
[
{:cowboy, "~> 1.1"},
{:plug, "~> 1.4"}
]
end
现在,我们可以在自己的监视树下启动Plug应用程序。 像这样调整启动功能:
def start(_type, _args) do
children = [
Plug.Adapters.Cowboy.child_spec(
:http, Sample.Router, [], [port: Application.fetch_env!(:sample, :port)]
),
{Sample.CalcServer, 0}
]
# ...
end
在这里,我们提供child_spec
并设置Sample.Router
来响应请求。 稍后将创建此模块。 但是,我不喜欢这种设置的原因是端口号是硬编码的,这并不方便。 在启动应用程序时,我可能想对其进行调整,因此我们将其存储在环境中:
Plug.Adapters.Cowboy.child_spec(
:http, Sample.Router, [], [port: Application.fetch_env!(:sample, :port)]
)
现在在config.exs文件中提供默认端口值:
config :sample, port: 8088
大!
路由器呢? 使用以下内容创建一个新的lib / router.ex文件:
defmodule Sample.Router do
use Plug.Router
plug :match
plug :dispatch
end
现在,我们需要定义一些路由来执行加法并获取结果:
get "/result" do
conn |> ok(to_string(Sample.CalcServer.result))
end
post "/add" do
fetch_number(conn) |> Sample.CalcServer.add
conn |> ok
end
我们正在使用get
和post
宏来定义/result
和/add
路由。 这些宏将为我们设置conn
对象。
ok
和fetch_number
是按以下方式定义的私有函数:
defp fetch_number(conn) do
Plug.Conn.fetch_query_params(conn).params["number"] |>
String.to_integer
end
defp ok(conn, data \\ "OK") do
send_resp conn, 200, data
end
fetch_query_params/2
返回具有所有查询参数的对象。 我们只对用户发送给我们的号码感兴趣。 最初所有参数都是字符串,因此我们需要将其转换为整数。
send_resp/3
使用提供的状态代码和正文向客户端发送响应。 我们不会在此处执行任何错误检查,因此代码始终为200
,这意味着一切正常。
而且,就是这样! 现在,您可以通过上面列出的任何一种方式(例如,通过键入iex -S mix
)启动应用程序,并使用curl
工具执行请求:
curl http://localhost:8088/result
# => 0
curl http://localhost:8088/add?number=1 -X POST
# => OK
curl http://localhost:8088/result
# => 1
结论
在本文中,我们讨论了Elixir应用程序及其用途。 您已经了解了如何创建应用程序,提供各种类型的信息以及在mix.exs文件中列出依赖项 。 您还了解了如何在应用程序环境中存储配置,并学习了几种启动应用程序的方法。 最后,我们看到了实际的应用程序,并创建了一个简单的基于Web的计算器。
不要忘记hex.pm网站列出了数百个准备在您的项目中使用的第三方应用程序,因此请确保浏览目录并选择适合您的解决方案!
希望您发现本文有用且有趣。 感谢您与我在一起直到下一次。
翻译自: https://code.tutsplus.com/articles/elixir-applications--cms-29598