作为一名踏上学习 Elixir 之旅的 .NET 开发者,第一个问题往往是:"如何构建 Web API?"在 Elixir 生态系统中,答案几乎总是 Phoenix 框架。如果你熟悉 ASP.NET Core,你会发现 Phoenix 是一个强大而优雅的对应框架。
在本文中,我将分解 Phoenix 的关键组件,并与 .NET 世界进行对比,以帮助构建我的理解框架。
概览
Phoenix 是 Elixir 语言的 Web 开发框架。它允许你构建现代 Web 应用程序,包括 Web API、Web Sockets(用于实时功能)以及传统的模型-视图-控制器 (MVC) 应用。
在微软生态系统中,它是 ASP.NET Core 的直接对应物。Phoenix 以高开发效率和卓越的应用性能而闻名。
Plug:HTTP 抽象层
正如 ASP.NET Core 建立在中间件概念之上,Phoenix 则建立在 Plug 之上。
Plug 既是 Web 应用程序之间可组合模块的规范,也是 Web 服务器(如 Cowboy 或 Bandit)的抽象层。核心概念是一个统一的 `连接`(%Plug.Conn{} 结构体,类似于 .NET 中的 HttpContext),它在通过一系列函数时被转换。
这比 ASP.NET Core 的中间件更加细粒度。单个 Plug 是一个接收连接并返回连接的函数,使它们具有高度的可组合性。
管道:组织中间件
在 Phoenix 中,你将 Plugs 分组为路由器中的"管道"。这是一种比在 Program.cs 文件中使用 app.UseMiddleware<T>() 调用更清晰、更声明式的方式来组织中间件。
在 Phoenix 中,我们显式创建管道。在请求到达控制器之前,你显式添加中间件(称为"plugs")---例如身份验证或验证。
Phoenix 示例:
ruby
defmodule HelloWeb.Router do
use HelloWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {HelloWeb.LayoutView, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug HelloWeb.Plugs.Locale, "en"
end
scope "/", HelloWeb do
pipe_through :browser
get "/", PageController, :index
end
在上面的示例中,我们创建了一个名为 :browser 的管道。在请求能够到达 PageController 之前,它必须通过这个管道。这比 .NET 更清晰,因为你可以在一个地方清楚地看到请求在到达控制器之前经历了哪些步骤。
使用 Plugs 简化控制器
Plugs 也可以用来清理控制器逻辑。看看这个"混乱"的控制器:
ruby
defmodule HelloWeb.MessageController do
use HelloWeb, :controller
def show(conn, params) do
case Authenticator.find_user(conn) do
{:ok, user} ->
case find_message(params["id"]) do
nil ->
conn |> put_flash(:info, "That message wasn't found") |> redirect(to: ~p"/")
message ->
if Authorizer.can_access?(user, message) do
render(conn, :show, page: message)
else
conn |> put_flash(:info, "You can't access that page") |> redirect(to: ~p"/")
end
end
:error ->
conn |> put_flash(:info, "You must be logged in") |> redirect(to: ~p"/")
end
end
end
嵌套结构使得应用程序流程难以理解。我们可以使用 Plug 将其重构为一个功能性管道:
ruby
defmodule HelloWeb.MessageController do
use HelloWeb, :controller
plug :authenticate
plug :fetch_message
plug :authorize_message
def show(conn, params) do
render(conn, :show, page: conn.assigns[:message])
end
defp authenticate(conn, _) do
case Authenticator.find_user(conn) do
{:ok, user} ->
assign(conn, :user, user)
:error ->
conn |> put_flash(:info, "You must be logged in") |> redirect(to: ~p"/") |> halt()
end
end
defp fetch_message(conn, _) do
case find_message(conn.params["id"]) do
nil ->
conn |> put_flash(:info, "That message wasn't found") |> redirect(to: ~p"/") |> halt()
message ->
assign(conn, :message, message)
end
end
defp authorize_message(conn, _) do
if Authorizer.can_access?(conn.assigns[:user], conn.assigns[:message]) do
conn
else
conn |> put_flash(:info, "You can't access that page") |> redirect(to: ~p"/") |> halt()
end
end
end
这使得我们的 show 方法变得极其清晰。
路由
Phoenix 路由感觉非常类似于 .NET 中的"Minimal APIs"。你有一个定义所有路由的单一文件,将它们指向应使用的特定控制器和管道。
ruby
defmodule HelloWeb.Router do
use HelloWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {HelloWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", HelloWeb do
pipe_through :browser
get "/", PageController, :home
end
# 其他作用域可能使用自定义堆栈。
# scope "/api", HelloWeb do
# pipe_through :api
# end
# ...
end
Phoenix 的另一个优秀特性是能够通过 CLI 列出应用程序中的每条路由:
bash
mix phx.routes
输出:
bash
GET / HelloWeb.PageController :home
控制器
在 Phoenix 中创建控制器非常简单。你使用基础模块引入必要的功能并定义动作函数。
cs
defmodule HelloWeb.PageController do
use HelloWeb, :controller
def home(conn, _params) do
render(conn, :home)
end
end
每个动作接收一个连接(conn)和参数,并返回一个(可能已修改的)连接。这种函数式方法---通过一系列步骤转换连接---是一种核心模式。
我的观点:.NET 与 Elixir 生态系统
从 .NET 世界而来,我发现自己不断在比较这两者。
令人耳目一新的是,Elixir 拥有一个强大而主导的 Web 框架。在其他生态系统中,你可能需要学习 2-3 个不同的框架才能决定使用哪一个。而在 Elixir 中,整个社区都围绕着 Phoenix 团结一致。
当谈到实时功能(如 `LiveView`)时,Phoenix 似乎最为闪耀。在 .NET 中,我们有 SignalR 和 Blazor Server,但我没有看到像 Elixir 中 Phoenix 采用率那样多的项目全面采用它们。
生态系统因素:.NET 背后有一家大型公司。这既有好处也有坏处。
- 好处:他们拥有大量资源可以投入到生态系统中。
- 坏处:这可能会抑制开源创新。例如,ASP.NET Core 由微软维护,因此社区驱动的 Web 框架很难与之竞争。同样,Entity Framework Core 基本上取代了 NHibernate,微软的原生依赖注入减少了对 Autofac 或 Ninject 等库的需求。
在 Elixir 世界中,没有单一科技巨头控制发展方向。Elixir 和 Phoenix 团队(相对较小)正在做着令人难以置信的工作,他们的工作支持开源倡议,而不是主导它。
结论
这是对该框架的高级概述。在下一篇文章中,我将演示如何使用 Phoenix 创建一个简单的 API。
参考资料
https://www.phoenixframework.org/
https://hexdocs.pm/phoenix/overview.html