認識 Phoenix:Elixir 上現代 Web 應用程序的類 Rails 框架
已發表: 2022-03-11Phoenix 框架一直在快速普及,它提供了 Ruby on Rails 等框架的生產力,同時也是可用的最快的框架之一。 它打破了必須犧牲性能才能提高生產力的神話。
那麼鳳凰到底是什麼?
Phoenix 是一個使用 Elixir 編程語言構建的 Web 框架。 Elixir 建立在 Erlang VM 之上,用於構建低延遲、容錯、分佈式系統,這些是現代 Web 應用程序日益必要的品質。 您可以從這篇博文或他們的官方指南中了解更多關於 Elixir 的信息。
如果您是一名 Ruby on Rails 開發人員,那麼您絕對應該對 Phoenix 感興趣,因為它所承諾的性能提升。 其他框架的開發人員也可以跟隨,了解 Phoenix 如何處理 Web 開發。
在本文中,我們將學習 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.exs
或config/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 導入了哪些模塊。 您可以在此處刪除或添加您想要的任何其他模塊,它們將被導入所有模型。
還有類似的功能,例如“視圖”和“控制器”,它們分別為視圖和控制器服務於相同的目的。
quote
和use
關鍵字可能看起來令人困惑。 對於此示例,您可以將引用視為在調用該函數的模塊的上下文中直接運行該代碼。 所以這相當於在模塊中的 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 讓你兩者兼得。