本文目录导读:

在 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 对象) | 否(但通常注入基础设施组件) |
最佳实践:
- 尽可能将业务规则下沉到 实体方法 和 领域服务。
- 应用服务保持“薄薄一层”,只做编排,不做业务公式。