Phoenix 1.3.0 发布

发表于 2017 年 7 月 28 日,作者 Chris McCord


Phoenix 1.3.0 现已发布!此版本侧重于代码生成器,包括改进的项目结构、一流的伞形项目支持以及强化 Phoenix 作为您更大 Elixir 应用程序的 Web 接口的脚手架。我们还添加了一个新的 action_fallback 功能到 Phoenix.Controller 中,允许您将域中的常见数据结构转换为有效的响应。在实践中,这将清理您的控制器代码,并为您提供一个处理重复代码路径的单一位置。它 对 JSON API 控制器特别有用。1.3 版本还包含我们的通道线协议 V2,它可以解决某些消息模式下的竞争条件,以及改进的序列化格式。

对于那些有兴趣了解更改和设计决策的详细概述的人,请查看我的 LonestarElixir 主题演讲:https://www.youtube.com/watch?v=tMO28ar0lW8。请注意,演讲中的目录结构略微过时,但所有想法仍然适用。

要使用新的 phx.new 项目生成器,您可以使用以下命令安装存档

$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new.ez

1.3.0 在所有生成器上使用 phx. 前缀。旧的生成器仍然存在,以便社区和学习资源有时间赶上来。它们将在 1.4.0 中删除。

与往常一样,我们有一个 升级指南,其中包含从 1.2.x 项目迁移的详细说明。

1.3.0 是一个向后兼容的版本,因此升级可以像将 mix.exs 中的 :phoenix 依赖项更新为“~> 1.3”一样简单。对于那些想要采用新约定的人来说,升级指南将一步一步地引导您。在升级之前,值得一看主题演讲或探索下面概述的设计决策。

Phoenix 1.3 - 有意设计

新的项目和代码生成器借鉴了过去两年的经验教训,并推动人们在学习时做出更好的设计决策。新项目有一个 lib/my_app 目录用于业务逻辑,以及一个 lib/my_app_web 目录,其中包含所有与 Phoenix 相关的 Web 模块,它们是您更大 Elixir 应用程序的 Web 接口。除了新的项目结构之外,还有新的 phx.gen.htmlphx.gen.json 生成器,它们采用了将 Web 接口与域隔离的目标。

上下文

当您使用 phx.gen.html|json 生成 HTML 或 JSON 资源时,Phoenix 将在上下文中生成代码。上下文是专门的模块,用于公开和分组相关功能。例如,每当您调用 Elixir 的标准库时,无论是 Logger.info/1 还是 Stream.map/2,您都在访问不同的上下文。在内部,Elixir 的日志记录器由多个模块组成,例如 Logger.ConfigLogger.Backends,但我们从未直接与这些模块交互。我们将 Logger 模块称为上下文,正是因为它公开并分组了所有日志记录功能。

例如,要生成一个“用户”资源,我们将运行

$ mix phx.gen.html Accounts User users email:string:unique

请注意,“Accounts” 是一个新的必需第一个参数。这是您的代码将驻留在其中的上下文模块,它执行应用程序中用户帐户的业务逻辑。它可以包括诸如身份验证和用户注册等功能。以下是一部分生成的代码的预览

# lib/my_app_web/controllers/user_controller.ex
defmodule MyAppWeb.UserController do
  ...
  alias MyApp.Accounts

  def index(conn, _params) do
    users = Accounts.list_users()
    render(conn, "index.html", users: users)
  end

  def create(conn, %{"user" => user_params}) do
    case Accounts.create_user(user_params) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "user created successfully.")
        |> redirect(to: user_path(conn, :show, user))
      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end
  ...
end


# lib/my_app/accounts/accounts.ex
defmodule MyApp.Accounts do
  alias MyApp.Accounts.User

  def list_users do
    Repo.all(User)
  end

  def create_user(attrs \\ %{}) do
    %User{}
    |> User.changeset(attrs)
    |> Repo.insert()
  end
  ...
end

您还将在 lib/my_app/accounts/user.ex 中生成一个 Ecto 架构。请注意,我们的控制器如何调用 API 边界来创建或获取系统中的用户。现在,我们可以轻松地在其他控制器、Phoenix 通道、管理任务等中重用该逻辑。测试也变得更加直接,因为我们可以测试域的输入和输出,而无需经过 Web 堆栈。

使用上下文进行设计为您提供了从头开始扩展应用程序的坚实基础。使用离散的、定义明确的 API 公开系统的意图,使您可以编写更易于维护的应用程序,并使用可重用代码。此外,我们可以通过浏览应用程序目录结构来了解应用程序的功能及其功能集

lib
 my_app
    accounts
       accounts.ex
       user.ex
    sales
       manager.ex
       sales.ex
       ticket.ex
    repo.ex
 my_app.ex
 my_app_web
    channels
    controllers
    templates
    views
 my_app_web.ex

只需看一眼目录结构,我们就可以看到这个应用程序有一个用户帐户系统,以及一个销售系统。我们还可以推断出 sales.exaccounts.ex 模块之间存在一个自然的 API。我们获得了这种洞察力,而无需查看一行代码。将其与以前的 web/models 进行对比,后者没有揭示文件之间的任何关系,主要反映了您的数据库结构,没有提供有关它们如何真正与您的域相关的洞察力。

action_fallback

新的 action_fallback 功能允许您指定一个插头,如果您的控制器操作未能返回有效的 Plug.Conn{} 结构,则会调用该插头。然后,操作回退插头的任务是获取控制器操作之前的连接,以及结果并将其转换为有效的插头响应。这对 JSON API 尤其有用,因为它消除了控制器之间的重复。例如,您之前的控制器可能看起来像这样


def MyAppWeb.PageController do
  alias MyApp.CMS
  def show(conn, %{"id" => id}) do
    case CMS.get_page(id, conn.assigns.current_user) do
      {:ok, page} -> render(conn, "show.html", page: page)
      {:error, :not_found} ->
        conn
        |> put_status(404)
        |> render(MyAppWeb.ErrorView, :"404")
      {:error, :unauthorized} ->
        conn
        |> put_status(401)
        |> render(MyAppWeb.ErrorView, :"401")
    end
  end
end

这段代码本身很好,但问题是域中的常见数据结构,例如 {:error, :not_found}{:error, :unauthorized} 必须在许多不同的控制器中重复处理。现在有一个更好的方法可以使用操作回退。在 1.3 中,我们可以编写

def MyAppWeb.PageController do
  alias MyApp.CMS

  action_fallback MyAppWeb.FallbackController

  def show(conn, %{"id" => id}) do
    with {:ok, page} <- CMS.get_page(id, conn.assigns.current_user) do
      render(conn, "show.html", page: page)
    end
  end
end


defmodule MyAppWeb.FallbackController do
  def call(conn, {:error, :not_found}) do
    conn
    |> put_status(:not_found)
    |> render(MyAppWeb.ErrorView, :"404")
  end

  def call(conn, {:error, :unauthorized}) do
    conn
    |> put_status(:unauthorized)
    |> render(MyAppWeb.ErrorView, :"401")
  end
end

请注意,我们的控制器现在可以使用 with 表达式匹配到成功路径。然后,我们可以指定一个回退控制器,该控制器在一个地方处理响应转换。这对代码清晰度和消除重复是一个巨大的胜利。

我们对这些更改及其在可维护性方面的长期回报感到兴奋。我们还认为,它们将导致可共享的、隔离的库,整个社区都可以利用这些库——无论是在 Phoenix 相关的项目内部还是外部。

如果您在升级时遇到问题,请在 #elixir-lang irc 或 slack 上找到我们,我们将解决问题!

最后但并非最不重要的一点,我想花一点时间感谢让这个项目成为可能的公司。非常感谢 plataformatec 对 Elixir 开发的持续支持,并感谢 DockYard 对 Phoenix 的赞助。

祝您编码愉快!🐥🔥

-Chris

完整变更日志

1.3.0-rc.3 (2017-07-24)

  • 增强功能

    • [ChannelTest] 将 connect 订阅到 UserSocket.id 以支持测试强制断开连接
    • [Socket] 在定义通道路由时支持静态 :assigns
    • [Channel] 添加通道线协议 V2,它可以解决竞争条件并压缩有效负载
    • [phx.new] 使用新的 lib/my_applib/my_app_web 目录结构
    • [phx.new] 对 Web 模块使用新的 MyAppWeb 别名约定
    • [phx.gen.context] 不再以上下文名称为前缀 Ecto 表格名称
  • JavaScript 客户端增强功能

    • 使用 V2 通道线协议支持
  • JavaScript 客户端错误修复

    • 当客户端发生加入超时,而服务器通道成功加入时,解决竞争条件

1.3.0-rc.2 (2017-05-15)

请查看这些 1.2.x1.3.x 升级说明,以将您现有的应用程序更新到最新状态。

  • 增强功能

    • [生成器] 添加新的 phx.newphx.new.webphx.new.ecto 项目生成器,具有改进的应用程序结构和对伞形应用程序的支持
    • [生成器] 添加新的 phx.gen.htmlphx.gen.json 资源生成器,具有改进的 API 边界隔离
    • [控制器] 添加 current_pathcurrent_url 以生成连接的路径和 URL
    • [控制器] 引入 action_fallback 来注册一个插头,作为控制器操作的回退调用
    • [控制器] 在控制器中包装异常以维护连接状态
    • [通道] 添加使用 :log_join:log_handle_in 选项配置通道事件日志记录的功能
    • [通道] 警告未处理的 handle_info/2 消息
    • [通道] 通道现在区分正常退出和应用程序重启,允许客户端进入错误模式并在冷部署后重新连接。
    • [路由器] 文档 match 支持使用特殊 :* 参数匹配任何 HTTP 方法
    • [路由器] 使用路由的路径参数填充 conn.path_params
    • [ConnTest] 添加 redirected_params/1 以返回路由器中匹配的重定向 URL 的命名参数
    • [Digester] 添加 mix phx.digest.clean 以删除已编译资产的旧版本
    • [phx.new] 在 phx.new 安装程序存档中添加 Erlang 20 支持
  • 错误修复

    • [控制器] 针对任意 URL 重定向硬化本地重定向
    • [控制器] 修复使用 clear_flash/1 时导致闪存会话保留的问题
  • 弃用

    • [生成器] 所有 phoenix.* mix 任务已被弃用,取而代之的是新的 phx.* 任务
  • JavaScript 客户端增强功能

    • 添加了向套接字构造函数传递 encodedecode 函数以进行自定义编码和解码传入和传出消息的功能。
    • 检测客户端上的心跳超时,以处理非正常连接丢失,从而实现更快的套接字错误检测
    • 添加对 AMD/RequireJS 的支持