06-微服务架构与分布式事务

微服务架构与分布式事务

基于 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 是一款开源的分布式事务管理器,支持多种事务模式。

官方资源

特点

  • 支持多种事务模式(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: 常见的分布式事务解决方案有哪些?

答案

  1. Saga:最终一致性,适合长事务
  2. TCC:强一致性,需要预留资源
  3. 本地消息表:最终一致性,实现简单
  4. 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:

  1. 用户接口层:接收请求、返回响应
  2. 应用层:业务流程编排、事务控制
  3. 领域层:业务逻辑(核心)
  4. 基础设施层:数据持久化、外部服务

Q5: 什么是 CQRS?

A: 命令查询职责分离,将读写操作分离。

  • 命令(写):修改数据,调用领域模型
  • 查询(读):读取数据,返回 DTO

优势:

  • 读写分离,各自优化
  • 提高系统性能
  • 简化复杂查询

12. 总结

分布式事务核心:

  1. 理解 CAP 和 BASE 理论
  2. 掌握主流解决方案:Saga、TCC、本地消息表
  3. 会使用 DTM:简化分布式事务开发
  4. 幂等性设计:防止重复提交
  5. 监控告警:及时发现问题

DDD 核心:

  1. 理解核心概念:实体、值对象、聚合根、领域服务
  2. 掌握分层架构:接口层、应用层、领域层、基础设施层
  3. 领域建模能力:统一语言、限界上下文
  4. 在 Hyperf 中落地:目录结构、代码实现

推荐学习

  • DTM 官方文档:dtm.pub/
  • DDD 经典书籍:《领域驱动设计》(Eric Evans)
  • Hyperf 分布式事务文档:hyperf.wiki/3.1/#/zh-cn...
  • 实践项目:搭建电商订单系统

建议

  • 能说出各方案的优缺点和适用场景
  • 理解 DDD 核心概念和实践
  • 有实际项目经验最好
  • 理解幂等性、超时处理等细节
相关推荐
yolo_Yang7 小时前
72.是否可以把所有Bean都通过Spring容器来管
后端·spring
村姑飞来了7 小时前
Kafka4.1.0 队列模式尝鲜
后端·架构
oak隔壁找我7 小时前
ShardingJdbc配置说明
java·后端
javachen__7 小时前
Spring Boot将错误日志发送到企微微信或钉钉群
spring boot·后端·钉钉
JaguarJack8 小时前
PHP 基金会宣布:Streams 现代化 将引入事件循环与异步新能力
后端·php
Mos_x8 小时前
HeidiSQL导入与导出数据
java·后端
oak隔壁找我8 小时前
Elasticsearch QueryBuilders 高级使用案例
java·后端
Zhang青山8 小时前
【玩转全栈】----Django基本配置和介绍
java·后端
勇敢牛牛_9 小时前
Rust真的适合写业务后端吗?
开发语言·后端·rust