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

148 阅读12分钟

微服务架构与分布式事务

基于 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 为什么需要分布式事务?

// 场景:电商下单
// 1. 订单服务:创建订单
// 2. 库存服务:扣减库存
// 3. 支付服务:创建支付单

// 问题:如果库存扣减成功,但创建支付单失败,如何回滚?
// 传统事务只能保证单数据库的 ACID
// 跨服务、跨数据库需要分布式事务

2.2 CAP 定理

C(一致性):所有节点看到相同的数据
A(可用性):系统持续可用
P(分区容错):网络分区时系统继续工作

定理:只能同时满足两个
- CP:强一致性,牺牲可用性(如 ZooKeeper)
- AP:高可用,牺牲一致性(如 Cassandra)
- CA:不现实(分布式系统必然有网络分区)

2.3 BASE 理论

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 服务端

# 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 客户端

composer require dtm/dtm-client

配置

// 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 原理

Saga 将长事务拆分为多个本地短事务:
- 每个短事务都有对应的补偿操作
- 正常流程:T1 → T2 → T3 → ... → Tn
- 失败回滚:Cn → Cn-1 → ... → C2 → C1

优点:
- 无需锁,性能高
- 支持长事务
- 实现简单

缺点:
- 隔离性较弱
- 需要设计补偿操作

5.2 Hyperf Saga 示例

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 实现正向和补偿操作

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 原理

TCC(Try-Confirm-Cancel)分三个阶段:

1. Try 阶段:
   - 尝试执行业务
   - 完成资源检查和预留

2. Confirm 阶段:
   - 确认执行业务
   - 真正执行业务

3. Cancel 阶段:
   - 取消执行业务
   - 释放预留资源

特点:
- 强隔离性
- 性能好
- 实现复杂(需要预留资源)

6.2 Hyperf TCC 示例

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 原理

本地消息表是最简单可靠的分布式事务方案:

1. 在本地事务中:
   - 执行业务操作
   - 插入消息到本地消息表

2. 定时任务:
   - 扫描未发送的消息
   - 发送到消息队列

3. 消费者:
   - 消费消息
   - 执行后续操作

优点:
- 实现简单
- 性能好
- 最终一致性

缺点:
- 需要定时任务
- 消息表需要维护

7.2 实现示例

// 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 事务消息

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 事务消息

// 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 选择合适的方案

场景 1:订单系统
- 推荐:Saga / 本地消息表
- 原因:可接受最终一致性,步骤较多

场景 2:支付系统
- 推荐:TCC
- 原因:需要强一致性,资金敏感

场景 3:库存扣减
- 推荐:TCC / 本地消息表
- 原因:需要预留资源或异步扣减

场景 4:日志记录
- 推荐:MQ 异步
- 原因:允许丢失,性能优先

9.2 幂等性设计

// 使用唯一键防止重复提交
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 超时处理

// 设置合理的超时时间
$saga = new Saga();
$saga->setTimeout(30);  // 30 秒超时

// 超时后自动回滚

9.4 监控和告警

// 记录分布式事务日志
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?
DDD (Domain-Driven Design) 领域驱动设计:
- 由 Eric Evans 提出的软件设计方法论
- 核心:将业务领域模型放在软件设计的中心
- 目标:应对复杂业务系统的开发和维护

优势:
✅ 统一业务和技术语言(通用语言)
✅ 清晰的分层架构
✅ 高内聚、低耦合
✅ 易于测试和维护
✅ 适合微服务拆分
DDD 分层架构
┌─────────────────────────────────┐
│     用户接口层 (Interface)       │  ← Controller、HTTP、RPC
│  - 接收请求                      │
│  - 参数验证                      │
│  - 返回响应                      │
├─────────────────────────────────┤
│     应用层 (Application)         │  ← Service、UseCase
│  - 业务流程编排                  │
│  - 事务控制                      │
│  - 权限校验                      │
├─────────────────────────────────┤
│     领域层 (Domain)              │  ← Entity、ValueObject、DomainService
│  - 业务逻辑(核心)              │
│  - 领域模型                      │
│  - 领域服务                      │
├─────────────────────────────────┤
│     基础设施层 (Infrastructure)  │  ← Repository、Cache、MQ
│  - 数据持久化                    │
│  - 外部服务调用                  │
│  - 技术实现                      │
└─────────────────────────────────┘

11.2 DDD 核心概念详解

实体(Entity)
实体:有唯一标识的对象,生命周期中状态可变

特征:
✅ 有唯一ID
✅ 状态可变
✅ 有生命周期
✅ 包含业务逻辑
<?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
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
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
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
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
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 落地

目录结构
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
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
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
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
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(命令查询职责分离)

CQRS:将读操作和写操作分离

┌─────────────────────────────────────────┐
│              客户端                      │
└──────┬────────────────────┬─────────────┘
       │                    │
       │ 写操作(Command)   │ 读操作(Query)
       ▼                    ▼
┌─────────────┐      ┌─────────────┐
│  写模型      │      │  读模型      │
│  (Domain)   │      │  (DTO/VO)   │
│  - 复杂业务  │      │  - 简单查询  │
│  - 事务      │──────►│  - 无业务    │
│  - 一致性    │ 同步  │  - 性能优化  │
└─────────────┘      └─────────────┘
       │                    │
       ▼                    ▼
┌─────────────┐      ┌─────────────┐
│  写库(MySQL)│      │  读库(ES/Redis)│
└─────────────┘      └─────────────┘
<?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 贫血模型
// ❌ 贫血模型(只有数据,没有行为)
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)
电商系统的限界上下文划分:

┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐
│  订单上下文     │  │  商品上下文     │  │  用户上下文     │
│                 │  │                 │  │                 │
│  - Order        │  │  - Product      │  │  - User         │
│  - OrderItem    │  │  - Category     │  │  - Address      │
│  - Payment      │  │  - Inventory    │  │  - Profile      │
└─────────────────┘  └─────────────────┘  └─────────────────┘
        │                    │                    │
        └────────────────────┴────────────────────┘
                     通过领域事件通信
3. 通用语言(Ubiquitous Language)
通用语言:业务和技术团队共同使用的语言

❌ 技术语言:
- 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 核心概念和实践
  • 有实际项目经验最好
  • 理解幂等性、超时处理等细节