本文目录导读:

在PHP项目中,限界上下文(Bounded Context) 的划分是领域驱动设计(DDD)落地的核心难点,它不仅是代码的组织方式,更是业务模型的边界。
以下是针对PHP项目进行限界上下文划分的实战指南,包含原则、步骤和代码示例。
核心原则:基于业务语义,而非技术分层
错误做法(技术驱动):
Models/(放所有模型的文件夹)Services/(放所有服务的文件夹)Repositories/(放所有仓库的文件夹)
正确做法(业务驱动):
- 一个上下文代表一个独立的业务子域(如:订单、支付、库存、用户)
- 每个上下文有自己独立的数据库表、模型、逻辑
划分步骤与实战示例
假设要开发一个电商系统,可以划分出以下几个关键上下文:
| 限界上下文 | 核心职责 | 关键实体 |
|---|---|---|
| 用户上下文 | 注册、登录、权限、地址管理 | User, Address, Role |
| 订单上下文 | 创建订单、状态流转、金额计算 | Order, OrderItem, OrderStatus |
| 支付上下文 | 发起支付、退款、与第三方网关交互 | Payment, Transaction, Refund |
| 库存上下文 | 库存扣减、库存预警、SKU管理 | Stock, ProductSku, Warehouse |
| 商品上下文 | 商品详情、分类、搜索展示 | Product, Category, Attribute |
代码结构组织(PHP示例)
推荐使用模块化或单项目多模块结构,以下是一个基于Laravel风格的目录结构(框架无关,核心是业务隔离):
ecommerce/
├── modules/
│ ├── Order/
│ │ ├── Domain/ # 领域层(核心逻辑,无框架依赖)
│ │ │ ├── Model/
│ │ │ │ └── Order.php # 实体,包含业务方法
│ │ │ ├── Repository/
│ │ │ │ └── OrderRepositoryInterface.php
│ │ │ ├── Service/
│ │ │ │ └── OrderService.php
│ │ │ └── Event/
│ │ │ └── OrderCreated.php
│ │ ├── Infrastructure/ # 基础设施层(DB、API、缓存)
│ │ │ ├── Repository/
│ │ │ │ └── EloquentOrderRepository.php
│ │ │ └── Migration/
│ │ │ └── create_orders_table.php
│ │ └── UI/ # 接口层(Controller、Request、Response)
│ │ ├── Controller/
│ │ │ └── OrderController.php
│ │ └── Request/
│ │ └── CreateOrderRequest.php
│ ├── Payment/
│ │ └── ...(类似结构)
│ ├── Stock/
│ └── User/
关键的划分技巧
寻找“语言边界”
每个上下文内部要有统一的业务语言(Ubiquitous Language)。
- 在库存上下文里,说“SKU”、“入库单”、“库存水位”。
- 在商品上下文里,说“SPU”、“商品详情”、“主图”、“规格值”。
代码体现:
// Stock上下文
class Stock {
public function reserve(ProductSku $sku, int $quantity): void { ... }
}
// Product上下文
class Product {
public function updateTitle(string $title): void { ... }
}
明确上下文之间的“协作关系”
上下文不能完全隔离,他们之间需要通信,常见方式:
- 事件驱动(推荐):订单创建后,发布事件,库存和支付上下文监听事件。
- 防腐层(ACL):当上下文B调用上下文A时,要转成上下文B的模型。
示例:订单上下文 调用 库存上下文(通过事件解耦)
// Order上下文 领域事件
class OrderCreated {
public function __construct(
public string $orderId,
public array $items // 包含skuId和quantity
) {}
}
// Stock上下文 事件监听器
class ReserveStockListener {
public function handle(OrderCreated $event): void {
foreach ($event->items as $item) {
// Stock上下文用自己的逻辑和数据库
$sku = ProductSku::find($item['skuId']);
$sku->decreaseStock($item['quantity']);
}
}
}
拒绝“共享模型”
两个上下文不要直接共享同一个Model类。
- 错误:
Order和Payment都使用同一个User模型。 - 正确:每个上下文维护自己需要的用户数据副本(或只保留ID)。
示例:Order上下文里的User数据
// Order上下文 - 只保留订单关心的用户快照
class OrderUser {
public function __construct(
public string $userId,
public string $name, // 从用户上下文同步过来
public string $phone
) {}
}
// User上下文 - 完整的用户实体
class User {
public function __construct(
public string $userId,
public string $name,
public string $phone,
public string $email,
public string $passwordHash
) {}
}
使用“上下文路线图”辅助决策
画一张简单的图,标出:
- 每个上下文的核心数据是什么?
- 每个上下文拥有哪些数据库表?
- 上下文之间的数据流向是什么?
[用户] --(用户ID)--> [订单]
[订单] --(订单ID)--> [支付]
[支付] --(支付成功事件)--> [订单](更新状态)
[订单] --(订单创建事件)--> [库存](扣减库存)
PHP项目中的常见误区和纠正
| 常见误区 | 正确做法 |
|---|---|
| 所有业务逻辑写在Controller里 | 业务逻辑放在Domain层,Controller负责路由和参数校验 |
| 所有上下文共用一个数据库 | 每个上下文可以有自己的数据库(或至少有自己的Schema) |
| 使用全局的Event系统 | 事件只在上下文内部或通过消息队列跨上下文传递 |
| 过度追求“纯DDD”导致代码复杂 | 根据项目规模调整:小项目可用“简化DDD”,大项目需严格分层 |
何时重新划分?
如果遇到以下情况,说明需要重新审视限界上下文的划分:
- 一个上下文里的改动,经常需要修改另一个上下文。
- 你的“领域模型”越来越胖,职责过多。
- 团队开会时,对同一个术语(如“订单状态”)有不同理解。
- 测试变得困难,因为耦合严重。
在实际PHP落地中,不需要一步到位完美划分。 可以先基于业务直觉进行粗粒度划分(比如先分5个模块),随着业务理解加深,再逐步拆解或合并,关键是保持每个上下文内部的高内聚和上下文之间的事件驱动协作。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播