本文目录导读:

在 PHP 中实现领域驱动设计(DDD)的限界上下文(Bounded Context),核心思路是通过代码结构和隔离机制来明确划分边界,确保每个上下文拥有独立的领域模型、数据存储和业务规则。
以下是具体的实现策略和实践:
目录结构隔离
最基础的实现方式,通过命名空间和目录结构物理隔离。
src/
├── Contexts/
│ ├── Billing/ # 计费上下文
│ │ ├── Domain/ # 领域层
│ │ │ ├── Model/
│ │ │ ├── Repository/
│ │ │ └── Service/
│ │ ├── Application/ # 应用层
│ │ ├── Infrastructure/ # 基础设施层
│ │ └── Interface/ # 接口层
│ ├── Inventory/ # 库存上下文
│ │ ├── Domain/
│ │ ├── Application/
│ │ ├── Infrastructure/
│ │ └── Interface/
│ └── SharedKernel/ # 共享内核
│ └── ...
// Billing 上下文中的订单
namespace App\Contexts\Billing\Domain\Model;
class Order {
private OrderId $id;
private Money $totalAmount;
// Billing 上下文中订单只关心金额相关逻辑
}
// Inventory 上下文中的订单
namespace App\Contexts\Inventory\Domain\Model;
class Order {
private OrderId $id;
private array $items;
private WarehouseLocation $location;
// Inventory 上下文的订单只关心库存相关逻辑
}
依赖关系控制
通过 PHP 依赖注入容器(如 PHP-DI、Laravel 容器)控制跨上下文依赖。
// 使用接口定义跨上下文通信契约
interface PaymentServiceInterface {
public function processPayment(Order $order): PaymentResult;
}
// Billing 上下文实现
class BillingPaymentService implements PaymentServiceInterface {
// 实现自己的支付逻辑
}
// Inventory 上下文通过依赖注入使用
class InventoryService {
public function __construct(
private PaymentServiceInterface $paymentService
) {}
public function shipOrder(Order $order) {
$result = $this->paymentService->processPayment($order);
// ...
}
}
数据模型隔离
每个上下文使用独立的数据库表或数据库实例。
// Billing 上下文的数据层
namespace App\Contexts\Billing\Infrastructure\Persistence;
class OrderMapper {
private $connection; // 单独的数据库连接
public function save(Order $order) {
// 只操作 billing_orders 表
}
}
// Inventory 上下文的数据层
namespace App\Contexts\Inventory\Infrastructure\Persistence;
class OrderMapper {
private $connection; // 单独的数据库连接
public function save(Order $order) {
// 只操作 inventory_orders 表
}
}
事件驱动通信
通过领域事件(Domain Events)实现上下文间的松耦合通信。
// 定义共享事件
interface DomainEvent {
public function getPayload(): array;
}
// Billing 上下文发布事件
class OrderPaid implements DomainEvent {
public function __construct(
private string $orderId,
private float $amount
) {}
public function getPayload(): array {
return [
'order_id' => $this->orderId,
'amount' => $this->amount
];
}
}
// 事件分发器(跨上下文)
class DomainEventDispatcher {
private array $listeners = [];
public function addListener(string $eventClass, callable $listener): void {
$this->listeners[$eventClass][] = $listener;
}
public function dispatch(DomainEvent $event): void {
$eventClass = get_class($event);
foreach ($this->listeners[$eventClass] ?? [] as $listener) {
$listener($event);
}
}
}
// Inventory 上下文监听事件
class InventoryListener {
public function onOrderPaid(OrderPaid $event) {
$payload = $event->getPayload();
// 更新库存逻辑
}
}
防腐层(Anti-Corruption Layer)
当上下文之间协作时,通过防腐层转换数据模型。
// Inventory 上下文中的防腐层
class BillingOrderTranslator {
public function toLocalOrder(PaymentServiceInterface $paymentService): LocalOrder {
// 转换 Billing 的订单为 Inventory 的本地订单
return new LocalOrder(
$paymentService->getOrderDetails()
);
}
}
配置隔离
通过不同的配置文件或环境变量隔离上下文。
// 配置文件示例
// config/billing.php
return [
'db' => [
'host' => env('BILLING_DB_HOST', 'localhost'),
'port' => env('BILLING_DB_PORT', 3307),
'database' => env('BILLING_DB_NAME', 'billing_db'),
],
'payment_gateway' => env('BILLING_PAYMENT_GATEWAY'),
];
// config/inventory.php
return [
'db' => [
'host' => env('INVENTORY_DB_HOST', 'localhost'),
'port' => env('INVENTORY_DB_PORT', 3308),
'database' => env('INVENTORY_DB_NAME', 'inventory_db'),
],
'warehouse' => env('INVENTORY_WAREHOUSE_LOCATION'),
];
使用框架特性(Laravel 示例)
// App\Providers\BillingServiceProvider.php
class BillingServiceProvider extends ServiceProvider {
public function register() {
$this->app->bind(PaymentServiceInterface::class, BillingPaymentService::class);
}
}
// App\Providers\InventoryServiceProvider.php
class InventoryServiceProvider extends ServiceProvider {
public function register() {
$this->app->bind(InventoryRepository::class, MysqlInventoryRepository::class);
}
}
测试隔离
// 每个上下文有独立的测试套件
// tests/Billing/...
// tests/Inventory/...
class BillingOrderTest extends TestCase {
public function testOrderTotalCalculation() {
// 只测试 Billing 上下文的逻辑
}
}
class InventoryOrderTest extends TestCase {
public function testStockDeduction() {
// 只测试 Inventory 上下文的逻辑
}
}
最佳实践建议
- 先从简单的包结构开始,不需要一开始就拆分成微服务
- 主动设计边界,明确哪些业务逻辑属于哪个上下文
- 使用接口契约定义上下文间的通信
- 保持上下文的自治性,尽量避免直接的类依赖
- 通过事件驱动实现异步解耦
- 持续重构边界,根据业务变化调整
通过以上方式,PHP 项目可以有效地实现限界上下文的隔离,保证代码的可维护性和业务逻辑的清晰性。