Hyperf 中实现模型事件观察者的事务提交后执行机制

Hyperf 中实现模型事件观察者的事务提交后执行机制

【免费下载链接】hyperf 🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease. 【免费下载链接】hyperf 项目地址: https://gitcode.com/hyperf/hyperf

痛点:为什么需要事务提交后执行?

在传统的模型事件处理中,我们经常遇到一个棘手的问题:模型事件在事务内部触发,但外部依赖(如消息队列、缓存更新、外部API调用)需要在事务成功提交后才能执行。如果这些操作在事务内部执行,一旦事务回滚,已经执行的外部操作无法自动撤销,会导致数据不一致。

典型场景

  • 订单创建后发送通知邮件
  • 用户注册后更新推荐系统
  • 库存扣减后同步到搜索引擎
  • 支付成功后触发分销计算

Hyperf 模型事件观察者架构解析

核心组件关系

mermaid

现有机制的限制

Hyperf 的模型监听器默认在模型事件发生时立即执行,这会导致:

  1. 事务未提交时执行外部操作
  2. 回滚时无法撤销已执行的操作
  3. 潜在的竞态条件

实现事务提交后执行机制

方案一:使用数据库事务事件监听器

<?php

declare(strict_types=1);

namespace App\Listener;

use Hyperf\Database\Connection;
use Hyperf\Database\Events\TransactionCommitted;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Utils\Coroutine;

#[Listener]
class TransactionCommitListener implements ListenerInterface
{
    protected static $afterCommitCallbacks = [];

    public static function afterCommit(callable $callback): void
    {
        $coroutineId = Coroutine::id();
        if (!isset(self::$afterCommitCallbacks[$coroutineId])) {
            self::$afterCommitCallbacks[$coroutineId] = [];
        }
        self::$afterCommitCallbacks[$coroutineId][] = $callback;
    }

    public function listen(): array
    {
        return [
            TransactionCommitted::class,
        ];
    }

    public function process(object $event): void
    {
        if ($event instanceof TransactionCommitted) {
            $coroutineId = Coroutine::id();
            if (isset(self::$afterCommitCallbacks[$coroutineId])) {
                foreach (self::$afterCommitCallbacks[$coroutineId] as $callback) {
                    try {
                        call_user_func($callback);
                    } catch (\Throwable $e) {
                        // 记录日志,但不影响其他回调
                        \Hyperf\Utils\ApplicationContext::getContainer()
                            ->get(\Hyperf\Logger\LoggerFactory::class)
                            ->get('transaction')
                            ->error('After commit callback failed: ' . $e->getMessage());
                    }
                }
                unset(self::$afterCommitCallbacks[$coroutineId]);
            }
        }
    }
}

方案二:增强模型监听器支持事务提交后执行

<?php

declare(strict_types=1);

namespace App\ModelListener;

use App\Listener\TransactionCommitListener;
use Hyperf\Database\Model\Events\Saved;
use Hyperf\Database\Model\Events\Deleted;
use Hyperf\Database\Model\Events\Event;
use Hyperf\Event\Annotation\Listener;
use Hyperf\ModelListener\AbstractListener;

#[Listener]
class UserListener extends AbstractListener
{
    public function listen(): array
    {
        return [
            Saved::class,
            Deleted::class,
        ];
    }

    public function saved(Event $event): void
    {
        $user = $event->getModel();
        
        // 注册事务提交后执行的回调
        TransactionCommitListener::afterCommit(function () use ($user) {
            $this->onUserSavedAfterCommit($user);
        });
    }

    public function deleted(Event $event): void
    {
        $user = $event->getModel();
        
        TransactionCommitListener::afterCommit(function () use ($user) {
            $this->onUserDeletedAfterCommit($user);
        });
    }

    protected function onUserSavedAfterCommit($user): void
    {
        // 事务提交后执行的安全操作
        $this->syncToSearchEngine($user);
        $this->sendWelcomeEmail($user);
        $this->updateRecommendationSystem($user);
    }

    protected function onUserDeletedAfterCommit($user): void
    {
        // 事务提交后执行的清理操作
        $this->removeFromSearchEngine($user);
        $this->cleanupUserData($user);
    }

    private function syncToSearchEngine($user): void
    {
        // 同步到Elasticsearch等搜索引擎
    }

    private function sendWelcomeEmail($user): void
    {
        // 发送欢迎邮件
    }

    private function updateRecommendationSystem($user): void
    {
        // 更新推荐系统
    }

    private function removeFromSearchEngine($user): void
    {
        // 从搜索引擎移除
    }

    private function cleanupUserData($user): void
    {
        // 清理用户相关数据
    }
}

方案三:使用注解简化事务提交后执行

<?php

declare(strict_types=1);

namespace App\Annotation;

use Attribute;
use Hyperf\Di\Annotation\AbstractAnnotation;

#[Attribute(Attribute::TARGET_METHOD)]
class AfterCommit extends AbstractAnnotation
{
    public function __construct(
        public array $events = []
    ) {
    }
}
<?php

declare(strict_types=1);

namespace App\Aspect;

use App\Annotation\AfterCommit;
use App\Listener\TransactionCommitListener;
use Hyperf\Di\Annotation\Aspect;
use Hyperf\Di\Aop\AbstractAspect;
use Hyperf\Di\Aop\ProceedingJoinPoint;

#[Aspect]
class AfterCommitAspect extends AbstractAspect
{
    public $annotations = [
        AfterCommit::class,
    ];

    public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        $result = $proceedingJoinPoint->process();
        
        $metadata = $proceedingJoinPoint->getAnnotationMetadata();
        $afterCommit = $metadata->method[AfterCommit::class] ?? null;
        
        if ($afterCommit) {
            TransactionCommitListener::afterCommit(function () use ($result, $proceedingJoinPoint) {
                // 可以在这里处理结果或重新执行某些逻辑
            });
        }
        
        return $result;
    }
}

配置与注册

配置文件示例

// config/autoload/listeners.php
return [
    Hyperf\ModelListener\Listener\ModelEventListener::class,
    Hyperf\ModelListener\Listener\ModelHookEventListener::class,
    App\Listener\TransactionCommitListener::class,
];

// config/autoload/annotations.php
return [
    'scan' => [
        'paths' => [
            BASE_PATH . '/app',
        ],
        'collectors' => [
            Hyperf\ModelListener\Collector\ListenerCollector::class,
        ],
    ],
];

模型监听器注册

// config/autoload/model_listeners.php
return [
    \App\Model\User::class => [
        \App\ModelListener\UserListener::class,
    ],
    \App\Model\Order::class => [
        \App\ModelListener\OrderListener::class,
    ],
];

最佳实践与注意事项

1. 错误处理策略

TransactionCommitListener::afterCommit(function () use ($user) {
    try {
        $this->criticalOperation($user);
    } catch (\Throwable $e) {
        // 记录详细日志
        $this->logger->error('After commit operation failed', [
            'user_id' => $user->id,
            'error' => $e->getMessage(),
            'trace' => $e->getTraceAsString()
        ]);
        
        // 可选:重试机制或告警
        $this->retryOrAlert($user, $e);
    }
});

2. 性能优化建议

场景优化策略说明
高频事件批量处理收集多个事件后统一处理
耗时操作异步队列使用Hyperf的异步队列组件
大量数据分页处理避免单次处理过多数据

3. 事务边界管理

// 明确的事务边界示例
DB::transaction(function () use ($userData) {
    $user = User::create($userData);
    
    // 注册提交后操作
    TransactionCommitListener::afterCommit(function () use ($user) {
        $this->afterUserCreation($user);
    });
    
    return $user;
});

完整示例:用户注册流程

<?php

declare(strict_types=1);

namespace App\Service;

use App\Listener\TransactionCommitListener;
use App\Model\User;
use Hyperf\DbConnection\Db;

class UserService
{
    public function register(array $data): User
    {
        return Db::transaction(function () use ($data) {
            // 创建用户
            $user = User::create([
                'name' => $data['name'],
                'email' => $data['email'],
                'password' => bcrypt($data['password']),
            ]);
            
            // 创建用户配置
            $user->profile()->create([
                'bio' => $data['bio'] ?? '',
                'avatar' => $data['avatar'] ?? '',
            ]);
            
            // 注册事务提交后的操作
            TransactionCommitListener::afterCommit(function () use ($user) {
                $this->afterRegistration($user);
            });
            
            return $user;
        });
    }
    
    protected function afterRegistration(User $user): void
    {
        // 这些操作只在事务提交后执行
        $this->sendWelcomeEmail($user);
        $this->syncToSearchEngine($user);
        $this->updateAnalytics($user);
        $this->notifyAdmins($user);
    }
    
    private function sendWelcomeEmail(User $user): void
    {
        // 使用异步队列发送邮件
        \Hyperf\AsyncQueue\Driver\DriverFactory::get('default')
            ->push(new SendWelcomeEmailJob($user));
    }
    
    private function syncToSearchEngine(User $user): void
    {
        // 同步到Elasticsearch
        \Hyperf\Elasticsearch\ClientFactory::get('default')
            ->index([
                'index' => 'users',
                'id' => $user->id,
                'body' => $user->toSearchArray(),
            ]);
    }
    
    private function updateAnalytics(User $user): void
    {
        // 更新数据分析系统
    }
    
    private function notifyAdmins(User $user): void
    {
        // 通知管理员新用户注册
    }
}

总结

Hyperf 中实现模型事件观察者的事务提交后执行机制,通过结合事务事件监听器和协程上下文管理,能够有效解决事务内外操作的一致性问题。这种机制特别适用于:

  1. 外部系统集成:消息队列、搜索引擎、缓存系统
  2. 异步操作:邮件发送、短信通知、文件处理
  3. 数据同步:多数据源之间的数据同步

关键优势:

  • ✅ 保证事务原子性
  • ✅ 避免脏读和幻读
  • ✅ 支持复杂的业务场景
  • ✅ 良好的错误处理机制

通过本文介绍的三种方案,您可以根据具体业务需求选择最适合的实现方式,构建健壮可靠的分布式应用系统。

【免费下载链接】hyperf 🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease. 【免费下载链接】hyperf 项目地址: https://gitcode.com/hyperf/hyperf

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值