PHP 在领域驱动(DDD)设计中的核心实践
领域驱动设计(Domain-Driven Design, DDD)是一种软件设计方法论,强调以业务领域为核心,通过建模来反映真实世界的业务逻辑。PHP 作为一种灵活且广泛使用的后端语言,在实现 DDD 时可以通过一系列实践来保持代码的清晰性、可维护性和业务一致性。本文将从实战角度出发,通过大量代码示例,展示 PHP 在 DDD 中的核心实践,包括实体、值对象、仓储、领域服务等关键概念。### 1. 实体与值对象的实现在 DDD 中,实体(Entity)具有唯一标识,而值对象(Value Object)则通过属性值来定义,且不可变。PHP 中可以通过类来清晰区分这两者。#### 代码示例 1:实体与值对象的定义php<?php/** * 值对象:表示金额,不可变且基于属性比较 */class Money{ private float $amount; private string $currency; public function __construct(float $amount, string $currency) { if ($amount < 0) { throw new InvalidArgumentException("金额不能为负"); } $this->amount = $amount; $this->currency = $currency; } // 不可变性:返回新对象 public function add(Money $other): Money { if ($this->currency !== $other->currency) { throw new InvalidArgumentException("货币类型不匹配"); } return new Money($this->amount + $other->amount, $this->currency); } // 基于属性比较 public function equals(Money $other): bool { return $this->amount === $other->amount && $this->currency === $other->currency; } public function getAmount(): float { return $this->amount; } public function getCurrency(): string { return $this->currency; }}/** * 实体:订单,具有唯一标识(订单ID) */class Order{ private string $orderId; private Money $totalAmount; private string $status; public function __construct(string $orderId, Money $initialAmount) { $this->orderId = $orderId; $this->totalAmount = $initialAmount; $this->status = 'pending'; } public function getOrderId(): string { return $this->orderId; } public function addItem(Money $itemPrice): void { $this->totalAmount = $this->totalAmount->add($itemPrice); } public function getTotalAmount(): Money { return $this->totalAmount; } public function getStatus(): string { return $this->status; }}// 使用示例$money1 = new Money(100.0, 'USD');$money2 = new Money(50.0, 'USD');$order = new Order('ORD-001', $money1);$order->addItem($money2);echo "订单总金额: " . $order->getTotalAmount()->getAmount() . " " . $order->getTotalAmount()->getCurrency();// 输出:订单总金额: 150 USD?>说明 :Money 作为值对象,使用不可变设计,并通过 equals 方法基于属性比较。Order 作为实体,通过 orderId 标识唯一性,并封装业务操作(如添加商品)。### 2. 仓储模式与依赖注入仓储(Repository)用于封装数据持久化逻辑,使领域层与基础设施层解耦。PHP 中常结合接口和依赖注入实现。#### 代码示例 2:仓储接口与实现php<?php/** * 订单仓储接口(定义在领域层) */interface OrderRepository{ public function save(Order $order): void; public function findByOrderId(string $orderId): ?Order;}/** * 基于MySQL的仓储实现(基础设施层) */class MySqlOrderRepository implements OrderRepository{ private PDO $pdo; public function __construct(PDO $pdo) { $this->pdo = $pdo; } public function save(Order $order): void { // 简化示例:实际应使用事务和关联表 $stmt = $this->pdo->prepare("INSERT INTO orders (order_id, amount, currency, status) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE amount = ?, currency = ?, status = ?"); $amount = $order->getTotalAmount()->getAmount(); $currency = $order->getTotalAmount()->getCurrency(); $status = $order->getStatus(); $orderId = $order->getOrderId(); $stmt->execute([$orderId, $amount, $currency, $status, $amount, $currency, $status]); } public function findByOrderId(string $orderId): ?Order { $stmt = $this->pdo->prepare("SELECT * FROM orders WHERE order_id = ?"); $stmt->execute([$orderId]); $row = $stmt->fetch(PDO::FETCH_ASSOC); if (!$row) { return null; } $money = new Money((float)$row['amount'], $row['currency']); $order = new Order($row['order_id'], $money); // 注意:实际中需要反射或构造方法设置状态,此处简化 return $order; }}/** * 应用层服务:使用依赖注入 */class OrderApplicationService{ private OrderRepository $orderRepository; public function __construct(OrderRepository $orderRepository) { $this->orderRepository = $orderRepository; } public function createOrder(string $orderId, float $initialAmount): void { $money = new Money($initialAmount, 'USD'); $order = new Order($orderId, $money); $this->orderRepository->save($order); }}// 使用(假设已建立PDO连接)// $pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');// $repository = new MySqlOrderRepository($pdo);// $service = new OrderApplicationService($repository);// $service->createOrder('ORD-002', 200.0);?>说明 :OrderRepository 接口定义在领域层,MySqlOrderRepository 实现细节在基础设施层。通过依赖注入,应用层服务无需关心数据存储方式,符合 DDD 的分层架构。### 3. 领域服务与聚合根领域服务处理跨实体的业务逻辑,而聚合根保证数据一致性。以订单和支付为例:php<?php/** * 领域服务:处理支付逻辑 */class PaymentService{ public function processPayment(Order $order, Money $paymentAmount): bool { if ($order->getStatus() !== 'pending') { throw new RuntimeException("订单状态不允许支付"); } if (!$order->getTotalAmount()->equals($paymentAmount)) { throw new RuntimeException("支付金额与订单总金额不匹配"); } // 模拟支付处理 // 更新订单状态(实际应通过聚合根方法) return true; }}### 总结PHP 在实现 DDD 时,需要遵循分层架构(领域层、应用层、基础设施层),并灵活运用实体、值对象、仓储、领域服务等模式。关键实践包括:- 使用值对象封装不可变业务概念(如金额),通过属性比较而非引用。- 利用实体唯一标识管理生命周期,并通过仓储解耦持久化。- 依赖注入和接口设计使领域层纯正,不依赖具体技术实现。- 领域服务处理跨聚合的业务逻辑,保持代码的领域聚焦。通过以上实践,PHP 项目能够更好地应对复杂业务需求,提高代码的可测试性和可维护性。虽然 DDD 学习曲线较陡,但在大型企业级应用中,它提供了一种强大的思维工具,帮助开发者与业务专家对齐,从而构建出更健壮的软件系统。