PHP 怎么领域服务

wen PHP项目 1

本文目录导读:

PHP 怎么领域服务

  1. 什么是领域服务?
  2. 代码示例(核心逻辑)
  3. 进阶:配合依赖注入(在 Laravel 中)
  4. 最佳实践(避坑指南)
  5. 什么时候不该用领域服务?
  6. 完整思维导图

在 PHP 中实现领域服务(Domain Service),核心在于将有业务逻辑但又不适合放在实体或值对象中的操作,提取为独立的服务类,下面我从概念到代码,给你一套清晰的实践思路。


什么是领域服务?

适用场景判断标准:

  1. 涉及多个实体/聚合 的协作(如转账涉及两个账户)
  2. 依赖外部基础设施 的业务规则(如检查邮箱是否唯一)
  3. 业务逻辑无法归属 到某个具体实体(如计算订单总价,但涉及多种折扣策略)

关键区别:实体负责自身状态和行为,领域服务负责跨实体的编排与业务规则。


代码示例(核心逻辑)

假设我们有一个转账场景:

实体(贫血模型,只存数据)

class BankAccount
{
    public function __construct(
        public string $id,
        public float $balance
    ) {}
    public function withdraw(float $amount): void
    {
        if ($this->balance < $amount) {
            throw new \DomainException('余额不足');
        }
        $this->balance -= $amount;
    }
    public function deposit(float $amount): void
    {
        $this->balance += $amount;
    }
}

领域服务(负责跨实体操作)

class TransferService
{
    public function __construct(
        private AccountRepository $accountRepository,
        private TransactionLogger $logger
    ) {}
    public function transfer(
        string $fromAccountId,
        string $toAccountId,
        float $amount
    ): void {
        // 1. 从仓储获取实体
        $from = $this->accountRepository->findById($fromAccountId);
        $to = $this->accountRepository->findById($toAccountId);
        if (!$from || !$to) {
            throw new \DomainException('账户不存在');
        }
        // 2. 执行业务规则(验资)
        $this->validateTransfer($from, $amount);
        // 3. 调用实体方法(不在服务里直接修改属性)
        $from->withdraw($amount);
        $to->deposit($amount);
        // 4. 持久化 + 记录日志
        $this->accountRepository->save($from);
        $this->accountRepository->save($to);
        $this->logger->log("转账: {$fromAccountId} -> {$toAccountId}, 金额: {$amount}");
    }
    private function validateTransfer(BankAccount $from, float $amount): void
    {
        if ($amount <= 0) {
            throw new \InvalidArgumentException('转账金额必须为正数');
        }
        if ($amount > 50000) {
            throw new \DomainException('单笔转账超过限额');
        }
        // 其他业务规则...
    }
}

重点:

  • 领域服务不直接操作 balance 属性,而是调用实体的 withdraw() / deposit() 方法。
  • 业务规则(限额、余额)既可以在服务中校验,也可以在实体方法内(余额不足放实体内部更合理,这里为了演示两种都写了)。

进阶:配合依赖注入(在 Laravel 中)

在 Laravel 等框架中,领域服务通常作为单例或普通服务被注册:

// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->bind(TransferService::class, function ($app) {
        return new TransferService(
            $app->make(AccountRepository::class),
            $app->make(TransactionLogger::class)
        );
    });
}

使用:

class TransferController extends Controller
{
    public function __invoke(TransferRequest $request, TransferService $service)
    {
        $service->transfer(
            $request->from_account_id,
            $request->to_account_id,
            $request->amount
        );
        return response()->json(['message' => '转账成功']);
    }
}

最佳实践(避坑指南)

要点 说明
命名 用动词(TransferService)或业务术语(OrderPricing
无状态 领域服务不要持有用户自定义状态(可以注入仓储,但不能保存业务数据)
只做编排 复杂的多步骤操作放在服务里,但单实体的状态变更必须通过实体方法
事务边界 服务方法通常就是事务边界,失败时回滚所有操作
接口抽象 依赖外部系统时,建议先定义接口,再实现具体(方便测试 mock)

什么时候不该用领域服务?

  • 单个实体自身的简单逻辑 → 放实体里
  • 纯粹的算法或工具函数(无业务上下文) → 放 Domain Service 或 Util
  • 只涉及数据存取 → 直接仓储

完整思维导图

领域服务 (Domain Service)
├── 职责
│   ├── 跨实体的业务操作(转账)
│   ├── 复杂业务规则(限额、税率计算)
│   └── 依赖外部服务的决策(汇率换算)
├── 特征
│   ├── 无状态(不保存业务数据)
│   ├── 注入仓储/接口
│   └── 方法级事务
└── 反模式
    ├── 服务包含基础设施代码(连接数据库)
    ├── 服务直接修改实体属性(绕过实体方法)
    └── 实体太瘦,所有逻辑都在服务(贫血模型过度)

抱歉,评论功能暂时关闭!