认识 Phoenix:Elixir 上现代 Web 应用程序的类 Rails 框架

已发表: 2022-03-11

Phoenix 框架一直在快速普及,它提供了 Ruby on Rails 等框架的生产力,同时也是可用的最快的框架之一。 它打破了必须牺牲性能才能提高生产力的神话。

那么凤凰到底是什么?

Phoenix 是一个使用 Elixir 编程语言构建的 Web 框架。 Elixir 建立在 Erlang VM 之上,用于构建低延迟、容错、分布式系统,这些是现代 Web 应用程序日益必要的品质。 您可以从这篇博文或他们的官方指南中了解更多关于 Elixir 的信息。

如果您是一名 Ruby on Rails 开发人员,那么您绝对应该对 Phoenix 感兴趣,因为它所承诺的性能提升。 其他框架的开发人员也可以跟随,了解 Phoenix 如何处理 Web 开发。

在 Elixir 上认识 Phoenix:现代 Web 应用程序的类 Rails 框架

在本文中,我们将学习 Phoenix 中的一些内容,如果您来自 Ruby on Rails 世界,您应该记住这些内容。

与 Ruby 不同,Elixir 是一种函数式编程语言,这可能是您可能必须处理的最大差异。 但是,任何学习 Phoenix 的人都应该注意这两个平台之间的一些关键差异,以最大限度地提高他们的生产力。

命名约定更简单。

这是一个小问题,但如果您来自 Ruby on Rails,很容易搞砸。

在 Phoenix 中,惯例是以单数形式编写所有内容。 因此,您将拥有一个“UserController”,而不是像在 Ruby on Rails 中那样的“UsersController”。 这适用于任何地方,除了命名数据库表时,惯例是以复数形式命名表。

在学习了在 Ruby on Rails 中何时何地使用复数形式之后,这似乎没什么大不了的,但起初我有点困惑,当我学习使用 Ruby on Rails 时,我相信它让很多人感到困惑其他人也一样。 Phoenix 让您少担心一件事。

路由更易于管理。

Phoenix 和 Ruby on Rails 在路由方面非常相似。 关键区别在于您如何控制请求的处理方式。

在 Ruby on Rails(和其他 Rack 应用程序)中,这是通过中间件完成的,而在 Phoenix 中,这是通过所谓的“插件”完成的。

插头是您用来处理连接的东西。

例如,中间件Rails::Rack::Logger在 Rails 中记录一个请求,在 Phoenix 中这将通过Plug.Logger插件完成。 基本上你可以在 Rack 中间件中做的任何事情都可以使用插件来完成。

以下是 Phoenix 中的路由器示例:

 defmodule HelloWorld.Router do use HelloWorld.Web, :router pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers end pipeline :api do plug :accepts, ["json"] end scope "/", HelloWorld do pipe_through :browser get "/", HelloController, :index end scope "/api", HelloWorld do pipe_through :api end end

首先,让我们看一下管道。

这些是请求将通过的插头组。 把它想象成一个中间件堆栈。 例如,这些可用于验证请求是否需要 HTML、获取会话并确保请求是安全的。 这一切都发生在到达控制器之前。

因为您可以指定多个管道,所以您可以挑选特定路线所需的插头。 例如,在我们的路由器中,我们为页面 (HTML) 和 API (JSON) 请求提供了不同的管道。

对不同类型的请求使用完全相同的管道并不总是有意义的。 Phoenix 为我们提供了这种灵活性。

视图和模板是两个不同的东西。

Phoenix 中的视图与 Ruby on Rails 中的视图不同。

Phoenix 中的视图负责渲染模板并提供使原始数据更易于模板使用的功能。 Phoenix 中的视图最类似于 Ruby on Rails 中的帮助程序,但增加了渲染模板。

让我们编写一个显示“Hello, World!”的示例在浏览器中。

Ruby on Rails:

应用程序/控制器/hello_controller.rb:

 class HelloController < ApplicationController def index end end

应用程序/视图/hello/index.html.erb:

 <h1>Hello, World!</h1>

凤凰:

网络/控制器/hello_controller.ex:

 defmodule HelloWorld.HelloController do use HelloWorld.Web, :controller def index(conn, _params) do render conn, "index.html" end end

网络/视图/hello_view.ex:

 defmodule HelloWorld.HelloView do use HelloWorld.Web, :view end

网页/模板/hello/index.html.eex:

 <h1>Hello, World!</h1>

这些示例都显示“Hello, World!” 在浏览器中,但有一些关键的区别。

首先,您必须明确说明您希望在 Phoenix 中呈现的模板,这与 Ruby on Rails 不同。

接下来,我们必须在 Phoenix 中包含一个位于控制器和模板之间的视图。

现在,您可能想知道为什么我们需要一个空视图? 这里的关键是,在后台,Phoenix 将模板编译成一个大致等于以下代码的函数:

 defmodule HelloWorld.HelloView do use HelloWorld.Web, :view def render("index.html", _assigns) do raw("<h1>Hello, World!</h1>") end end

您可以删除模板,并使用新的渲染功能,您将得到相同的结果。 当您只想返回文本甚至 JSON 时,这很有用。

模型就是这样:数据模型。

在 Phoenix 中,模型主要处理数据验证、它的模式、它与其他模型的关系以及它的呈现方式。

在模型中指定模式起初可能听起来很奇怪,但它允许您轻松创建“虚拟”字段,这些字段不会持久化到数据库中。 我们来看一个例子:

 defmodule HelloPhoenix.User do use HelloPhoenix.Web, :model schema "users" do field :name, :string field :email, :string field :password, :string, virtual: true field :password_hash, :string end end

这里我们在“users”表中定义了四个字段:name、email、password和password_hash。

这里没什么有趣的,除了设置为“虚拟”的“密码”字段。

这允许我们设置和获取此字段,而无需将更改保存到数据库中。 这很有用,因为我们有将密码转换为哈希的逻辑,我们会将其保存在“password_hash”字段中,然后将其保存到数据库中。

您仍然需要创建迁移; 模型中的模式是必需的,因为字段不会像 Ruby on Rails 那样自动加载到模型中。

Phoenix 和 Ruby on Rails 之间的区别在于模型不处理数据库的持久性。 这由一个名为“Repo”的模块处理,该模块配置了数据库信息,如下例所示:

 config :my_app, Repo, adapter: Ecto.Adapters.Postgres, database: "ecto_simple", username: "postgres", password: "postgres", hostname: "localhost"

此代码包含在特定于环境的配置文件中,例如config/dev.exsconfig/test.exs 。 这使我们可以使用 Repo 执行数据库操作,例如创建和更新。

 Repo.insert(%User{name: "John Smith", example: "[email protected]"}) do {:ok, user} -> # Insertion was successful {:error, changeset} -> # Insertion failed end

这是 Phoenix 控制器中的一个常见示例。

我们为用户提供姓名和电子邮件,并且 Repo 尝试在数据库中创建新记录。 然后,我们可以选择处理成功或失败的尝试,如示例所示。

为了更好地理解这段代码,你需要理解 Elixir 中的模式匹配。 函数返回的值是一个元组。 该函数返回一个由两个值组成的元组,一个状态,然后是模型或变更集。 变更集是一种跟踪变更和验证模型的方法(变更集将在下一节中讨论)。

第一个元组,从上到下,与试图将用户插入数据库的函数返回的元组的模式相匹配,将运行其定义的函数。

我们可以为状态设置一个变量而不是一个原子(这基本上是 Ruby 中的符号),但是我们将匹配成功和失败的尝试,并在两种情况下使用相同的函数。 指定我们想要匹配的原子,我们专门为该状态定义一个函数。

Ruby on Rails 通过 ActiveRecord 将持久性功能内置到模型中。 这给模型增加了更多的责任,有时会使模型的测试变得更加复杂。 在 Phoenix 中,这已经以一种有意义的方式分离,并防止使用持久性逻辑使每个模型膨胀。

变更集允许明确的验证和转换规则。

在 Ruby on Rails 中,验证和转换数据可能是难以发现错误的根源。 这是因为当数据被诸如“before_create”和验证之类的回调转换时并不是很明显。

在 Phoenix 中,您可以使用变更集显式地进行这些验证和转换。 这是我在 Phoenix 中最喜欢的功能之一。

让我们通过在之前的模型中添加一个变更集来看看:

 defmodule HelloPhoenix.User do use HelloPhoenix.Web, :model schema "users" do field :name, :string field :email, :string field :password, :string, virtual: true field :password_hash, :string end def changeset(struct, params \\ %{}) do struct |> cast(params, [:name, :email, :password]) |> validate_required([:email, :password]) end end

这里变更集做了两件事。

首先,它调用“cast”函数,这是一个允许字段的白名单,类似于 Ruby on Rails 中的“strong_parameters”,然后它验证“email”和“password”字段是否包含,使“name”字段可选的。 这样,用户只能修改您允许的字段。

这种方法的好处是我们不限于一个变更集。 我们可以创建多个变更集。 用于注册的变更集和用于更新用户的变更集很常见。 也许我们只想在注册时要求密码字段,而不是在更新用户时。

将此方法与 Ruby on Rails 中的常用方法进行比较,您必须指定验证仅应在“创建”时运行。 这有时会让 Rails 很难在你有一个复杂的模型后弄清楚你的代码在做什么。

导入功能既简单又灵活。

一个名为“Ecto”的库的大部分功能都被导入到模型中。 模型通常在顶部附近有这条线:

 use HelloPhoenix.Web, :model

“HelloPhoenix.Web”模块位于“web/web.ex”。 在模块中,应该有一个名为“model”的函数,如下所示:

 def model do quote do use Ecto.Schema import Ecto import Ecto.Changeset import Ecto.Query end end

在这里你可以看到我们从 Ecto 导入了哪些模块。 您可以在此处删除或添加您想要的任何其他模块,它们将被导入所有模型。

还有类似的功能,例如“视图”和“控制器”,它们分别为视图和控制器服务于相同的目的。

quoteuse关键字可能看起来令人困惑。 对于此示例,您可以将引用视为在调用该函数的模块的上下文中直接运行该代码。 所以这相当于在模块中的 quote 中编写了代码。

use 关键字还允许您在调用它的上下文中运行代码。 它本质上需要指定的模块,然后在调用它的上下文中调用运行它的模块上的__using__宏。 您可以在官方指南中阅读有关报价和使用的更多信息。

这确实可以帮助您了解某些功能在框架中的位置,并有助于减少框架在做很多“魔术”的感觉。

并发是核心。

并发在 Phoenix 中是免费的,因为它是 Elixir 的主要功能。 您将获得一个可以生成多个进程并在多个内核上运行的应用程序,而无需担心线程安全性和可靠性。

您可以简单地在 Elixir 中生成一个新进程:

 spawn fn -> 1 + 2 end

spawn之后和end之前的所有内容都将在一个新进程中运行。

实际上,Phoenix 中的每个请求都在自己的进程中处理。 Elixir 使用 Erlang VM 的强大功能“免费”提供可靠且高效的并发。

这也使 Phoenix 成为运行使用 WebSockets 的服务的绝佳选择,因为 WebSockets 需要在客户端和服务器之间保持开放连接(这意味着您需要构建应用程序以便它可以处理可能的数千个并发连接)。

这些要求会给基于 Ruby on Rails 的项目增加很多复杂性,但 Phoenix 可以通过 Elixir 免费满足这些要求。

如果您想在 Phoenix 应用程序中使用 WebSockets,您将需要使用 Channels。 它相当于 Ruby on Rails 中的ActionCable ,但设置起来不太复杂,因为您不需要运行单独的服务器。

Phoenix 让构建现代 Web 应用程序变得轻松。

虽然我们主要关注的是差异,但 Phoenix 确实与 Ruby on Rails 有一些共同点。

Phoenix 大致遵循与 Ruby on Rails 相同的 MVC 模式,因此既然您了解了主要区别,那么弄清楚哪些代码在哪里应该不难。 Phoenix 也有与 Ruby on Rails 类似的生成器,用于创建模型、控制器、迁移等。

学习 Elixir 后,一旦您对 Phoenix 更加熟悉,您将慢慢接近 Ruby on Rails 的生产力水平。

有几次我觉得效率不高是当我遇到一个由 Ruby 中的 gem 解决的问题时,我找不到类似的 Elixir 库。 幸运的是,这些空白正在被不断壮大的 Elixir 社区慢慢填补。

Phoenix 的不同之处有助于解决管理复杂的 Ruby on Rails 项目所带来的许多痛苦。 虽然它们不能解决所有问题,但它们确实有助于将您推向正确的方向。 选择一个缓慢的框架,因为你想要生产力不再是一个有效的借口,Phoenix 让你两者兼得。