Elixir应用简介

在我以前的文章中,我们讨论了各种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

再次行为

应用程序是行为,就像我们在前面的文章中讨论过的GenServerSupervisor一样。 如前所述,我们通过以下方式在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

好的,为了查看应用程序的运行情况,让我们修改已经在我的GenServerSupervisors文章中讨论过的示例。 这是一个简单的计算器,允许用户执行各种数学运算并非常轻松地获取结果。

我要做的是使该计算器基于网络,以便我们可以发送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

我们正在使用getpost宏来定义/result/add路由。 这些宏将为我们设置conn对象。

okfetch_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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值