这是 Phoenix 最值得学透、也最容易被误解的部分。
很多人学别的 Web 框架久了,会自然地把业务逻辑往这些地方乱塞:
- controller
- live view
- template helper
- websocket handler
Phoenix 的官方主线一直很明确:
Web 层只是城门与窗口,真正的业务边界应该放在 Contexts。
也就是:
不要让办事大厅自己兼任:
- 档案管理员
- 审批规则制定者
- 广播调度员
- 权限审查官这些应该进职能部门。
比如“调度”部门:
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:职能局 APIRepo/Ecto:档案系统Scope:当前操作者的权限边界PubSub.broadcast/3:职能局对全城发通知
你以后判断 Phoenix 代码写得好不好,一个很实用的标准就是:
Web 层是不是只负责接待与展示?
业务规则是不是回到了 Context?如果答案是“是”,这座城邦的治理结构通常就比较健康。