领域驱动设计聚合如何帮助您在现代 PHP 中构建可维护、一致且可读的系统。

现代 PHP 应用程序不再是简单的 CRUD 系统。今天,我们构建分布式系统、微服务、SaaS 平台、事件驱动系统以及领域丰富的应用程序。随着复杂性的增加,传统的 MVC 结构往往在纠缠的业务逻辑、脆弱的模型和不一致的状态管理下崩溃。
一种显著改善复杂系统设计架构概念是聚合,这是领域驱动设计 (DDD) 的核心模式。
在本文中,我们将探讨:
- 如何在现代 PHP (8.0 → 8.5) 中实现聚合
本文假设您已经具备面向对象编程 (OOP)、架构模式以及现代 PHP 特性的经验。
问题:大型 PHP 应用程序中的复杂性
大多数 PHP 应用程序从简单开始。
典型结构:
Controller ├── Service │ └── Repository └── Model (Entity)
但几年后,业务逻辑散布到各处:
最终您会得到:
*贫血模型*重复的业务规则*不一致的领域状态*难以测试的逻辑
这正是领域驱动设计 (DDD) 试图解决的问题。
DDD 专注于围绕业务领域建模软件,将代码结构与业务概念和规则对齐。
这种方法中最强大的工具之一是聚合。
什么是聚合?
聚合 是领域对象(实体和值对象)的一个集群,被视为单个单元进行数据修改。
在聚合内部:
示例:
Order (Aggregate Root) ├── OrderItem ├── OrderItem └── ShippingAddress (Value Object)
外部代码仅与聚合根交互。
$order->addItem($productId, 2);$order->cancel();$order->changeShippingAddress($address);
其余对象是内部实现细节。
这种设计确保业务规则始终被保留。
聚合的核心组件
聚合由几个领域构建块构成。
1. 实体
由身份定义的对象。
示例:
finalclassOrder{publicfunction__construct( private OrderId $id ){}}
即使属性发生变化,实体仍然是相同的。
2. 值对象
由属性而非身份定义的对象。
它们通常是不可变的。
示例:
final readonly classMoney{publicfunction__construct( public int $amount, public string $currency ){}}
3. 聚合根
修改聚合内部对象的唯一入口点。
示例:
finalclassOrder{privatearray $items = [];publicfunctionaddItem(ProductId $productId, int $quantity): void{if ($quantity <= 0) {thrownew DomainException("Invalid quantity"); }$this->items[] = new OrderItem($productId, $quantity); }}
外部服务不能直接修改 OrderItem。
为什么聚合在复杂系统中重要
聚合解决了几个架构问题。
1. 保护业务不变量
不变量是必须始终为真的规则。
示例:
订单总额必须等于项目总和
没有聚合时:
Service A 修改订单项目Service B 重新计算总额Service C 更新折扣
系统很容易变得不一致。
使用聚合时:
$order->addItem(...)
根保证不变量。
示例:
publicfunctionaddItem(ProductId $productId, int $quantity): void{$this->items[] = new OrderItem($productId, $quantity);$this->recalculateTotal();}
一致性得到保证。
2. 定义事务边界
聚合定义事务边界。
聚合内部的所有变更必须一起成功或失败。
示例:
Order ├── items ├── shipping └── payment status
要么所有变更成功,要么整个操作回滚。
这大大简化了事务管理。
3. 减少耦合
没有聚合时:
OrderItem -> ProductProduct -> InventoryInventory -> Order
您会得到意大利面条般的依赖图。
聚合限制引用。
外部对象只能引用聚合根。
这大大减少了耦合。
4. 封装领域逻辑
聚合强制执行行为驱动模型。
而不是:
$order->status = "shipped";
您使用:
$order->ship();
这确保规则被强制执行。
5. 改善可读性
比较两种方法。
没有聚合
$orderService->addItem(...)$orderRepository->update(...)$totalService->recalculate(...)
使用聚合
$order->addItem(...)$orderRepository->save($order)
业务流程变得更容易理解。
使用现代 PHP (8.0 → 8.5) 的聚合
现代 PHP 特性使聚合的实现显著更容易。
让我们探讨最有用的特性。
1. 构造函数属性提升
减少样板代码。
finalclassOrderItem{publicfunction__construct( private ProductId $productId, private int $quantity ){}}
2. 只读属性
完美适用于值对象。
final readonly classAddress{publicfunction__construct( public string $street, public string $city ){}}
保证不可变性。
3. 用于领域状态的枚举
enum OrderStatus{case Draft;case Paid;case Shipped;case Cancelled;}
在聚合中使用。
private OrderStatus $status;
4. 类型化属性
避免运行时错误。
privatearray $items = [];
更好:
/** @var OrderItem[] */privatearray $items = [];
5. 属性
对于框架或事件溯源很有用。
#[Aggregate]finalclassOrder{}
示例:在现代 PHP 中的订单聚合
finalclassOrder{privatearray $items = [];private OrderStatus $status;publicfunction__construct( private OrderId $id ){$this->status = OrderStatus::Draft; }publicfunctionaddItem(ProductId $productId, int $quantity): void{if ($this->status !== OrderStatus::Draft) {thrownew DomainException("Cannot modify finalized order"); }$this->items[] = new OrderItem($productId, $quantity); }publicfunctionpay(): void{if (empty($this->items)) {thrownew DomainException("Order must contain items"); }$this->status = OrderStatus::Paid; }publicfunctionship(): void{if ($this->status !== OrderStatus::Paid) {thrownew DomainException("Order must be paid first"); }$this->status = OrderStatus::Shipped; }}
注意:
聚合的仓库模式
聚合通常通过仓库持久化。
示例:
interfaceOrderRepository{publicfunctionsave(Order $order): void;publicfunctionfind(OrderId $id): ?Order;}
仓库将聚合视为集合。
这将持久化复杂性从领域中隐藏。
常见的聚合错误
1. 巨大的聚合
不良:
User ├── Orders ├── Payments ├── Addresses ├── Notifications
太大 → 性能问题。
2. 贫血聚合
不良:
$order->setStatus("paid");
没有行为。
3. 跨聚合引用
而不是:
Order -> Product object
使用:
Order -> ProductId
何时应该使用聚合?
聚合最有用时:
典型领域:
对大型 PHP 系统的益处
使用聚合会导致:
更干净的架构
业务逻辑位于领域模型内部。
更好的可维护性
规则被集中化。
改进的可测试性
聚合易于单元测试。
减少错误
无效状态变得不可能。
总结
聚合不仅仅是领域驱动设计中的理论概念。
它们是构建大型、可维护 PHP 应用程序的最实用工具之一。
通过强制执行:
聚合允许开发者编写干净、可读且可靠的领域模型。
结合现代 PHP 特性(只读、枚举、属性、强类型),聚合成为建模复杂系统的一种强大方式。
对于构建长期产品的团队,采用聚合往往标志着从框架驱动开发到架构驱动系统的转变。
而那就是真正可扩展性的开始。