PHP项目领域服务与应用服务

wen PHP项目 2

本文目录导读:

PHP项目领域服务与应用服务

  1. 核心区别一览
  2. 领域服务(Domain Service)
  3. 应用服务(Application Service)
  4. 代码分层图(常见 PHP 项目结构)
  5. 何时使用领域服务 vs 应用服务?
  6. 常见误区

在 PHP 项目中,领域服务(Domain Service)应用服务(Application Service)领域驱动设计(DDD, Domain-Driven Design) 中的两个核心概念,它们虽然都叫“服务”,但职责、关注点和所在层级完全不同。

简单一句话总结:

领域服务主要负责“业务逻辑”,应用服务主要负责“流程编排”。

下面我们通过对比和代码示例来详细拆解。


核心区别一览

维度 领域服务 应用服务
核心职责 处理单个领域对象(实体/值对象)无法独立完成的复杂业务逻辑。 负责用例(Use Case)的流程编排,串联领域层、基础设施层(如数据库、缓存、消息队列)。
所属层级 领域层 应用层
依赖方向 只依赖领域对象(Entity, Value Object, Repository 接口) 依赖领域层、基础设施层接口(如 Repository 实现、通知服务)
业务复杂性 高,包含核心业务规则 低,主要是调度和协调
是否需要事务 通常不需要亲自管理事务 必须管理事务(通常一个用例一个事务)
典型术语 转账(transfer)计算折扣(calculateDiscount) 创建订单(CreateOrder)注册用户(RegisterUser)

领域服务(Domain Service)

场景: 当某个业务操作 不属于某一个实体的天然行为 时,就需要提炼出领域服务。
转账涉及 扣款方账户收款方账户 两个实体,不能把 transfer 方法写在某一个账户实体里,所以需要一个领域服务 TransferService

特点:

  • 无状态:不存储数据,只执行业务逻辑。
  • 依赖 Repository 接口:获取领域对象(不注入具体实现,依赖倒置)。
  • 返回领域对象或业务结果(如 TransferResult)。

PHP 示例:领域服务

<?php
namespace Domain\Service;
use Domain\Entity\Account;
use Domain\Repository\AccountRepositoryInterface;
use Domain\ValueObject\Money;
use Domain\Exception\InsufficientBalanceException;
class TransferService
{
    public function __construct(
        private AccountRepositoryInterface $accountRepository
    ) {}
    public function transfer(string $fromAccountId, string $toAccountId, Money $amount): void
    {
        // 1. 获取领域对象
        $fromAccount = $this->accountRepository->findById($fromAccountId);
        $toAccount   = $this->accountRepository->findById($toAccountId);
        // 2. 执行业务规则(核心逻辑)
        if (!$fromAccount->canWithdraw($amount)) {
            throw new InsufficientBalanceException('余额不足');
        }
        $fromAccount->withdraw($amount);
        $toAccount->deposit($amount);
        // 3. 保存状态(通过 Repository 接口,具体实现由基础设施层提供)
        $this->accountRepository->save($fromAccount);
        $this->accountRepository->save($toAccount);
    }
}

注意:这里 $this->accountRepository->save() 在领域服务中出现是有争议的,领域服务只应执行逻辑,由 应用服务 负责调用仓储保存,但在一些简化实践中,领域服务也会负责持久化,这取决于架构约定,通常我们更推荐“领域服务只负责业务计算,由应用服务协调持久化”。


应用服务(Application Service)

场景: 系统的一个外部用例。
用户点击“转账”按钮,前端调用 TransferApplicationService::transfer()

特点:

  • 负责事务:开启、提交、回滚。
  • 协调多个领域对象和服务:调用领域服务、仓储、MQ、事件总线等。
  • 处理 DTO 转换:将请求参数转换为领域对象,将领域结果转换为响应。
  • 不应包含业务逻辑:业务逻辑必须委托给领域层。

PHP 示例:应用服务

<?php
namespace Application\Service;
use Domain\Service\TransferService;
use Domain\Repository\AccountRepositoryInterface;
use Domain\ValueObject\Money;
use Infrastructure\Transaction\TransactionManager;
use Infrastructure\Notification\NotificationService;
class TransferApplicationService
{
    public function __construct(
        private TransferService $transferService,
        private AccountRepositoryInterface $accountRepository,
        private TransactionManager $transactionManager,
        private NotificationService $notificationService
    ) {}
    public function transfer(TransferRequest $request): TransferResponse
    {
        // 1. 开启事务
        $this->transactionManager->begin();
        try {
            // 2. 调用领域服务执行核心逻辑
            $this->transferService->transfer(
                $request->fromAccountId,
                $request->toAccountId,
                new Money($request->amount, $request->currency)
            );
            // 3. 事务提交
            $this->transactionManager->commit();
            // 4. 发送通知(非核心,属于应用层协调)
            $this->notificationService->sendTransferNotification($request->fromAccountId, $request->amount);
            return new TransferResponse(true, '转账成功');
        } catch (\Throwable $e) {
            // 5. 回滚事务
            $this->transactionManager->rollback();
            return new TransferResponse(false, $e->getMessage());
        }
    }
}

代码分层图(常见 PHP 项目结构)

src/
├── Domain/             (领域层)
│   ├── Entity/         (实体: Account, Order)
│   ├── ValueObject/    (值对象: Money, Email)
│   ├── Service/        (领域服务: TransferService)
│   ├── Repository/     (仓储接口: AccountRepositoryInterface)
│   └── Exception/      (领域异常: InsufficientBalanceException)
│
├── Application/        (应用层)
│   ├── Service/        (应用服务: TransferApplicationService)
│   └── DTO/            (数据传输对象: TransferRequest, TransferResponse)
│
├── Infrastructure/     (基础设施层)
│   ├── Repository/     (仓储实现: EloquentAccountRepository)
│   ├── Transaction/    (事务实现: DBTransactionManager)
│   └── Notification/   (通知实现: EmailNotificationService)
│
└── UI/                 (用户界面层, 如 Controller/Command)
    └── Http/
        └── Controllers/
            └── TransferController.php

何时使用领域服务 vs 应用服务?

情况 应该放在哪里?
操作涉及 多个实体 且逻辑复杂(如转账、下单) 领域服务
操作只涉及 单个实体的内部状态(如修改密码前的校验) 实体内部方法(不一定是服务)
需要 事务锁定发送事件/通知调用第三方 API 应用服务
业务规则会 频繁变化,且期望 复用 领域服务
纯粹是 数据组装和转换(如从多个表查询再组合) 应用服务(或简单直接写仓储)

常见误区

❌ 应用服务直接调用 Repository 实现业务逻辑

// 错误示范:应用服务里直接写业务规则
class OrderApplicationService {
    public function createOrder($data) {
        $user = $this->userRepo->find($data['userId']);
        // 这里直接检查用户是否有权限(业务逻辑)—— 应该交给领域层
        if ($user->getLevel() < 5) {
            throw new \Exception('权限不足');
        }
        // ...
    }
}

正确做法:权限检查 封装到领域服务或实体的方法中。

❌ 领域服务里管理事务

class TransferService {
    public function transfer(...) {
        DB::beginTransaction(); // 领域服务不应该知道事务
        // ...
    }
}

原因: 同一领域服务可能在不同用例中被调用,事务边界可能不同(有的用例需要事务,有的不需要)。


领域服务 应用服务
一句话 负责 “怎么做”(业务规则) 负责 “做什么”(流程调度)
维护成本 高(业务复杂) 低(代码简单但依赖多)
可测试性 很好(纯业务,可 mock Repository) 较好(需 mock 所有依赖)
是否被框架耦合 否(纯粹 PHP 对象) 否(但通常注入基础设施组件)

最佳实践:

  • 尽可能将业务规则下沉到 实体方法领域服务
  • 应用服务保持“薄薄一层”,只做编排,不做业务公式。

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