名称: phoenix-contexts 描述: Phoenix上下文模式、有界上下文设计、范围以及模块结构。在设计上下文、组织业务逻辑或规划模块结构时加载。 用户可调用: 假
Phoenix上下文参考
设计和实现Phoenix上下文(有界上下文)的参考。
铁律 — 切勿违反
- 上下文拥有其数据 — 切勿通过Repo直接查询另一个上下文的架构
- 范围是强制性的(Phoenix 1.8+) — 每个上下文函数必须接受范围作为第一个参数
- 瘦控制器/LiveView — 控制器处理HTTP,业务逻辑保持在上下文中
- 架构中无副作用 — 使用
Ecto.Multi处理具有副作用的事务
上下文结构
lib/my_app/
├── accounts/ # 上下文目录
│ ├── user.ex # 架构
│ ├── scope.ex # 范围结构体(Phoenix 1.8+)
├── accounts.ex # 上下文模块(公共API)
Phoenix 1.8+ 范围(关键)
所有上下文函数必须接受范围作为第一个参数:
def list_posts(%Scope{} = scope) do
from(p in Post, where: p.user_id == ^scope.user.id)
|> Repo.all()
end
def create_post(%Scope{} = scope, attrs) do
%Post{user_id: scope.user.id}
|> Post.changeset(attrs)
|> Repo.insert()
|> broadcast(scope, :created)
end
快速决策
何时拆分上下文?
- 模块超过约400行
- 函数不共享领域语言
- 理论上可以是独立的微服务
- 团队成员可以独立拥有
何时保持在一起?
- 资源共享词汇和领域概念
- 函数经常一起操作相同数据
- 拆分会产生过多的跨上下文调用
跨上下文引用
# ✅ 通过ID引用,在边界处转换
def create_order(%Scope{} = scope, user_id, product_ids) do
with {:ok, user} <- Accounts.fetch_user(scope, user_id) do
do_create_order(scope, user.id, product_ids)
end
end
# ❌ 深入到其他上下文的内部
alias MyApp.Accounts.User # 不要这样做
Repo.all(from o in Order, join: u in User, ...) # 不要查询其他架构
反模式
| 错误 | 正确 |
|---|---|
服务对象(UserCreationService) |
上下文函数(Accounts.create_user/2) |
| 包裹Repo的仓储模式 | Repo就是仓储 |
| 控制器中直接调用Repo | 委托给上下文 |
| 具有副作用的架构回调 | 使用Ecto.Multi |
版本说明
- Phoenix 1.8+:使用内置的
%Scope{}结构体进行授权上下文 - Phoenix 1.7:需要手动授权上下文(见
references/scopes-auth.md“预范围模式”)
参考资料
详细模式见:
references/context-patterns.md- 完整上下文模块、PubSub、Multi、跨边界references/scopes-auth.md- 范围结构体、多租户、授权、插件references/routing-patterns.md- 已验证路由、管道、API授权references/plug-patterns.md- 函数/模块插件、放置、守卫references/json-api-patterns.md- JSON控制器、FallbackController、API授权