name: phoenix-patterns user-invocable: false description: 当应用Phoenix Framework最佳实践时使用,包括上下文设计、控制器模式和应用架构。在构建Phoenix应用程序时使用。 allowed-tools:
- Bash
- Read
Phoenix模式
掌握Phoenix框架模式,以构建结构良好、可维护的Elixir Web应用程序。
上下文设计
上下文是专用模块,用于暴露和分组相关功能:
defmodule MyApp.Accounts do
@moduledoc """
Accounts上下文处理用户管理和认证。
"""
alias MyApp.Repo
alias MyApp.Accounts.User
def list_users do
Repo.all(User)
end
def get_user!(id), do: Repo.get!(User, id)
def get_user_by_email(email) do
Repo.get_by(User, email: email)
end
def create_user(attrs \\ %{}) do
%User{}
|> User.changeset(attrs)
|> Repo.insert()
end
def update_user(%User{} = user, attrs) do
user
|> User.changeset(attrs)
|> Repo.update()
end
def delete_user(%User{} = user) do
Repo.delete(user)
end
def change_user(%User{} = user, attrs \\ %{}) do
User.changeset(user, attrs)
end
end
控制器模式
defmodule MyAppWeb.UserController do
use MyAppWeb, :controller
alias MyApp.Accounts
alias MyApp.Accounts.User
action_fallback MyAppWeb.FallbackController
def index(conn, _params) do
users = Accounts.list_users()
render(conn, :index, users: users)
end
def create(conn, %{"user" => user_params}) do
with {:ok, %User{} = user} <- Accounts.create_user(user_params) do
conn
|> put_status(:created)
|> put_resp_header("location", ~p"/api/users/#{user}")
|> render(:show, user: user)
end
end
def show(conn, %{"id" => id}) do
user = Accounts.get_user!(id)
render(conn, :show, user: user)
end
def update(conn, %{"id" => id, "user" => user_params}) do
user = Accounts.get_user!(id)
with {:ok, %User{} = user} <- Accounts.update_user(user, user_params) do
render(conn, :show, user: user)
end
end
def delete(conn, %{"id" => id}) do
user = Accounts.get_user!(id)
with {:ok, %User{}} <- Accounts.delete_user(user) do
send_resp(conn, :no_content, "")
end
end
end
回退控制器
defmodule MyAppWeb.FallbackController do
use MyAppWeb, :controller
def call(conn, {:error, :not_found}) do
conn
|> put_status(:not_found)
|> put_view(json: MyAppWeb.ErrorJSON)
|> render(:"404")
end
def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
conn
|> put_status(:unprocessable_entity)
|> put_view(json: MyAppWeb.ChangesetJSON)
|> render(:error, changeset: changeset)
end
def call(conn, {:error, :unauthorized}) do
conn
|> put_status(:unauthorized)
|> put_view(json: MyAppWeb.ErrorJSON)
|> render(:"401")
end
end
插件管道
defmodule MyAppWeb.Router do
use MyAppWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {MyAppWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
plug MyAppWeb.Plugs.Authenticate
end
pipeline :admin do
plug MyAppWeb.Plugs.RequireAdmin
end
scope "/", MyAppWeb do
pipe_through :browser
get "/", PageController, :home
end
scope "/api", MyAppWeb do
pipe_through :api
resources "/users", UserController, except: [:new, :edit]
scope "/admin" do
pipe_through :admin
resources "/settings", Admin.SettingsController
end
end
end
自定义插件
defmodule MyAppWeb.Plugs.Authenticate do
import Plug.Conn
def init(opts), do: opts
def call(conn, _opts) do
with ["Bearer " <> token] <- get_req_header(conn, "authorization"),
{:ok, user} <- MyApp.Accounts.verify_token(token) do
assign(conn, :current_user, user)
else
_ ->
conn
|> put_status(:unauthorized)
|> Phoenix.Controller.put_view(json: MyAppWeb.ErrorJSON)
|> Phoenix.Controller.render(:"401")
|> halt()
end
end
end
何时使用此技能
在以下情况下使用phoenix-patterns:
- 结构化Phoenix应用架构
- 设计上下文边界
- 实现REST API控制器
- 创建自定义认证/授权
- 构建可重用的插件管道
最佳实践
- 保持控制器精简,上下文丰富
- 使用action_fallback进行错误处理
- 在上下文中分组相关功能
- 使用插件处理横切关注点
- 在路由中遵循RESTful约定
- 分离API和浏览器管道
常见陷阱
- 将业务逻辑放在控制器中
- 未使用上下文进行组织
- 创建过于庞大的上下文
- 忘记正确处理错误
- 未有效使用管道
- 在单个插件中混合关注点