微服务架构与分布式事务
基于 Hyperf 的微服务架构设计和分布式事务解决方案
1. 微服务架构概述
1.1 微服务特点
- 服务独立部署和扩展
- 数据库独立
- 技术栈可以不同
- 通过 HTTP/gRPC 通信
1.2 Hyperf 微服务组件
- hyperf/json-rpc:JSON-RPC 服务
- hyperf/grpc:gRPC 服务
- hyperf/consul:服务注册与发现
- hyperf/config-center:配置中心
- hyperf/circuit-breaker:熔断器
- hyperf/rate-limit:限流器
2. 分布式事务问题
2.1 为什么需要分布式事务?
php
// 场景:电商下单
// 1. 订单服务:创建订单
// 2. 库存服务:扣减库存
// 3. 支付服务:创建支付单
// 问题:如果库存扣减成功,但创建支付单失败,如何回滚?
// 传统事务只能保证单数据库的 ACID
// 跨服务、跨数据库需要分布式事务
2.2 CAP 定理
diff
C(一致性):所有节点看到相同的数据
A(可用性):系统持续可用
P(分区容错):网络分区时系统继续工作
定理:只能同时满足两个
- CP:强一致性,牺牲可用性(如 ZooKeeper)
- AP:高可用,牺牲一致性(如 Cassandra)
- CA:不现实(分布式系统必然有网络分区)
2.3 BASE 理论
diff
BA(Basically Available):基本可用
S(Soft State):软状态
E(Eventually Consistent):最终一致性
适用于:
- 对一致性要求不严格的场景
- 可以接受短暂不一致
- 大多数互联网业务
3. 分布式事务解决方案
3.1 方案对比
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 2PC | 强一致 | 低 | 低 | 不推荐 |
| 3PC | 强一致 | 低 | 中 | 不推荐 |
| TCC | 最终一致 | 高 | 高 | 金融场景 |
| Saga | 最终一致 | 高 | 中 | 长事务 |
| 本地消息表 | 最终一致 | 高 | 低 | 推荐 |
| MQ 事务消息 | 最终一致 | 高 | 低 | 推荐 |
4. DTM 分布式事务管理器
4.1 DTM 简介
DTM 是一款开源的分布式事务管理器,支持多种事务模式。
官方资源:
- 文档:dtm.pub/
- GitHub:github.com/dtm-labs/dt...
- PHP 客户端:github.com/dtm-php/dtm...
特点:
- 支持多种事务模式(Saga、TCC、XA、二阶段消息)
- 高性能(QPS 10万+)
- 跨语言支持
- 易于接入
4.2 安装 DTM 服务端
bash
# Docker 方式
docker run -d --name dtm -p 36789:36789 -p 36790:36790 \
dtm/dtm:latest
# 或下载二进制
wget https://github.com/dtm-labs/dtm/releases/download/v1.17.0/dtm_1.17.0_linux_amd64.tar.gz
tar -xzf dtm_1.17.0_linux_amd64.tar.gz
./dtm
4.3 安装 Hyperf DTM 客户端
bash
composer require dtm/dtm-client
配置:
php
// config/autoload/dtm.php
return [
'protocol' => 'http',
'server' => '127.0.0.1',
'port' => ['http' => 36789, 'grpc' => 36790],
'barrier' => [
'db' => [
'type' => 'mysql',
],
'redis' => [
],
'apply' => [], // 全局中间件
],
'guzzle' => [
'options' => [],
],
];
5. Saga 模式
5.1 Saga 原理
diff
Saga 将长事务拆分为多个本地短事务:
- 每个短事务都有对应的补偿操作
- 正常流程:T1 → T2 → T3 → ... → Tn
- 失败回滚:Cn → Cn-1 → ... → C2 → C1
优点:
- 无需锁,性能高
- 支持长事务
- 实现简单
缺点:
- 隔离性较弱
- 需要设计补偿操作
5.2 Hyperf Saga 示例
php
use DtmClient\Saga;
use DtmClient\TransContext;
class OrderService
{
/**
* 创建订单(Saga 模式)
*/
public function createOrder($userId, $productId, $quantity)
{
$saga = new Saga();
// 设置 DTM 服务器地址
$saga->init();
// 业务参数
$payload = [
'user_id' => $userId,
'product_id' => $productId,
'quantity' => $quantity,
];
// 1. 创建订单
$saga->add(
$this->getServiceUrl('/order/create'), // 正向操作
$this->getServiceUrl('/order/compensate'), // 补偿操作
$payload
);
// 2. 扣减库存
$saga->add(
$this->getServiceUrl('/inventory/reduce'),
$this->getServiceUrl('/inventory/compensate'),
$payload
);
// 3. 创建支付单
$saga->add(
$this->getServiceUrl('/payment/create'),
$this->getServiceUrl('/payment/compensate'),
$payload
);
// 提交 Saga 事务
$saga->submit();
return ['success' => true];
}
private function getServiceUrl($path)
{
return 'http://localhost:9501' . $path;
}
}
5.3 实现正向和补偿操作
php
class OrderController
{
/**
* 正向操作:创建订单
*/
#[PostMapping('/order/create')]
public function create(RequestInterface $request)
{
$payload = $request->getParsedBody();
// 使用 Barrier 防止重复提交和空补偿
$barrier = new Barrier($request->getQueryParams());
$barrier->call(function () use ($payload) {
// 创建订单
$order = Order::create([
'user_id' => $payload['user_id'],
'product_id' => $payload['product_id'],
'quantity' => $payload['quantity'],
'status' => 'pending',
]);
return $order->id;
});
return ['dtm_result' => 'SUCCESS'];
}
/**
* 补偿操作:取消订单
*/
#[PostMapping('/order/compensate')]
public function compensate(RequestInterface $request)
{
$payload = $request->getParsedBody();
$barrier = new Barrier($request->getQueryParams());
$barrier->call(function () use ($payload) {
// 更新订单状态为已取消
Order::where('user_id', $payload['user_id'])
->where('product_id', $payload['product_id'])
->where('status', 'pending')
->update(['status' => 'cancelled']);
});
return ['dtm_result' => 'SUCCESS'];
}
}
class InventoryController
{
/**
* 正向操作:扣减库存
*/
#[PostMapping('/inventory/reduce')]
public function reduce(RequestInterface $request)
{
$payload = $request->getParsedBody();
$barrier = new Barrier($request->getQueryParams());
$barrier->call(function () use ($payload) {
$product = Product::find($payload['product_id']);
if ($product->stock < $payload['quantity']) {
// 库存不足,返回失败
throw new \Exception('Insufficient stock');
}
// 扣减库存
$product->stock -= $payload['quantity'];
$product->save();
});
return ['dtm_result' => 'SUCCESS'];
}
/**
* 补偿操作:恢复库存
*/
#[PostMapping('/inventory/compensate')]
public function compensate(RequestInterface $request)
{
$payload = $request->getParsedBody();
$barrier = new Barrier($request->getQueryParams());
$barrier->call(function () use ($payload) {
// 恢复库存
$product = Product::find($payload['product_id']);
$product->stock += $payload['quantity'];
$product->save();
});
return ['dtm_result' => 'SUCCESS'];
}
}
6. TCC 模式
6.1 TCC 原理
markdown
TCC(Try-Confirm-Cancel)分三个阶段:
1. Try 阶段:
- 尝试执行业务
- 完成资源检查和预留
2. Confirm 阶段:
- 确认执行业务
- 真正执行业务
3. Cancel 阶段:
- 取消执行业务
- 释放预留资源
特点:
- 强隔离性
- 性能好
- 实现复杂(需要预留资源)
6.2 Hyperf TCC 示例
php
use DtmClient\Tcc;
class AccountService
{
/**
* 转账(TCC 模式)
*/
public function transfer($fromUserId, $toUserId, $amount)
{
$tcc = new Tcc();
$tcc->init();
$payload = [
'from_user_id' => $fromUserId,
'to_user_id' => $toUserId,
'amount' => $amount,
];
// 分支事务 1:扣款
$tcc->callBranch(
$payload,
$this->getServiceUrl('/account/try-deduct'),
$this->getServiceUrl('/account/confirm-deduct'),
$this->getServiceUrl('/account/cancel-deduct')
);
// 分支事务 2:入账
$tcc->callBranch(
$payload,
$this->getServiceUrl('/account/try-add'),
$this->getServiceUrl('/account/confirm-add'),
$this->getServiceUrl('/account/cancel-add')
);
// 提交 TCC 事务
$tcc->submit();
return ['success' => true];
}
}
class AccountController
{
/**
* Try:尝试扣款(冻结金额)
*/
#[PostMapping('/account/try-deduct')]
public function tryDeduct(RequestInterface $request)
{
$payload = $request->getParsedBody();
$barrier = new Barrier($request->getQueryParams());
$barrier->call(function () use ($payload) {
$account = Account::where('user_id', $payload['from_user_id'])->first();
if ($account->balance < $payload['amount']) {
throw new \Exception('Insufficient balance');
}
// 冻结金额
$account->balance -= $payload['amount'];
$account->frozen += $payload['amount'];
$account->save();
});
return ['dtm_result' => 'SUCCESS'];
}
/**
* Confirm:确认扣款
*/
#[PostMapping('/account/confirm-deduct')]
public function confirmDeduct(RequestInterface $request)
{
$payload = $request->getParsedBody();
$barrier = new Barrier($request->getQueryParams());
$barrier->call(function () use ($payload) {
// 扣减冻结金额
$account = Account::where('user_id', $payload['from_user_id'])->first();
$account->frozen -= $payload['amount'];
$account->save();
});
return ['dtm_result' => 'SUCCESS'];
}
/**
* Cancel:取消扣款(解冻金额)
*/
#[PostMapping('/account/cancel-deduct')]
public function cancelDeduct(RequestInterface $request)
{
$payload = $request->getParsedBody();
$barrier = new Barrier($request->getQueryParams());
$barrier->call(function () use ($payload) {
// 解冻金额
$account = Account::where('user_id', $payload['from_user_id'])->first();
$account->balance += $payload['amount'];
$account->frozen -= $payload['amount'];
$account->save();
});
return ['dtm_result' => 'SUCCESS'];
}
}
7. 本地消息表模式
7.1 原理
markdown
本地消息表是最简单可靠的分布式事务方案:
1. 在本地事务中:
- 执行业务操作
- 插入消息到本地消息表
2. 定时任务:
- 扫描未发送的消息
- 发送到消息队列
3. 消费者:
- 消费消息
- 执行后续操作
优点:
- 实现简单
- 性能好
- 最终一致性
缺点:
- 需要定时任务
- 消息表需要维护
7.2 实现示例
php
// 1. 创建消息表
CREATE TABLE `outbox_messages` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`topic` varchar(100) NOT NULL COMMENT '主题',
`payload` text NOT NULL COMMENT '消息内容',
`status` varchar(20) NOT NULL DEFAULT 'pending' COMMENT '状态:pending, sent, failed',
`retry_count` int NOT NULL DEFAULT 0 COMMENT '重试次数',
`created_at` datetime NOT NULL,
`sent_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_status_created` (`status`, `created_at`)
);
// 2. 在事务中创建订单和消息
class OrderService
{
public function createOrder($data)
{
Db::transaction(function () use ($data) {
// 创建订单
$order = Order::create($data);
// 插入消息到本地消息表
OutboxMessage::create([
'topic' => 'order.created',
'payload' => json_encode([
'order_id' => $order->id,
'user_id' => $order->user_id,
'amount' => $order->amount,
]),
'status' => 'pending',
]);
});
}
}
// 3. 定时任务发送消息
#[Crontab(rule: "*/5 * * * * *", name: "send-outbox-messages")]
class SendOutboxMessagesJob
{
#[Inject]
protected ProducerInterface $producer;
public function execute()
{
// 查询未发送的消息
$messages = OutboxMessage::where('status', 'pending')
->where('retry_count', '<', 3)
->limit(100)
->get();
foreach ($messages as $message) {
try {
// 发送到 Kafka/RabbitMQ
$this->producer->produce(
new ProducerMessage($message->topic, $message->payload)
);
// 更新状态
$message->status = 'sent';
$message->sent_at = date('Y-m-d H:i:s');
$message->save();
} catch (\Exception $e) {
// 重试
$message->retry_count++;
if ($message->retry_count >= 3) {
$message->status = 'failed';
}
$message->save();
}
}
}
}
// 4. 消费者处理消息
#[Consumer(topic: "order.created", nums: 1)]
class OrderCreatedConsumer extends AbstractConsumer
{
public function consume($data): string
{
$payload = json_decode($data->payload, true);
// 执行后续操作(扣减库存、发送通知等)
$this->inventoryService->reduce($payload['product_id'], $payload['quantity']);
$this->notificationService->sendOrderNotification($payload['order_id']);
return Result::ACK;
}
}
8. MQ 事务消息
8.1 RabbitMQ 事务消息
php
use Hyperf\Amqp\Producer;
use Hyperf\Amqp\Message\ProducerMessage;
class OrderService
{
#[Inject]
protected Producer $producer;
public function createOrder($data)
{
Db::transaction(function () use ($data) {
// 1. 创建订单
$order = Order::create($data);
// 2. 发送事务消息
$message = new OrderCreatedMessage([
'order_id' => $order->id,
'user_id' => $order->user_id,
]);
// RabbitMQ 事务模式
$this->producer->beginTransaction();
try {
$this->producer->produce($message);
$this->producer->commitTransaction();
} catch (\Exception $e) {
$this->producer->rollbackTransaction();
throw $e;
}
});
}
}
8.2 RocketMQ 事务消息
php
// RocketMQ 原生支持事务消息
// 流程:
// 1. 发送半消息(Half Message)
// 2. 执行本地事务
// 3. 提交/回滚事务消息
// 4. 如果超时未确认,RocketMQ 回查本地事务状态
class OrderService
{
public function createOrder($data)
{
// 发送事务消息
$this->rocketmq->sendMessageInTransaction('order.created', $data, function ($data) {
// 执行本地事务
Db::transaction(function () use ($data) {
Order::create($data);
});
// 返回本地事务状态
return TransactionStatus::COMMIT;
});
}
// 回查接口
public function checkTransactionStatus($transactionId)
{
// 查询本地事务是否成功
$order = Order::where('transaction_id', $transactionId)->first();
if ($order) {
return TransactionStatus::COMMIT;
} else {
return TransactionStatus::ROLLBACK;
}
}
}
9. 分布式事务最佳实践
9.1 选择合适的方案
diff
场景 1:订单系统
- 推荐:Saga / 本地消息表
- 原因:可接受最终一致性,步骤较多
场景 2:支付系统
- 推荐:TCC
- 原因:需要强一致性,资金敏感
场景 3:库存扣减
- 推荐:TCC / 本地消息表
- 原因:需要预留资源或异步扣减
场景 4:日志记录
- 推荐:MQ 异步
- 原因:允许丢失,性能优先
9.2 幂等性设计
php
// 使用唯一键防止重复提交
class PaymentService
{
public function createPayment($orderId, $amount)
{
// 使用订单 ID 作为唯一键
$payment = Payment::firstOrCreate(
['order_id' => $orderId], // 唯一键
['amount' => $amount, 'status' => 'pending']
);
return $payment;
}
}
// 使用 Token 防止重复提交
class TokenService
{
public function generateToken()
{
$token = uniqid('token_', true);
Redis::setex("token:{$token}", 300, 1); // 5 分钟有效
return $token;
}
public function validateToken($token)
{
return Redis::del("token:{$token}") > 0;
}
}
9.3 超时处理
php
// 设置合理的超时时间
$saga = new Saga();
$saga->setTimeout(30); // 30 秒超时
// 超时后自动回滚
9.4 监控和告警
php
// 记录分布式事务日志
class TransactionLogger
{
public function log($type, $status, $data)
{
DistributedTransactionLog::create([
'type' => $type, // saga, tcc, local_message
'status' => $status, // success, failed, timeout
'data' => json_encode($data),
'created_at' => date('Y-m-d H:i:s'),
]);
}
}
// 监控失败率
// 如果失败率超过阈值,触发告警
10. 高频问题
Q1: 什么是分布式事务?为什么需要?
答案: 分布式事务是跨多个服务、多个数据库的事务。需要分布式事务的原因:
- 微服务架构下,每个服务有独立数据库
- 本地事务只能保证单数据库 ACID
- 跨服务调用需要保证数据一致性
Q2: 常见的分布式事务解决方案有哪些?
答案:
- Saga:最终一致性,适合长事务
- TCC:强一致性,需要预留资源
- 本地消息表:最终一致性,实现简单
- MQ 事务消息:最终一致性,依赖 MQ
Q3: 如何选择分布式事务方案?
答案:
- 强一致性需求:TCC
- 可接受最终一致性:Saga、本地消息表
- 长事务、步骤多:Saga
- 简单场景:本地消息表
- 异步场景:MQ 事务消息
Q4: 如何保证幂等性?
答案:
- 唯一键约束(数据库)
- Token 机制(Redis)
- 状态机(检查状态)
- 分布式锁
Q5: DTM 相比其他方案的优势?
答案:
- 支持多种事务模式(Saga、TCC、XA、二阶段消息)
- 跨语言支持
- 高性能(10万+ QPS)
- 易于接入
- 开源免费
11. DDD(领域驱动设计)
11.1 DDD 核心概念
什么是 DDD?
diff
DDD (Domain-Driven Design) 领域驱动设计:
- 由 Eric Evans 提出的软件设计方法论
- 核心:将业务领域模型放在软件设计的中心
- 目标:应对复杂业务系统的开发和维护
优势:
✅ 统一业务和技术语言(通用语言)
✅ 清晰的分层架构
✅ 高内聚、低耦合
✅ 易于测试和维护
✅ 适合微服务拆分
DDD 分层架构
scss
┌─────────────────────────────────┐
│ 用户接口层 (Interface) │ ← Controller、HTTP、RPC
│ - 接收请求 │
│ - 参数验证 │
│ - 返回响应 │
├─────────────────────────────────┤
│ 应用层 (Application) │ ← Service、UseCase
│ - 业务流程编排 │
│ - 事务控制 │
│ - 权限校验 │
├─────────────────────────────────┤
│ 领域层 (Domain) │ ← Entity、ValueObject、DomainService
│ - 业务逻辑(核心) │
│ - 领域模型 │
│ - 领域服务 │
├─────────────────────────────────┤
│ 基础设施层 (Infrastructure) │ ← Repository、Cache、MQ
│ - 数据持久化 │
│ - 外部服务调用 │
│ - 技术实现 │
└─────────────────────────────────┘
11.2 DDD 核心概念详解
实体(Entity)
实体:有唯一标识的对象,生命周期中状态可变
特征:
✅ 有唯一ID
✅ 状态可变
✅ 有生命周期
✅ 包含业务逻辑
php
<?php
namespace App\Domain\Order\Entity;
// 订单实体
class Order
{
private OrderId $orderId; // 唯一标识
private UserId $userId;
private OrderStatus $status;
private Money $totalAmount;
private array $items = [];
private \DateTimeImmutable $createdAt;
// 构造函数:创建订单
private function __construct(
OrderId $orderId,
UserId $userId,
array $items
) {
$this->orderId = $orderId;
$this->userId = $userId;
$this->status = OrderStatus::pending();
$this->items = $items;
$this->totalAmount = $this->calculateTotal();
$this->createdAt = new \DateTimeImmutable();
}
// 工厂方法:创建新订单
public static function create(UserId $userId, array $items): self
{
if (empty($items)) {
throw new \DomainException('订单至少包含一个商品');
}
return new self(
OrderId::generate(),
$userId,
$items
);
}
// 业务方法:支付订单
public function pay(): void
{
if (!$this->status->isPending()) {
throw new \DomainException('只有待支付订单才能支付');
}
$this->status = OrderStatus::paid();
// 触发领域事件
$this->recordEvent(new OrderPaidEvent($this->orderId));
}
// 业务方法:取消订单
public function cancel(): void
{
if ($this->status->isCompleted()) {
throw new \DomainException('已完成订单不能取消');
}
$this->status = OrderStatus::cancelled();
$this->recordEvent(new OrderCancelledEvent($this->orderId));
}
// 业务逻辑:计算总金额
private function calculateTotal(): Money
{
$total = Money::zero();
foreach ($this->items as $item) {
$total = $total->add($item->getSubtotal());
}
return $total;
}
// Getter 方法
public function getOrderId(): OrderId
{
return $this->orderId;
}
public function getStatus(): OrderStatus
{
return $this->status;
}
}
值对象(Value Object)
值对象:没有唯一标识,完全由属性值决定相等性
特征:
✅ 无唯一ID
✅ 不可变(Immutable)
✅ 通过值比较相等性
✅ 可复用
php
<?php
namespace App\Domain\Shared\ValueObject;
// 金额值对象
final class Money
{
private float $amount;
private string $currency;
private function __construct(float $amount, string $currency = 'USD')
{
if ($amount < 0) {
throw new \InvalidArgumentException('金额不能为负数');
}
$this->amount = $amount;
$this->currency = $currency;
}
public static function fromAmount(float $amount, string $currency = 'USD'): self
{
return new self($amount, $currency);
}
public static function zero(): self
{
return new self(0);
}
// 加法(返回新对象,保持不可变性)
public function add(Money $other): self
{
$this->assertSameCurrency($other);
return new self($this->amount + $other->amount, $this->currency);
}
// 减法
public function subtract(Money $other): self
{
$this->assertSameCurrency($other);
return new self($this->amount - $other->amount, $this->currency);
}
// 乘法
public function multiply(float $multiplier): self
{
return new self($this->amount * $multiplier, $this->currency);
}
// 比较
public function equals(Money $other): bool
{
return $this->amount === $other->amount
&& $this->currency === $other->currency;
}
public function greaterThan(Money $other): bool
{
$this->assertSameCurrency($other);
return $this->amount > $other->amount;
}
private function assertSameCurrency(Money $other): void
{
if ($this->currency !== $other->currency) {
throw new \InvalidArgumentException('货币类型不匹配');
}
}
public function getAmount(): float
{
return $this->amount;
}
}
// 订单状态值对象
final class OrderStatus
{
private const PENDING = 'pending';
private const PAID = 'paid';
private const SHIPPED = 'shipped';
private const COMPLETED = 'completed';
private const CANCELLED = 'cancelled';
private string $value;
private function __construct(string $value)
{
$this->value = $value;
}
public static function pending(): self
{
return new self(self::PENDING);
}
public static function paid(): self
{
return new self(self::PAID);
}
public static function shipped(): self
{
return new self(self::SHIPPED);
}
public static function completed(): self
{
return new self(self::COMPLETED);
}
public static function cancelled(): self
{
return new self(self::CANCELLED);
}
public function isPending(): bool
{
return $this->value === self::PENDING;
}
public function isPaid(): bool
{
return $this->value === self::PAID;
}
public function isCompleted(): bool
{
return $this->value === self::COMPLETED;
}
public function equals(OrderStatus $other): bool
{
return $this->value === $other->value;
}
public function getValue(): string
{
return $this->value;
}
}
聚合根(Aggregate Root)
聚合根:一组相关对象的集合,对外只暴露聚合根
特征:
✅ 保证聚合内的一致性
✅ 对外提供统一接口
✅ 控制对聚合内对象的访问
✅ 只有聚合根可以被外部引用
php
<?php
namespace App\Domain\Order\Aggregate;
// 订单聚合根
class Order
{
private OrderId $orderId;
private UserId $userId;
private OrderStatus $status;
private Money $totalAmount;
private ShippingAddress $shippingAddress;
// 订单项(聚合内对象)
private array $items = [];
// 只有通过聚合根才能修改订单项
public function addItem(ProductId $productId, int quantity, Money $price): void
{
// 业务规则:已支付订单不能添加商品
if ($this->status->isPaid()) {
throw new \DomainException('已支付订单不能添加商品');
}
// 业务规则:最多10个商品
if (count($this->items) >= 10) {
throw new \DomainException('订单最多包含10个商品');
}
// 检查是否已存在相同商品
foreach ($this->items as $item) {
if ($item->getProductId()->equals($productId)) {
$item->increaseQuantity($quantity);
$this->recalculateTotal();
return;
}
}
// 添加新商品
$this->items[] = OrderItem::create(
OrderItemId::generate(),
$productId,
$quantity,
$price
);
$this->recalculateTotal();
}
// 移除订单项
public function removeItem(OrderItemId $itemId): void
{
if ($this->status->isPaid()) {
throw new \DomainException('已支付订单不能移除商品');
}
foreach ($this->items as $key => $item) {
if ($item->getItemId()->equals($itemId)) {
unset($this->items[$key]);
$this->recalculateTotal();
return;
}
}
throw new \DomainException('商品不存在');
}
// 更新收货地址
public function updateShippingAddress(ShippingAddress $address): void
{
if ($this->status->isShipped()) {
throw new \DomainException('已发货订单不能修改地址');
}
$this->shippingAddress = $address;
}
// 重新计算总金额(保证聚合一致性)
private function recalculateTotal(): void
{
$total = Money::zero();
foreach ($this->items as $item) {
$total = $total->add($item->getSubtotal());
}
$this->totalAmount = $total;
}
// 获取订单项(返回只读副本)
public function getItems(): array
{
return $this->items;
}
}
// 订单项(不是聚合根,只能通过Order访问)
class OrderItem
{
private OrderItemId $itemId;
private ProductId $productId;
private int $quantity;
private Money $price;
private function __construct(
OrderItemId $itemId,
ProductId $productId,
int $quantity,
Money $price
) {
if ($quantity <= 0) {
throw new \InvalidArgumentException('数量必须大于0');
}
$this->itemId = $itemId;
$this->productId = $productId;
$this->quantity = $quantity;
$this->price = $price;
}
public static function create(
OrderItemId $itemId,
ProductId $productId,
int $quantity,
Money $price
): self {
return new self($itemId, $productId, $quantity, $price);
}
public function increaseQuantity(int $amount): void
{
$this->quantity += $amount;
}
public function getSubtotal(): Money
{
return $this->price->multiply($this->quantity);
}
public function getItemId(): OrderItemId
{
return $this->itemId;
}
public function getProductId(): ProductId
{
return $this->productId;
}
}
领域服务(Domain Service)
领域服务:不属于任何实体或值对象的业务逻辑
特征:
✅ 无状态
✅ 处理跨实体的业务逻辑
✅ 协调多个聚合
php
<?php
namespace App\Domain\Order\Service;
// 订单定价服务
class OrderPricingService
{
/**
* 计算订单价格(包含优惠、税费等)
*/
public function calculatePrice(
Order $order,
?CouponCode $coupon,
TaxRate $taxRate
): Money {
// 原始金额
$originalAmount = $order->getTotalAmount();
// 应用优惠券
$discountedAmount = $coupon
? $this->applyCoupon($originalAmount, $coupon)
: $originalAmount;
// 计算税费
$tax = $discountedAmount->multiply($taxRate->getValue());
// 最终金额
return $discountedAmount->add($tax);
}
private function applyCoupon(Money $amount, CouponCode $coupon): Money
{
if ($coupon->isPercentage()) {
return $amount->multiply(1 - $coupon->getDiscount());
}
return $amount->subtract($coupon->getFixedAmount());
}
}
// 库存检查服务
class InventoryCheckService
{
/**
* 检查订单商品库存是否充足
*/
public function checkAvailability(Order $order): bool
{
foreach ($order->getItems() as $item) {
$product = $this->productRepository->find($item->getProductId());
if ($product->getStock() < $item->getQuantity()) {
return false;
}
}
return true;
}
}
领域事件(Domain Event)
领域事件:领域中发生的重要业务事件
特征:
✅ 过去时命名(OrderPaid, OrderShipped)
✅ 不可变
✅ 包含事件发生时的关键信息
✅ 用于解耦聚合间的依赖
php
<?php
namespace App\Domain\Order\Event;
// 订单已支付事件
final class OrderPaidEvent
{
private OrderId $orderId;
private Money $amount;
private \DateTimeImmutable $occurredAt;
public function __construct(OrderId $orderId, Money $amount)
{
$this->orderId = $orderId;
$this->amount = $amount;
$this->occurredAt = new \DateTimeImmutable();
}
public function getOrderId(): OrderId
{
return $this->orderId;
}
public function getAmount(): Money
{
return $this->amount;
}
public function getOccurredAt(): \DateTimeImmutable
{
return $this->occurredAt;
}
}
// 订单已发货事件
final class OrderShippedEvent
{
private OrderId $orderId;
private string $trackingNumber;
private \DateTimeImmutable $occurredAt;
public function __construct(OrderId $orderId, string $trackingNumber)
{
$this->orderId = $orderId;
$this->trackingNumber = $trackingNumber;
$this->occurredAt = new \DateTimeImmutable();
}
}
仓储(Repository)
仓储:封装聚合的持久化和查询
特征:
✅ 面向聚合根
✅ 提供类集合接口
✅ 隐藏持久化细节
php
<?php
namespace App\Domain\Order\Repository;
// 仓储接口(在领域层定义)
interface OrderRepositoryInterface
{
public function nextIdentity(): OrderId;
public function save(Order $order): void;
public function findById(OrderId $orderId): ?Order;
public function findByUserId(UserId $userId): array;
public function remove(Order $order): void;
}
// 仓储实现(在基础设施层)
namespace App\Infrastructure\Persistence\Order;
use App\Domain\Order\Repository\OrderRepositoryInterface;
class MySQLOrderRepository implements OrderRepositoryInterface
{
public function nextIdentity(): OrderId
{
return OrderId::generate();
}
public function save(Order $order): void
{
// 将领域模型转换为数据模型
$data = [
'order_id' => $order->getOrderId()->getValue(),
'user_id' => $order->getUserId()->getValue(),
'status' => $order->getStatus()->getValue(),
'total_amount' => $order->getTotalAmount()->getAmount(),
'created_at' => $order->getCreatedAt()->format('Y-m-d H:i:s'),
];
// 保存到数据库
Db::table('orders')->updateOrInsert(
['order_id' => $data['order_id']],
$data
);
// 保存订单项
foreach ($order->getItems() as $item) {
$this->saveOrderItem($order->getOrderId(), $item);
}
}
public function findById(OrderId $orderId): ?Order
{
$data = Db::table('orders')
->where('order_id', $orderId->getValue())
->first();
if (!$data) {
return null;
}
// 从数据模型重建领域模型
return $this->reconstitute($data);
}
private function reconstitute(array $data): Order
{
// 使用反射或工厂方法重建聚合
// ...
}
}
11.3 Hyperf 中的 DDD 落地
目录结构
bash
app/
├── Application/ # 应用层
│ ├── Command/ # 命令(写操作)
│ │ ├── CreateOrderCommand.php
│ │ └── PayOrderCommand.php
│ ├── Query/ # 查询(读操作)
│ │ ├── GetOrderQuery.php
│ │ └── ListOrdersQuery.php
│ └── Service/ # 应用服务
│ └── OrderApplicationService.php
│
├── Domain/ # 领域层
│ ├── Order/ # 订单限界上下文
│ │ ├── Entity/ # 实体
│ │ │ └── Order.php
│ │ ├── ValueObject/ # 值对象
│ │ │ ├── OrderId.php
│ │ │ ├── OrderStatus.php
│ │ │ └── OrderItem.php
│ │ ├── Service/ # 领域服务
│ │ │ └── OrderPricingService.php
│ │ ├── Event/ # 领域事件
│ │ │ ├── OrderCreatedEvent.php
│ │ │ └── OrderPaidEvent.php
│ │ └── Repository/ # 仓储接口
│ │ └── OrderRepositoryInterface.php
│ │
│ ├── Product/ # 商品限界上下文
│ └── Shared/ # 共享内核
│ └── ValueObject/
│ ├── Money.php
│ └── UserId.php
│
├── Infrastructure/ # 基础设施层
│ ├── Persistence/ # 持久化
│ │ ├── Order/
│ │ │ └── MySQLOrderRepository.php
│ │ └── Product/
│ └── External/ # 外部服务
│ ├── PaymentGateway.php
│ └── EmailService.php
│
└── Interface/ # 接口层
├── Http/
│ └── Controller/
│ └── OrderController.php
├── Rpc/
└── Console/
完整示例:创建订单
1. 用户接口层(Controller)
php
<?php
namespace App\Interface\Http\Controller;
use App\Application\Command\CreateOrderCommand;
use App\Application\Service\OrderApplicationService;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\PostMapping;
use Hyperf\Di\Annotation\Inject;
#[Controller(prefix: '/api/orders')]
class OrderController
{
#[Inject]
private OrderApplicationService $orderService;
#[PostMapping('')]
public function create(OrderRequest $request)
{
// 1. 验证请求
$validated = $request->validated();
// 2. 构建命令
$command = new CreateOrderCommand(
userId: $validated['user_id'],
items: $validated['items'],
shippingAddress: $validated['shipping_address']
);
// 3. 执行命令
$orderId = $this->orderService->createOrder($command);
// 4. 返回响应
return [
'code' => 0,
'data' => ['order_id' => $orderId->getValue()],
];
}
}
2. 应用层(Application Service)
php
<?php
namespace App\Application\Service;
use App\Application\Command\CreateOrderCommand;
use App\Domain\Order\Entity\Order;
use App\Domain\Order\Repository\OrderRepositoryInterface;
use App\Domain\Order\Service\InventoryCheckService;
use Hyperf\DbConnection\Db;
class OrderApplicationService
{
public function __construct(
private OrderRepositoryInterface $orderRepository,
private InventoryCheckService $inventoryCheck,
private EventDispatcherInterface $eventDispatcher
) {}
/**
* 创建订单(应用服务编排业务流程)
*/
public function createOrder(CreateOrderCommand $command): OrderId
{
return Db::transaction(function () use ($command) {
// 1. 构建领域对象
$userId = UserId::fromString($command->userId);
$items = $this->buildOrderItems($command->items);
// 2. 创建订单聚合(调用领域模型)
$order = Order::create($userId, $items);
// 3. 设置收货地址
$shippingAddress = ShippingAddress::fromArray($command->shippingAddress);
$order->updateShippingAddress($shippingAddress);
// 4. 检查库存(调用领域服务)
if (!$this->inventoryCheck->checkAvailability($order)) {
throw new \DomainException('库存不足');
}
// 5. 保存订单(通过仓储)
$this->orderRepository->save($order);
// 6. 发布领域事件
foreach ($order->getEvents() as $event) {
$this->eventDispatcher->dispatch($event);
}
return $order->getOrderId();
});
}
private function buildOrderItems(array $items): array
{
return array_map(function ($item) {
return [
'product_id' => ProductId::fromString($item['product_id']),
'quantity' => $item['quantity'],
'price' => Money::fromAmount($item['price']),
];
}, $items);
}
}
3. 领域层(Domain Model)
php
<?php
namespace App\Domain\Order\Entity;
class Order
{
private OrderId $orderId;
private UserId $userId;
private OrderStatus $status;
private Money $totalAmount;
private ShippingAddress $shippingAddress;
private array $items = [];
private array $events = []; // 领域事件
private function __construct(OrderId $orderId, UserId $userId)
{
$this->orderId = $orderId;
$this->userId = $userId;
$this->status = OrderStatus::pending();
}
public static function create(UserId $userId, array $items): self
{
$order = new self(OrderId::generate(), $userId);
// 添加订单项
foreach ($items as $item) {
$order->addItem(
$item['product_id'],
$item['quantity'],
$item['price']
);
}
// 记录领域事件
$order->recordEvent(new OrderCreatedEvent($order->orderId));
return $order;
}
public function addItem(ProductId $productId, int $quantity, Money $price): void
{
$this->items[] = OrderItem::create(
OrderItemId::generate(),
$productId,
$quantity,
$price
);
$this->recalculateTotal();
}
private function recalculateTotal(): void
{
$total = Money::zero();
foreach ($this->items as $item) {
$total = $total->add($item->getSubtotal());
}
$this->totalAmount = $total;
}
protected function recordEvent(object $event): void
{
$this->events[] = $event;
}
public function getEvents(): array
{
return $this->events;
}
public function clearEvents(): void
{
$this->events = [];
}
}
4. 基础设施层(Repository)
php
<?php
namespace App\Infrastructure\Persistence\Order;
use App\Domain\Order\Entity\Order;
use App\Domain\Order\Repository\OrderRepositoryInterface;
class MySQLOrderRepository implements OrderRepositoryInterface
{
public function save(Order $order): void
{
// 保存订单主表
Db::table('orders')->updateOrInsert(
['order_id' => $order->getOrderId()->getValue()],
[
'user_id' => $order->getUserId()->getValue(),
'status' => $order->getStatus()->getValue(),
'total_amount' => $order->getTotalAmount()->getAmount(),
'shipping_address' => json_encode($order->getShippingAddress()->toArray()),
'created_at' => date('Y-m-d H:i:s'),
]
);
// 保存订单项
foreach ($order->getItems() as $item) {
Db::table('order_items')->updateOrInsert(
['item_id' => $item->getItemId()->getValue()],
[
'order_id' => $order->getOrderId()->getValue(),
'product_id' => $item->getProductId()->getValue(),
'quantity' => $item->getQuantity(),
'price' => $item->getPrice()->getAmount(),
]
);
}
}
public function findById(OrderId $orderId): ?Order
{
$orderData = Db::table('orders')
->where('order_id', $orderId->getValue())
->first();
if (!$orderData) {
return null;
}
$items = Db::table('order_items')
->where('order_id', $orderId->getValue())
->get();
// 重建聚合
return $this->reconstitute($orderData, $items);
}
}
11.4 CQRS(命令查询职责分离)
scss
CQRS:将读操作和写操作分离
┌─────────────────────────────────────────┐
│ 客户端 │
└──────┬────────────────────┬─────────────┘
│ │
│ 写操作(Command) │ 读操作(Query)
▼ ▼
┌─────────────┐ ┌─────────────┐
│ 写模型 │ │ 读模型 │
│ (Domain) │ │ (DTO/VO) │
│ - 复杂业务 │ │ - 简单查询 │
│ - 事务 │──────►│ - 无业务 │
│ - 一致性 │ 同步 │ - 性能优化 │
└─────────────┘ └─────────────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ 写库(MySQL)│ │ 读库(ES/Redis)│
└─────────────┘ └─────────────┘
php
<?php
// 命令(写操作)
namespace App\Application\Command;
class CreateOrderCommand
{
public function __construct(
public string $userId,
public array $items,
public array $shippingAddress
) {}
}
// 命令处理器
class CreateOrderCommandHandler
{
public function handle(CreateOrderCommand $command): OrderId
{
// 调用领域模型
$order = Order::create(...);
$this->repository->save($order);
return $order->getOrderId();
}
}
// 查询(读操作)
namespace App\Application\Query;
class GetOrderQuery
{
public function __construct(
public string $orderId
) {}
}
// 查询处理器
class GetOrderQueryHandler
{
public function handle(GetOrderQuery $query): OrderDTO
{
// 直接查询数据库,返回DTO
$data = Db::table('orders')
->where('order_id', $query->orderId)
->first();
return OrderDTO::fromArray($data);
}
}
// 数据传输对象(DTO)
class OrderDTO
{
public string $orderId;
public string $status;
public float $totalAmount;
public static function fromArray(array $data): self
{
$dto = new self();
$dto->orderId = $data['order_id'];
$dto->status = $data['status'];
$dto->totalAmount = $data['total_amount'];
return $dto;
}
}
11.5 DDD 最佳实践
1. 充血模型 vs 贫血模型
php
// ❌ 贫血模型(只有数据,没有行为)
class Order
{
public $id;
public $status;
public $amount;
}
class OrderService
{
public function pay(Order $order)
{
if ($order->status !== 'pending') {
throw new Exception('订单状态错误');
}
$order->status = 'paid';
}
}
// ✅ 充血模型(数据+行为)
class Order
{
private OrderId $id;
private OrderStatus $status;
private Money $amount;
public function pay(): void
{
if (!$this->status->isPending()) {
throw new DomainException('只有待支付订单才能支付');
}
$this->status = OrderStatus::paid();
$this->recordEvent(new OrderPaidEvent($this->id));
}
}
2. 限界上下文(Bounded Context)
css
电商系统的限界上下文划分:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 订单上下文 │ │ 商品上下文 │ │ 用户上下文 │
│ │ │ │ │ │
│ - Order │ │ - Product │ │ - User │
│ - OrderItem │ │ - Category │ │ - Address │
│ - Payment │ │ - Inventory │ │ - Profile │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
└────────────────────┴────────────────────┘
通过领域事件通信
3. 通用语言(Ubiquitous Language)
csharp
通用语言:业务和技术团队共同使用的语言
❌ 技术语言:
- Table: orders
- Column: status_code
- Method: updateStatusCode()
✅ 通用语言:
- 聚合: Order(订单)
- 值对象: OrderStatus(订单状态)
- 方法: pay()(支付)、ship()(发货)
代码中使用通用语言:
class Order {
public function pay() {} // 不是 updateStatus('paid')
public function ship() {} // 不是 setStatus(2)
public function complete() {} // 不是 markAsCompleted()
}
11.6 DDD 面试题
Q1: 什么是 DDD?有什么优势?
A: DDD 是领域驱动设计,将业务领域模型放在软件设计中心。
优势:
- 统一业务和技术语言
- 清晰的分层架构
- 高内聚、低耦合
- 适合复杂业务系统
- 便于微服务拆分
Q2: 实体和值对象的区别?
A:
| 维度 | 实体 | 值对象 |
|---|---|---|
| 标识 | 有唯一ID | 无ID |
| 可变性 | 可变 | 不可变 |
| 相等性 | 通过ID | 通过值 |
| 生命周期 | 有 | 无 |
Q3: 什么是聚合根?为什么需要?
A: 聚合根是一组相关对象的根实体,对外提供统一接口。
作用:
- 保证聚合内的一致性
- 控制事务边界
- 简化对外接口
- 只有聚合根可被外部引用
Q4: DDD 的分层架构?
A:
- 用户接口层:接收请求、返回响应
- 应用层:业务流程编排、事务控制
- 领域层:业务逻辑(核心)
- 基础设施层:数据持久化、外部服务
Q5: 什么是 CQRS?
A: 命令查询职责分离,将读写操作分离。
- 命令(写):修改数据,调用领域模型
- 查询(读):读取数据,返回 DTO
优势:
- 读写分离,各自优化
- 提高系统性能
- 简化复杂查询
12. 总结
分布式事务核心:
- ✅ 理解 CAP 和 BASE 理论
- ✅ 掌握主流解决方案:Saga、TCC、本地消息表
- ✅ 会使用 DTM:简化分布式事务开发
- ✅ 幂等性设计:防止重复提交
- ✅ 监控告警:及时发现问题
DDD 核心:
- ✅ 理解核心概念:实体、值对象、聚合根、领域服务
- ✅ 掌握分层架构:接口层、应用层、领域层、基础设施层
- ✅ 领域建模能力:统一语言、限界上下文
- ✅ 在 Hyperf 中落地:目录结构、代码实现
推荐学习:
- DTM 官方文档:dtm.pub/
- DDD 经典书籍:《领域驱动设计》(Eric Evans)
- Hyperf 分布式事务文档:hyperf.wiki/3.1/#/zh-cn...
- 实践项目:搭建电商订单系统
建议:
- 能说出各方案的优缺点和适用场景
- 理解 DDD 核心概念和实践
- 有实际项目经验最好
- 理解幂等性、超时处理等细节