百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Elixir实战:11 组件工作 (1) OTP 应用程序

zhezhongyun 2025-01-05 21:29 37 浏览

本章涵盖

  • 创建 OTP 应用程序
  • 处理依赖关系
  • 构建一个网络服务器
  • 配置应用程序

是时候将我们的注意力转向生产可发布的系统,这些系统可以被部署。为了实现这个目标,您需要了解 OTP 应用程序,它们可以让您将系统组织成可重用的组件。应用程序是构建 Elixir 或 Erlang 生产系统和库的标准方式。依赖它们带来了各种好处,例如依赖管理;简化系统启动;以及构建独立的、可部署的发布版本的能力。

在本章中,您将学习如何创建应用程序并处理依赖关系。在此过程中,您将把待办事项系统转变为一个正式的 OTP 应用程序,并使用来自 Erlang 和 Elixir 生态系统的一些第三方库,为您现有的系统暴露一个 HTTP 接口。前方还有很多工作,所以让我们开始进行 OTP 应用程序的创建。

11.1 OTP 应用程序

OTP 应用程序是由多个模块组成的组件,并且可以依赖其他应用程序。这使得通过单个函数调用启动整个系统成为可能。正如你将要看到的,将代码转换为应用程序是相当简单的。你当前版本的待办事项系统已经是一个 OTP 应用程序,但还有一些小细节可以改进。你很快就会看到这一点;首先,让我们看看 OTP 应用程序由什么组成。

11.1.1 使用混合工具创建应用程序

应用程序是一个特定于 OTP 的构造。定义应用程序的资源称为应用程序资源文件——一个用 Erlang 术语编写的纯文本文件,描述了该应用程序。(不用担心;您不需要直接编写这个。您将依赖 mix 工具为您完成这项工作)。该文件包含几条信息,例如:

  • 应用名称和版本,以及描述
  • 应用模块列表
  • 应用依赖项列表(这些必须是应用程序本身)
  • 可选的应用回调模块

依赖于 mix 工具简化和自动化了生成应用程序资源文件的部分工作。例如,应用程序资源文件必须包含所有应用程序模块的列表。当您使用 mix 时,此列表会根据源代码中定义的模块自动为您生成。

某些信息,例如应用名称、版本和描述,当然必须由您提供。然后, mix 工具可以使用这些数据和您的源代码在编译项目时生成相应的资源文件。

让我们看看这个在实践中的应用。去一个临时文件夹,运行 mix new hello_world --sup 。这个命令创建了一个 hello_world 文件夹,里面有最小的 Mix 项目骨架。参数 --sup 使得 mix 工具生成应用回调模块,并从中启动空的(无子进程的)监督者。

您现在可以切换到 hello_world 文件夹,并使用熟悉的 iex -S mix 启动系统。表面上看,似乎没有什么特别的事情发生。但是 mix 会自动启动您的应用程序,您可以通过调用 Application.started_applications/0 来验证:

iex(1)> Application.started_applications()
[
  {:iex, ~c"iex", ~c"1.15.0"},
  {:hello_world, ~c"hello_world", ~c"0.1.0"},    ?
  {:logger, ~c"logger", ~c"1.15.0"},
  {:mix, ~c"mix", ~c"1.15.0"},
  {:elixir, ~c"elixir", ~c"1.15.0"},
  {:compiler, ~c"ERTS  CXC 138 10", ~c"8.3"},
  {:stdlib, ~c"ERTS  CXC 138 10", ~c"5.0"},
  {:kernel, ~c"ERTS  CXC 138 10", ~c"9.0"}
]

应用程序正在运行。

如您所见, hello_world 应用程序正在运行,同时还有一些其他应用程序,例如 Elixir 的 mix 、 iex 和 elixir ,以及 Erlang 的 stdlib 和 kernel 。

您很快就会看到这的好处,但首先,让我们看看应用程序是如何描述的。您指定应用程序的主要位置是在 mix.exs 文件中。以下是生成文件的完整内容(注释已被删除):

defmodule HelloWorld.MixProject do
  use Mix.Project
 
  def project do                              ?
    [                                         ?
      app: :hello_world,                      ?
      version: "0.1.0",                       ?
      elixir: "~> 1.15",                      ?
      start_permanent: Mix.env() == :prod,    ?
      deps: deps()                            ?
    ]                                         ?
  end                                         ?
 
  def application do                          ?
    [                                         ?
      extra_applications: [:logger],          ?
      mod: {HelloWorld.Application, []}       ?
    ]                                         ?
  end                                         ?
 
  defp deps do                                ?
    []                                        ?
  end                                         ?
end

? 描述项目

? 描述应用程序

? 列出依赖项

第一个有趣的事情发生在 project/0 函数中,在这里你描述了 Mix 项目。 app: :hello_world 为你的应用程序命名。应用程序名称只能是一个原子,你可以使用这个原子在运行时启动和停止应用程序。

该应用程序在 application/0 函数中描述。在这里,您指定一些选项,这些选项最终将进入应用程序资源文件。在这种情况下,描述包括您依赖的其他 Erlang 和 Elixir 应用程序的列表,以及将用于启动应用程序的模块。默认情况下,Elixir 的 :logger 应用程序被列出(https://hexdocs.pm/logger/Logger.xhtml)。

最后, deps 函数返回第三方依赖项的列表——您希望在项目中使用的其他库。默认情况下,此列表为空。您将在本章稍后看到如何使用依赖项。

11.1.2 应用程序行为

应用程序描述的关键部分是 mod: {HelloWorld.Application, []} ,由 application/0 在 mix.exs 中提供。该部分指定将用于启动应用程序的模块。当应用程序启动时,将调用函数 HelloWorld.Application.start/2 。

显然,您需要实现 HelloWorld.Application 模块。这是由 mix 工具为您完成的,所以让我们看看它的样子:

defmodule HelloWorld.Application do
  use Application                                                  ?
 
  def start(_type, _args) do                                       ?
    children = []                                                  ?
    opts = [strategy: :one_for_one, name: HelloWorld.Supervisor]   ?
    Supervisor.start_link(children, opts)                          ?
  end
end

? 使用应用程序模块

? 回调函数

? 启动顶级监督者

应用程序是一个由 Application 模块(https://hexdocs.pm/elixir/Application.xhtml)驱动的 OTP 行为,它是 Erlang 的 :application 模块(https://www.erlang.org/doc/man/application.xhtml)的一层封装。要能够与 Application 一起工作,您必须实现自己的回调模块并定义一些回调函数。

至少,您的回调模块必须包含 start/2 函数。传递的参数是应用程序启动类型(您通常会忽略)和一个任意参数(在 mix.exs 中的 mod 键下指定的术语)。有关详细信息,请参阅官方文档 (https://hexdocs.pm/elixir/Application.xhtml#c:start/2)。

start/2 回调的任务是启动您系统的顶级进程,这通常应该是一个监督者。如果出现问题,函数将以 {:ok, pid} 或 {:error, reason} 的形式返回其结果。

11.1.3 启动应用程序

要在运行的 BEAM 实例中启动应用程序,可以调用 Application .start/1 。此函数首先查找应用程序资源文件(由 mix 生成)并解释其内容。然后,它验证您所依赖的所有应用程序是否已启动。最后,通过调用回调模块的 start/2 函数来启动应用程序。 Application.ensure_all_started/2 函数也可用,它递归启动所有尚未启动的依赖项。

通常,您不需要调用这些函数,因为 mix 会自动启动项目实现的应用程序。调用 iex -S mix 会自动启动应用程序及其依赖项。

请注意,您无法启动单个应用程序的多个实例。尝试启动已在运行的应用程序将返回错误:

$ iex -S mix
 
iex(1)> Application.start(:hello_world)
{:error, {:already_started, :hello_world}}
 

您可以使用 Application.stop/1 停止应用程序:

iex(2)> Application.stop(:hello_world)
[info] Application hello_world exited: :stopped

停止应用程序会终止其顶级进程。如果该进程是一个监督者,它将在停止自身之前停止其子进程。这就是为什么在监督树中组织进程很重要,如第 9 章所述。这样可以确保应用程序以受控的方式停止,避免留下悬挂的进程。

Application.stop/1 仅停止指定的应用程序,保留依赖项(其他应用程序)运行。要以受控方式停止整个系统,您可以调用 System.stop/0 。此功能将关闭所有 OTP 应用程序,然后关闭 BEAM 实例。 Application.stop/1 和 System.stop/0 都以礼貌的方式工作。监督树中的每个进程都可以在其 terminate/2 回调中执行一些最终清理,如第 9.1.6 节所述。

11.1.4 库应用程序

您不需要在 mix.exs 中提供 application/0 函数的 mod: ... 选项:

defmodule HelloWorld.Application do
  ...
 
  def application do
    []
  end
 
  ...
end

在这种情况下,没有应用回调模块,这反过来意味着没有顶级进程可以启动。这仍然是一个合适的 OTP 应用程序。您甚至可以启动和停止它。

这样的应用程序有什么目的?该技术用于库应用程序——不需要创建自己监督树的组件。顾名思义,这些通常是库,例如 JSON 或 CSV 解析器。Erlang 自己的 STDLIB 应用程序(https://erlang.org/doc/apps/stdlib/index.xhtml)是一个纯库应用程序,因为它暴露了各种实用模块,但不需要管理自己的监督树。

库应用程序很有用,因为您可以将它们列为运行时依赖项。这在您开始组装可部署版本时起着重要作用,正如您将在第 13 章中看到的。

11.1.5 实现应用程序回调

凭借这些知识,您可以将您的待办事项系统转变为一个正式的应用程序。如前所述,它已经是一个 OTP 应用程序,尽管是一个库,它并没有为应用程序行为实现回调模块。实际上,Mix 项目与 OTP 应用程序之间通常存在 1:1 的关系。一个 Mix 项目实现恰好一个 OTP 应用程序。一个例外是所谓的伞形项目,它可以包含多个 OTP 应用程序。

考虑到待办系统在一个监督树下运行一组自己的进程,实现应用程序回调模块是有意义的。一旦您这样做,系统可以在您运行 iex -S mix 时自动启动。

您需要做的第一件事是编辑 mix.exs 文件。回想一下,您在第 7 章中使用 mix 工具创建了初始项目。因此,您已经有了这个文件,只需添加一些信息即可。mix.exs 的完整代码在以下列表中提供。

清单 11.1 指定应用程序参数 (todo_app/mix.exs)

defmodule Todo.MixProject do
  use Mix.Project
 
  def project do
    [
      app: :todo,
      version: "0.1.0",
      elixir: "~> 1.15",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end
 
  def application do
    [
      extra_applications: [:logger],
      mod: {Todo.Application, []}      ?
    ]
  end
 
  defp deps do
    []
  end
end

? 指定应用回调模块

对 mix.exs 的唯一更改是在 application/0 函数中,指定了回调模块。

接下来,您需要实现回调模块。代码如下。

清单 11.2 实现应用模块 (todo_app/lib/todo/application.ex)

defmodule Todo.Application do
  use Application
 
  def start(_, _) do
    Todo.System.start_link()
  end
end

如前所述,启动应用程序就像启动顶级监督者一样简单。鉴于您已经将系统结构设置为位于 Todo.System 监督者之下,这就是将您的系统转变为完整 OTP 应用程序所需的一切。

让我们看看它的实际效果:

$ iex -S mix
 
Starting database server            ?
Starting database worker            ?
Starting database worker            ?
Starting database worker            ?
Starting to-do cache                ?

系统自动启动。

通过实现 OTP 应用程序回调,您已使系统能够自动启动。

值得注意的是,在运行测试时您也会获得相同的好处。当您调用 mix test 时,系统中的所有基本进程都会启动。在第 7 章中,当您添加了一些测试时,您必须手动启动缓存进程:

defmodule Todo.CacheTest do
  use ExUnit.Case
 
  test "server_process" do
    {:ok, cache} = Todo.Cache.start()                   ?
    bob_pid = Todo.Cache.server_process(cache, "bob")
 
    assert bob_pid != Todo.Cache.server_process("alice")
    assert bob_pid == Todo.Cache.server_process("bob")
  end
 
  ...
end

手动启动缓存

代码自那时起经历了许多变革,但在每个版本中,都有前述模式的某种变体。在 Todo.CacheTest 内,您需要手动启动支持进程,例如缓存。随着系统转变为一个真正的 OTP 应用,这种情况不再存在,如下列表所示。

清单 11.3 测试 server_process (todo_app/test/todo_cache_test.exs)

defmodule Todo.CacheTest do
  use ExUnit.Case
 
  test "server_process" do
    bob_pid = Todo.Cache.server_process("bob")      ?
 
    assert bob_pid != Todo.Cache.server_process("alice")
    assert bob_pid == Todo.Cache.server_process("bob")
  end
 
  ...
end

? 无需手动启动缓存即可工作

11.1.6 应用程序文件夹结构

让我们简要讨论一下您编译的应用程序的文件夹结构。由于 mix 工具,您通常不需要担心这个,但有时了解编译系统的文件夹结构是有用的。

混合环境

在查看应用程序结构之前,您应该了解一些关于 Mix 环境的知识。Mix 环境是一种编译时选项,可以用来影响编译代码的形状。

混合项目使用三个环境:开发(dev)、测试(test)和生产(prod)。这三个环境在编译代码时会产生细微的差异。例如,在为开发环境编译的版本中(dev 环境),您可能希望运行一些额外的调试日志,而在为生产环境编译的版本中(prod 环境),您不希望包含这样的日志。在为测试环境编译的版本中(test 环境),您希望进一步减少日志量,并使用不同的数据库,以防止测试污染您的开发数据库。

您可以根据需要引入自己的 Mix 环境,但这通常不必要,甚至可以说从未需要。这里提到的三种环境应该足以涵盖所有可能的场景。

对于大多数 mix 任务,默认环境是 dev,表示您正在处理开发。这个规则的一个例外是测试任务。当您调用 mix test 时,Mix 环境会自动设置为 test 。

您可以通过设置 MIX_ENV 操作系统环境变量来指定 Mix 环境。根据惯例,在为生产环境构建时,您应该使用 prod 环境。要为 prod 编译代码,您可以调用 MIX_ENV=prod mix compile 。要编译并启动 prod 版本,您可以调用 MIX_ENV=prod iex -S mix 。

生产环境常常是一个令人困惑的来源。许多团队将生产环境与生产机器混为一谈。生产版本确实运行在生产机器上,但它也会在其他机器上运行,例如预发布环境。最重要的是,应该能够并且简单地在本地开发机器上启动为生产编译的版本。你不需要频繁这样做,但偶尔分析与已部署的生产版本尽可能接近的系统行为是有用的。这正是生产编译版本的意义。因此,建议支持在本地运行生产编译版本。从项目开始时就致力于这个目标,因为在代码库显著增长后再去做通常会困难得多。

编译代码结构

一旦您编译项目,编译后的二进制文件将位于_build/project_env 文件夹中,其中 project_env 是编译期间生效的 Mix 环境。

因为 dev 是默认环境,如果你运行 mix compile 或 iex -S mix ,你会在 _build/dev 文件夹中得到二进制文件。OTP 本身推荐以下文件夹约定:

lib/
  App1/
    ebin/
    priv/
 
  App2/
    ebin/
    priv/
  ...

在这里, App1 和 App2 代表应用程序名称(例如 todo )。ebin 文件夹包含编译后的二进制文件(.beam 文件和应用程序资源文件),而 priv 文件夹包含特定于应用程序的私有文件(图像、编译的 C 二进制文件等)。这不是一个强制性的结构,但它是大多数 Elixir 和 Erlang 项目中使用的约定。一些工具可能依赖于此结构,因此最好遵循这一约定。

幸运的是,您不需要自己维护这个结构,因为 mix 会自动完成。编译后的 Mix 项目的最终文件夹结构如下:

YourProjectFolder
_build
  dev
    lib
      App1
        ebin
        priv
 
      App2
      ...

除了您的应用程序,lib 文件夹还包含应用程序依赖项,标准应用程序除外,这些标准应用程序包含在 Elixir 和 Erlang 中,位于 Elixir 和 Erlang 安装的文件夹中,并可以通过加载路径访问。

如前所述,应用程序资源文件位于 lib/YourApp/ebin,文件名为 YourApp.app。对于待办事项系统,该文件位于 _build/dev/lib/todo/ebin/(相对于根项目文件夹)。当您尝试启动应用程序时,通用应用程序行为会在加载路径中查找资源文件(与搜索编译二进制文件的路径相同)。

这结束了我们对应用基础的讨论。现在你已经掌握了一些理论,让我们来看看如何处理依赖关系。

可部署系统

应用程序在构建可部署系统中发挥着重要作用。您将在第 13 章中了解这一点,我们将讨论 OTP 发布。简而言之,基本思想是组装一个最小的自包含系统,仅包含所需的应用程序和 Erlang 运行时。为了实现这一点,您必须将代码转换为 OTP 应用程序,因为只有这样您才能指定对其他应用程序的依赖关系。这最终用于将所有所需的应用程序打包成一个单一的可部署发布。

因此,Elixir 和 Erlang 中的任何可重用内容都应该位于一个应用程序中。这对于 Elixir 和 Erlang 也是如此:例如 elixir 应用程序,它将 Elixir 标准库与 iex 和 mix 打包在一起,这些都是作为单独的应用程序实现的。在 Erlang 中也发生了同样的事情,它被划分为许多应用程序(https://www.erlang.org/doc/applications.xhtml),例如 kernel 和 stdlib 。

相关推荐

前端面试:聊聊 meta 标签?(meta标签用法)

提供给页面的一些元信息(名称/值对),有助于SEO。Meta标签是HTML中用于定义文档类型声明的标签。它们通常被用在head标签中,与文档的body标签相关联。在一个有head...

web网页性能分析系列(网页性能指标有哪些)

在前端开发中,App或者WebPage性能的好坏和响应速度,尤其是App端显得格外重要,一直都是前端很头疼的问题。专业的测试工具可以知道自己的网页还有哪些需要优化的地方,总体的评分是多少,是否合乎用...

[抓狂瞬间] 5 大差异 + 布局绝招!前端元素面试通关秘籍

刚入行的搬砖工程师,面对面试必问题“行内元素和块级元素有啥区别”,是不是心跳加速,大脑却一片空白?别担心!这看似基础的问题,实则是面试逆袭的突破口。今天就带你深入剖析,用5个关键差异,解锁前端布...

python中Django视图(view)的详解(附示例)

本篇文章给大家带来的内容是关于python中Django视图(view)的详解(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。一个视图函数(类),简称视图,是一个简单的Pyt...

MySQL进行整库数据备份「表(结构+数据)、视图、函数、事件」

  前言  通常情况下,我们需要改什么地方就备份什么地方就可以了,但也免不了需要整库备份的时候,本文记录实现MySQL使用脚本进行整库数据备份【表(结构+数据)、视图、函数、事件】  主要是使用mys...

python入门-day14-周末小项目(python周末培训班哪个好)

周末小项目-简易记事本的内容。这是一个综合练习,结合之前学过的函数、文件操作和异常处理,设计一个简单的命令行记事本程序。我会用清晰的步骤和代码带你实现添加、查看、删除笔记并保存到文件的功能,确保...

Python必会的50个代码操作(python代码介绍)

学习Python时,掌握一些常用的程序操作非常重要。以下是50个Python必会的程序操作,主要包括基础语法、数据结构、函数和文件操作等。1.HelloWorldprint("Hello,...

ScalersTalk成长会Python小组第7周学习笔记

Scalers点评:在2015年,ScalersTalk成长会完成Python小组完成了《Python核心编程》第1轮的学习。到2016年,我们开始第二轮的学习,并且将重点放在章节的习题上。Pytho...

电脑CMD命令与电脑工作效率提升(cmd使用提升命令)

在日常使用电脑同时按“windows+R”,可以弹出来一个框然后输入CMD弹出以下框:在这个框中输入一些内容可以简化一些我们的电脑操作好的,以下是一些最常用的CMD命令,按功能分类整理,适合日常使用和...

多线程实现消息推送并可重试3次以及1小时后重试

#-*-coding:utf-8-*-"""CreatedonTueApr2209:05:462025@author:1""&#...

RBAC权限模型(rbac权限模型的优点)

RBAC权限模型RBAC权限模型(Role-BasedAccessControl)即:基于角色的权限控制。这是目前最常被开发者使用也是相对易用、通用权限模型。准备工作CREATETABLE`s...

如何使用PIL生成验证码?(pixivic验证码)

web项目中遇到使用验证码的情况有很多,进行介绍下使用PIL生成验证码的方法。安装开始安装PIL的过程确实麻烦各种问题层出不绝,不过不断深入后就没有这方面的困扰了:windows安装:直接安装Pil...

技术是这样应用的(一)(技术的运用)

WindowsServer2003路由与远程访问在计算机网络教室使用过程中网络访问控制的实现目前很多计算机网络教室采用双网卡服务器的路由和远程访问功能,通过NAT地址转换实现教室内学生机的互联网访...

Flask-RESTful 用法指南(flask写restful接口)

Flask-RESTful是一个Flask扩展,用于快速构建RESTfulAPI。它提供了简单的语法来创建资源路由,并内置了请求解析和响应格式化功能。##安装首先安装Flask-REST...

Python办公自动化系列篇之三:PowerPoint演示文稿(.pptx)

作为高效办公自动化领域的主流编程语言,Python凭借其优雅的语法结构、完善的技术生态及成熟的第三方工具库集合,已成为企业数字化转型过程中提升运营效率的理想选择。该语言在结构化数据处理、自动化文档生成...