全书目录

第三十八篇:Phoenix 完全指南 —— 实时频道城邦宇宙

第五章:Contexts 不是文件夹分类法,而是城邦的职能部门

1 分钟 350 字 第 724 / 962 个阅读单元

这是 Phoenix 最值得学透、也最容易被误解的部分。

很多人学别的 Web 框架久了,会自然地把业务逻辑往这些地方乱塞:

  • controller
  • live view
  • template helper
  • websocket handler

Phoenix 的官方主线一直很明确:

Web 层只是城门与窗口,真正的业务边界应该放在 Contexts。

也就是:

text
不要让办事大厅自己兼任:
- 档案管理员
- 审批规则制定者
- 广播调度员
- 权限审查官

这些应该进职能部门。

比如“调度”部门:

elixir
defmodule City.Dispatch do
  import Ecto.Query, warn: false

  alias City.Repo
  alias City.Accounts.Scope
  alias City.Dispatch.Incident

  def list_dispatches(%Scope{user: user}) do
    from(i in Incident,
      where: i.organization_id == ^user.organization_id,
      order_by: [desc: i.inserted_at]
    )
    |> Repo.all()
  end

  def create_dispatch(%Scope{user: user}, attrs) do
    %Incident{
      organization_id: user.organization_id,
      reporter_id: user.id
    }
    |> Incident.changeset(attrs)
    |> Repo.insert()
    |> case do
      {:ok, incident} ->
        Phoenix.PubSub.broadcast(
          City.PubSub,
          topic_for(user.organization_id),
          {:dispatch_created, incident}
        )

        {:ok, incident}

      {:error, changeset} ->
        {:error, changeset}
    end
  end

  defp topic_for(org_id), do: "org:#{org_id}:dispatches"
end

这段代码背后的心智很重要:

  • Context:职能局 API
  • Repo/Ecto:档案系统
  • Scope:当前操作者的权限边界
  • PubSub.broadcast/3:职能局对全城发通知

你以后判断 Phoenix 代码写得好不好,一个很实用的标准就是:

text
Web 层是不是只负责接待与展示?
业务规则是不是回到了 Context?

如果答案是“是”,这座城邦的治理结构通常就比较健康。