Laravel 12 中实践领域驱动设计(DDD)的完整指南

Laravel 12 中实践领域驱动设计(DDD)的完整指南

在 Laravel 12 中实施领域驱动设计可以帮助您创建更可维护、可扩展且业务专注的应用程序。本指南将带您了解核心概念、实现步骤和最佳实践。

项目结构与核心概念

目录结构

src/
├── Domain/              # 核心业务逻辑
│   ├── Models/          # 聚合根和实体
│   ├── ValueObjects/    # 值对象
│   ├── Events/          # 领域事件
│   ├── Repositories/    # 仓储接口
│   └── Services/        # 领域服务
├── Application/         # 用例协调
│   ├── Services/        # 应用服务
│   ├── Commands/        # CQRS命令
│   ├── Queries/         # CQRS查询
│   └── DTOs/            # 数据传输对象
├── Infrastructure/      # 技术实现
│   ├── Repositories/    # 仓储实现
│   ├── Providers/       # 服务绑定
│   └── EventListeners/  # 事件监听器
└── UI/                  # 用户界面
    ├── Http/            # Web接口
    │   ├── Controllers/
    │   ├── Requests/
    │   └── Resources/
    └── Console/         # CLI命令

核心DDD组件

  • 聚合根(Aggregate Roots):业务边界,封装业务规则
  • 实体(Entities):具有唯一标识的对象
  • 值对象(Value Objects):不可变的属性容器
  • 领域事件(Domain Events):业务状态变化通知
  • 仓储(Repositories):数据访问抽象
  • 领域服务(Domain Services):不自然属于聚合的逻辑

实现步骤

1. 配置项目自动加载

修改 composer.json

"autoload": {
    "psr-4": {
        "App\\": "app/",
        "Domain\\": "src/Domain/",
        "Application\\": "src/Application/",
        "Infrastructure\\": "src/Infrastructure/",
        "UI\\": "src/UI/"
    }
}

运行 composer dump-autoload

2. 创建领域模型(聚合根)

// src/Domain/Models/Order.php
namespace Domain\Models;

use Domain\Events\OrderCreated;
use Domain\ValueObjects\OrderId;
use Domain\ValueObjects\CustomerId;
use Domain\ValueObjects\Money;
use Domain\Exceptions\DomainException;

class Order {
    private array $items = [];
    private \DateTimeImmutable $createdAt;
    
    public function __construct(
        private OrderId $id,
        private CustomerId $customerId,
        private OrderStatus $status = OrderStatus::PENDING
    ) {
        $this->createdAt = new \DateTimeImmutable();
    }
    
    public function addItem(ProductId $productId, int $quantity, Money $price): void {
        if ($quantity <= 0) {
            throw new DomainException('Quantity must be positive');
        }
        
        $this->items[] = new OrderItem(
            $productId,
            $quantity,
            $price
        );
    }
    
    public function confirm(): void {
        if (count($this->items) === 0) {
            throw new DomainException('Cannot confirm empty order');
        }
        
        $this->status = OrderStatus::CONFIRMED;
        $this->recordEvent(new OrderConfirmed($this->id));
    }
    
    // 获取聚合根ID
    public function getId(): OrderId {
        return $this->id;
    }
}

3. 创建值对象

// src/Domain/ValueObjects/Money.php
namespace Domain\ValueObjects;

class Money {
    public function __construct(
        private float $amount,
        private string $currency = 'USD'
    ) {
        if ($amount < 0) {
            throw new \InvalidArgumentException('Money amount cannot be negative');
        }
    }
    
    public function add(Money $other): Money {
        if ($this->currency !== $other->currency) {
            throw new \InvalidArgumentException('Currencies must match');
        }
        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 __toString(): string {
        return sprintf('%s %.2f', $this->currency, $this->amount);
    }
}

4. 定义仓储接口

// src/Domain/Repositories/OrderRepositoryInterface.php
namespace Domain\Repositories;

use Domain\Models\Order;
use Domain\ValueObjects\OrderId;

interface OrderRepositoryInterface {
    public function save(Order $order): void;
    public function findById(OrderId $id): ?Order;
    public function nextIdentity(): OrderId;
}

5. 实现基础设施仓储

// src/Infrastructure/Repositories/EloquentOrderRepository.php
namespace Infrastructure\Repositories;

use Domain\Models\Order;
use Domain\Repositories\OrderRepositoryInterface;
use Domain\ValueObjects\OrderId;
use App\Models\EloquentOrder as OrderModel;

class EloquentOrderRepository implements OrderRepositoryInterface {
    public function save(Order $order): void {
        OrderModel::updateOrCreate(
            ['uuid' => $order->getId()->toString()],
            $this->mapToEloquent($order)
        );
    }
    
    private function mapToEloquent(Order $order): array {
        return [
            'customer_id' => $order->getCustomerId()->toString(),
            'status' => $order->getStatus()->value,
            'items' => array_map(fn($item) => [
                'product_id' => $item->getProductId()->toString(),
                'quantity' => $item->getQuantity(),
                'price' => $item->getPrice()->getAmount(),
                'currency' => $item->getPrice()->getCurrency()
            ], $order->getItems())
        ];
    }
    
    public function findById(OrderId $id): ?Order {
        $model = OrderModel::where('uuid', $id->toString())->first();
        return $model ? $this->mapToDomain($model) : null;
    }
    
    public function nextIdentity(): OrderId {
        return OrderId::generate();
    }
}

6. 创建应用服务

// src/Application/Services/OrderService.php
namespace Application\Services;

use Domain\Models\Order;
use Domain\Repositories\OrderRepositoryInterface;
use Domain\ValueObjects\CustomerId;
use Application\DTOs\CreateOrderDTO;
use Illuminate\Events\Dispatcher;

class OrderService {
    public function __construct(
        private OrderRepositoryInterface $orderRepository,
        private Dispatcher $dispatcher
    ) {}
    
    public function createOrder(CreateOrderDTO $dto): Order {
        $order = new Order(
            $this->orderRepository->nextIdentity(),
            new CustomerId($dto->customerId)
        );
        
        foreach ($dto->items as $item) {
            $order->addItem(
                $item->productId,
                $item->quantity,
                new Money($item->price, $item->currency)
            );
        }
        
        $this->orderRepository->save($order);
        $this->dispatcher->dispatch(new OrderCreated($order->getId()));
        
        return $order;
    }
}

7. 创建控制器

// src/UI/Http/Controllers/OrderController.php
namespace UI\Http\Controllers;

use Application\Services\OrderService;
use UI\Http\Requests\CreateOrderRequest;
use UI\Http\Resources\OrderResource;
use Illuminate\Http\JsonResponse;

class OrderController extends Controller {
    public function __construct(private OrderService $orderService) {}
    
    public function store(CreateOrderRequest $request): JsonResponse {
        $order = $this->orderService->createOrder($request->toDTO());
        
        return response()->json([
            'data' => new OrderResource($order),
            'message' => 'Order created successfully'
        ], 201);
    }
}

8. 配置服务提供者

// src/Infrastructure/Providers/DomainServiceProvider.php
namespace Infrastructure\Providers;

use Illuminate\Support\ServiceProvider;
use Domain\Repositories\OrderRepositoryInterface;
use Infrastructure\Repositories\EloquentOrderRepository;

class DomainServiceProvider extends ServiceProvider {
    public function register() {
        $this->app->bind(
            OrderRepositoryInterface::class,
            EloquentOrderRepository::class
        );
    }
}

config/app.php 中注册提供者:

'providers' => [
    // ...
    Infrastructure\Providers\DomainServiceProvider::class,
],

领域事件处理

1. 定义领域事件

// src/Domain/Events/OrderCreated.php
namespace Domain\Events;

use Domain\ValueObjects\OrderId;

class OrderCreated {
    public function __construct(
        public readonly OrderId $orderId
    ) {}
}

2. 创建事件监听器

// src/Infrastructure/EventListeners/SendOrderConfirmation.php
namespace Infrastructure\EventListeners;

use Domain\Events\OrderCreated;
use App\Services\NotificationService;

class SendOrderConfirmation {
    public function __construct(private NotificationService $notifier) {}
    
    public function handle(OrderCreated $event): void {
        $this->notifier->sendOrderConfirmation($event->orderId);
    }
}

3. 注册事件监听

EventServiceProvider 中:

protected $listen = [
    OrderCreated::class => [
        SendOrderConfirmation::class,
        UpdateInventory::class
    ]
];

CQRS实现

命令端(写操作)

// src/Application/Commands/CancelOrderHandler.php
namespace Application\Commands;

use Domain\Repositories\OrderRepositoryInterface;
use Domain\ValueObjects\OrderId;

class CancelOrderHandler {
    public function __construct(private OrderRepositoryInterface $orderRepo) {}
    
    public function handle(CancelOrderCommand $command): void {
        $order = $this->orderRepo->findById(
            OrderId::fromString($command->orderId)
        );
        
        if (!$order) {
            throw new \Exception('Order not found');
        }
        
        $order->cancel();
        $this->orderRepo->save($order);
    }
}

查询端(读操作)

// src/Application/Queries/GetOrderStatusQuery.php
namespace Application\Queries;

use App\Models\OrderStatusViewModel;

class GetOrderStatusQuery {
    public function execute(string $orderId): ?OrderStatusViewModel {
        return OrderStatusViewModel::where('uuid', $orderId)->first();
    }
}

测试策略

单元测试(领域模型)

// tests/Unit/Domain/Models/OrderTest.php
use Domain\Models\Order;
use Domain\ValueObjects\OrderId;
use Domain\ValueObjects\CustomerId;
use Domain\ValueObjects\Money;
use Domain\ValueObjects\ProductId;
use Domain\Exceptions\DomainException;

test('order requires at least one item for confirmation', function () {
    $order = new Order(OrderId::generate(), new CustomerId('cust-123'));
    
    $this->expectException(DomainException::class);
    $order->confirm();
});

test('adding item with negative quantity throws exception', function () {
    $order = new Order(OrderId::generate(), new CustomerId('cust-123'));
    
    $this->expectException(DomainException::class);
    $order->addItem(
        new ProductId('prod-456'),
        -1,
        new Money(10.99)
    );
});

集成测试(应用服务)

// tests/Integration/Application/Services/OrderServiceTest.php
use Application\Services\OrderService;
use Domain\Repositories\InMemoryOrderRepository;
use Illuminate\Events\Dispatcher;
use Application\DTOs\CreateOrderDTO;

test('order service creates order with items', function () {
    $repo = new InMemoryOrderRepository();
    $service = new OrderService($repo, new Dispatcher());
    
    $dto = new CreateOrderDTO('cust-123', [
        ['product_id' => 'prod-1', 'quantity' => 2, 'price' => 9.99, 'currency' => 'USD'],
        ['product_id' => 'prod-2', 'quantity' => 1, 'price' => 19.99, 'currency' => 'USD']
    ]);
    
    $order = $service->createOrder($dto);
    
    expect($order->getId())->toBeInstanceOf(OrderId::class);
    expect($order->getItems())->toHaveCount(2);
});

性能优化策略

  1. 批量操作

    // 在仓储实现中
    public function saveMultiple(array $orders): void {
        $this->entityManager->transactional(function() use ($orders) {
            foreach ($orders as $order) {
                $this->save($order);
            }
        });
    }
    
  2. 缓存常见查询

    // 在查询处理中
    public function getOrderStatus(string $orderId): OrderStatusView {
        return Cache::remember("order_status_{$orderId}", 300, function() use ($orderId) {
            return $this->query->execute($orderId);
        });
    }
    
  3. 异步事件处理

    // 在事件服务提供者中
    protected $listen = [
        OrderCreated::class => [
            SendOrderConfirmation::class => ['queue' => 'notifications'],
            UpdateInventory::class => ['queue' => 'inventory']
        ]
    ];
    

常见问题解决

问题:如何处理复杂事务?

解决方案:使用工作单元模式

class UnitOfWork {
    private array $aggregates = [];
    
    public function register(AggregateRoot $aggregate): void {
        $this->aggregates[] = $aggregate;
    }
    
    public function commit(): void {
        foreach ($this->aggregates as $aggregate) {
            $this->repository->save($aggregate);
        }
        $this->aggregates = [];
    }
}

问题:如何避免贫血领域模型?

解决方案:

  1. 将所有业务规则封装在聚合根中
  2. 避免在应用服务中实现业务逻辑
  3. 使用领域事件通知状态变化

问题:如何管理聚合之间的依赖?

解决方案:

  1. 通过ID引用而不是直接对象引用
  2. 在应用服务中协调多个聚合
  3. 使用领域事件进行跨聚合通信

最佳实践总结

  1. 严格分层:确保领域层不依赖基础设施

  2. 聚合设计

    • 保持聚合小巧(最多5-10个实体)
    • 通过ID引用其他聚合
    • 封装不变条件
  3. 测试策略

    • 单元测试覆盖领域模型
    • 集成测试验证应用服务
    • E2E测试API端点
  4. 持续重构

    • 定期与领域专家沟通
    • 根据业务变化调整模型
    • 重构代码以反映领域语言
  5. 性能考虑

    • 读/写模型分离(CQRS)
    • 事件溯源处理复杂业务流
    • 异步处理非关键操作
  6. 团队协作

    • 使用通用语言(Ubiquitous Language)
    • 创建上下文映射图
    • 定期进行事件风暴会议

通过遵循这些实践,您可以在 Laravel 12 中成功实现领域驱动设计,创建出灵活、可维护且与业务需求紧密对齐的应用程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值