本文目录导读:

这是一个很有价值的问题,Laravel DDD(领域驱动设计)的落地实践,核心挑战在于如何将DDD的战略设计(限界上下文、通用语言)和战术设计(实体、值对象、领域服务等)与Laravel的MVC架构及Eloquent ORM的Active Record模式进行平衡和适配。
完全照搬Java/Spring的DDD写法,在Laravel中会非常别扭且低效,下面是一套经过验证、可落地的实践指南。
核心理念:分层与适配,而非对抗
不要试图完全摒弃Eloquent,Eloquent非常强大,我们要做的是在边界内部(领域层)使用纯净的PHP对象,在边界外部(基础设施层)利用Eloquent。
第一步:项目结构设计(关键中的关键)
推荐的目录结构是 Module-based(模块化),每个模块就是一个独立的限界上下文。
app/
├── Modules/
│ ├── Ordering/ # 订单上下文(BC)
│ │ ├── Domain/ # 领域层(核心,零依赖外部框架)
│ │ │ ├── Models/ # 实体(Entity)和 值对象(Value Object)
│ │ │ ├── Events/ # 领域事件
│ │ │ ├── Exceptions/
│ │ │ └── Services/ # 领域服务
│ │ ├── Application/ # 应用层(用例/服务)
│ │ │ ├── Commands/ # 命令/请求对象
│ │ │ ├── Handlers/ # 命令处理器
│ │ │ └── DTOs/ # 数据传输对象
│ │ ├── Infrastructure/ # 基础设施层
│ │ │ ├── Eloquent/ # Eloquent模型、Repository实现
│ │ │ ├── Providers/ # 服务提供者(绑定接口和实现)
│ │ │ ├── Listeners/ # 监听器(处理领域事件)
│ │ │ └── Database/ # 迁移、工厂、种子
│ │ └── Presentation/ # 表现层(或直接放在 Http/Controllers)
│ │ └── Http/
│ │ └── Controllers/
│ └── Shared/ # 共享内核
│ └── Domain/
│ └── ValueObjects/
├── Http/
└── ...
关键点:
- Domain 层是“孤岛”:不 import 任何 Laravel 类(如
Model,DB,Auth),只使用标准 PHP 对象和自定义接口。 - Infrastructure 层是“适配器”:负责实现 Domain 层的接口,这里可以尽情使用 Eloquent, Cache, Queue 等 Laravel 特性。
第二步:战术设计关键模式落地
实体(Entity)与值对象(Value Object)
不要用 Eloquent Model 充当领域实体。 创建一个纯净的 Plain PHP Class。
// Modules/Ordering/Domain/Models/Order.php (Domain Entity)
namespace Modules\Ordering\Domain\Models;
use Modules\Ordering\Domain\ValueObjects\OrderId;
use Modules\Ordering\Domain\ValueObjects\Money;
use Modules\Ordering\Domain\Events\OrderPlaced;
class Order
{
public function __construct(
private OrderId $orderId,
private Money $totalAmount,
private string $customerId,
private array $lineItems = []
) {
// 构造函数中执行创建时的业务规则
if ($totalAmount->isLessThan(Money::fromFloat(0.01))) {
throw new \InvalidArgumentException("Order total must be positive");
}
}
public function place(): void
{
// ... 业务逻辑 ...
// 触发领域事件(通过EventDispatcher或简单的事件集合)
// $this->events[] = new OrderPlaced($this->orderId);
}
// Getters 暴露必要信息,但避免暴露修改内部状态的方法
public function getOrderId(): OrderId { return $this->orderId; }
public function getTotalAmount(): Money { return $this->totalAmount; }
}
// Modules/Ordering/Domain/ValueObjects/Money.php (Value Object)
namespace Modules\Ordering\Domain\ValueObjects;
class Money
{
public function __construct(
private int $amount, // 以分为单位,避免浮点数
private string $currency = 'CNY'
) {}
public static function fromFloat(float $amount, string $currency = 'CNY'): self
{
return new self((int) round($amount * 100), $currency);
}
public function add(self $other): self
{
if ($this->currency !== $other->currency) {
throw new \InvalidArgumentException('Currency mismatch');
}
return new self($this->amount + $other->amount, $this->currency);
}
// ... equals(), isLessThan() 等方法
}
资源库(Repository)
Repository 是通往数据库的唯一桥梁。 Domain 层定义接口,Infrastructure 层用 Eloquent 实现。
// 1. Domain 层定义接口
// Modules/Ordering/Domain/Repositories/OrderRepositoryInterface.php
namespace Modules\Ordering\Domain\Repositories;
use Modules\Ordering\Domain\Models\Order;
use Modules\Ordering\Domain\ValueObjects\OrderId;
interface OrderRepositoryInterface
{
public function save(Order $order): void;
public function findById(OrderId $orderId): ?Order;
public function findByCustomerId(string $customerId): array;
}
// 2. Infrastructure 层实现
// Modules/Ordering/Infrastructure/Eloquent/EloquentOrderRepository.php
namespace Modules\Ordering\Infrastructure\Eloqeunt;
use Modules\Ordering\Domain\Models\Order;
use Modules\Ordering\Domain\Repositories\OrderRepositoryInterface;
use Modules\Ordering\Infrastructure\Eloqeunt\Models\Order as EloquentOrderModel; // 注意命名冲突
use Modules\Ordering\Infrastructure\Factories\OrderFactory; // 用于将Eloquent模型转换为领域实体
class EloquentOrderRepository implements OrderRepositoryInterface
{
public function save(Order $order): void
{
// 将领域实体转换为Eloquent数据
$eloquentModel = EloquentOrderModel::findOrNew($order->getOrderId()->getId());
$eloquentModel->customer_id = $order->getCustomerId();
$eloquentModel->total_amount = $order->getTotalAmount()->getAmount(); // 存分
// ... 保存关联商品
$eloquentModel->save();
}
public function findById(OrderId $orderId): ?Order
{
$eloquentModel = EloquentOrderModel::with('items')->find($orderId->getId());
if (!$eloquentModel) {
return null;
}
// 将Eloquent模型转换回领域实体
return OrderFactory::fromEloquent($eloquentModel);
}
// ...
}
// 3. 在 ServiceProvider 中绑定
// Modules/Ordering/Infrastructure/Providers/OrderingServiceProvider.php
class OrderingServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(
\Modules\Ordering\Domain\Repositories\OrderRepositoryInterface::class,
\Modules\Ordering\Infrastructure\Eloqeunt\EloquentOrderRepository::class
);
}
}
应用服务(Application Service)与命令(Command)
Controller 不应该直接调用 Repository,而是通过 Application Service。
// Modules/Ordering/Application/Commands/PlaceOrderCommand.php
namespace Modules\Ordering\Application\Commands;
class PlaceOrderCommand
{
public function __construct(
public readonly string $customerId,
public readonly array $items // [{productId, quantity, price}]
) {}
}
// Modules/Ordering/Application/Handlers/PlaceOrderHandler.php
namespace Modules\Ordering\Application\Handlers;
use Modules\Ordering\Domain\Repositories\OrderRepositoryInterface;
use Modules\Ordering\Domain\Models\Order;
// ... 其他依赖
class PlaceOrderHandler
{
public function __construct(
private OrderRepositoryInterface $orderRepository,
private ProductRepositoryInterface $productRepository
) {}
public function handle(PlaceOrderCommand $command): void
{
// 1. 从数据库查询(通过Repository)
$customer = $this->customerRepository->findById($command->customerId);
// 2. 构建领域实体(这里是核心业务逻辑所在)
$order = new Order(
new OrderId(Uuid::generate()),
// ... 从command和商品信息构建lineItems并计算总价
);
// 3. 执行业务方法
$order->place(); // 可能会抛出异常或触发领域事件
// 4. 持久化
$this->orderRepository->save($order);
}
}
领域事件(Domain Events)
使用 Laravel 的 Event 机制来分发领域事件,但事件类定义在 Domain 层。
// Modules/Ordering/Domain/Events/OrderPlaced.php
namespace Modules\Ordering\Domain\Events;
use Modules\Ordering\Domain\ValueObjects\OrderId;
class OrderPlaced
{
public function __construct(
public OrderId $orderId,
public string $customerId
) {}
}
// 在 Application/Infrastructure 层监听
// Modules/Ordering/Infrastructure/Listeners/SendOrderConfirmation.php
class SendOrderConfirmation
{
public function handle(OrderPlaced $event): void
{
// 使用 Laravel Mail 发送邮件
}
}
第三步:实际落地中的坑与最佳实践
-
不要一开始就所有模块都DDD,选择核心业务逻辑复杂、规则频繁变化的模块(如订单、库存、支付)先落地,普通CRUD模块(如文章管理、配置管理)依然用Laravel传统方式。
-
聚合(Aggregate)是重要概念但容易用错,在Laravel中,可以简单理解为:一个聚合根就是一个Repository的入口点。
Order(订单)是聚合根,OrderLineItem(订单行)是内部实体,你只能通过Order来操作OrderLineItem,不能有OrderLineItemRepository,这有助于维护数据一致性。 -
Eloquent 模型放置的位置:放在
Infrastructure/Eloquent/Models/下,而不是app/Models/,它们只负责数据映射和数据访问,不包含业务规则。 -
处理事务:事务边界通常在应用服务层,可以使用
DB::transaction()包裹$handler->handle($command)。 -
性能优化:如果领域实体构建开销过大(比如加载大量关联数据),可以考虑使用 CQRS(命令查询职责分离):
- Command 端:完整DDD流程,保证数据一致性。
- Query 端:直接使用 Eloquent/DB Facade 进行查询,返回 DTO(数据传输对象)或数组,跳过领域实体,追求极致读取性能,这是 Laravel DDD 中最常见也最有效的优化。
-
使用 Laravel 自带 IoC 容器:Service Provider 是绑定接口和实现的绝佳场所,每个 Module 注册自己的 Provider。
Laravel DDD 落地路径
- 模块化:按业务切分目录。
- 纯净领域:Domain 层只定义接口、实体、值对象和领域事件。
- 适配器模式:Infrastructure 层用 Eloquent 实现 Repository 接口,用 Laravel Mail/Queue 实现领域事件监听。
- 应用层调度:Controller -> Application Service (Command/Handler) -> Domain (Entity/Service) -> Infrastructure (Repository)。
- 关键取舍:聚合根、跨模块通信、性能优化(CQRS)。
DDD 是一个持续演进的过程,从一个小模块开始,让代码自然生长出清晰的边界,比一次性设计出完美的架构更重要。