PHP限界上下文怎么划分

wen PHP项目 1

本文目录导读:

PHP限界上下文怎么划分

  1. 核心原则:基于业务语义,而非技术分层
  2. 划分步骤与实战示例
  3. 代码结构组织(PHP示例)
  4. 关键的划分技巧
  5. PHP项目中的常见误区和纠正
  6. 总结:何时重新划分?

在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类。

  • 错误:OrderPayment 都使用同一个 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”,大项目需严格分层

何时重新划分?

如果遇到以下情况,说明需要重新审视限界上下文的划分:

  1. 一个上下文里的改动,经常需要修改另一个上下文。
  2. 你的“领域模型”越来越胖,职责过多。
  3. 团队开会时,对同一个术语(如“订单状态”)有不同理解。
  4. 测试变得困难,因为耦合严重。

在实际PHP落地中,不需要一步到位完美划分。 可以先基于业务直觉进行粗粒度划分(比如先分5个模块),随着业务理解加深,再逐步拆解或合并,关键是保持每个上下文内部的高内聚上下文之间的事件驱动协作

抱歉,评论功能暂时关闭!