从 .NET 到 Elixir:初探 Phoenix 框架

作为一名踏上学习 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 服务器(如 CowboyBandit)的抽象层。核心概念是一个统一的 `连接`(%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 中,我们有 SignalRBlazor 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

https://en.wikipedia.org/wiki/Phoenix_(web_framework)

https://elixirschool.com/en/lessons/misc/plug

相关推荐
m0_488777651 天前
Web与Nginx网站服务
nginx·web
三七吃山漆2 天前
攻防世界——easy_web
安全·网络安全·web·ctf
励志成为糕手3 天前
基于SpringBoot的企业考勤管理系统设计与实现
java·spring boot·后端·web·企业应用
yezipi耶不耶3 天前
Cloudflare 11.18 故障深度复盘:当“极致优化”撞上“现实边界“
rust·web
sg_knight10 天前
微信小程序中 WebView 组件的使用与应用场景
前端·javascript·微信·微信小程序·小程序·web·weapp
逻极10 天前
Webhook 全解析:事件驱动时代的实时集成核心技术
python·web
敲敲了个代码10 天前
11月3-5年Web前端开发面试需要达到的强度
前端·vue.js·学习·react.js·面试·职场和发展·web
带刺的坐椅11 天前
Solon Web 的“分身术”:单应用多端口监听,化身多重服务
java·web·solon·端口·单体多模块
合作小小程序员小小店12 天前
web网页开发,在线物流管理系统,基于Idea,html,css,jQuery,jsp,java,SSM,mysql
java·前端·后端·spring·intellij-idea·web