定制 smskin/laravel-saga 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

smskin/laravel-saga

Composer 安装命令:

composer require smskin/laravel-saga

包简介

State-machine (Saga) engine for laravel projects

README 文档

README

Composer Static Analysis Tests

While working with .Net Core and MassTransit, it became frustrating that Laravel lacked a ready-made state machine engine. Inspired by MassTransit, I wrote a library that functions similarly to MassTransit sagas.

Installation

  1. composer require smskin/laravel-saga
  2. php artisan vendor:publish --provider=SMSkin\LaravelSaga\Providers\ServiceProvider

Configuration

In the configuration file config/saga.php, you will find the engine settings and descriptions of state machines.

  • logger - a class responsible for logging the state machine's operation process. Can be changed to another class implementing the ISagaLogger interface.
  • state-machines - an array of registered state machines.
  • repositories
    • default - the repository for storing the state machine's state (database).
    • database
      • class - the repository class. Can be changed to another class implementing the ISagaRepository interface.
      • table - the name of the table where the state machine's state will be stored.

Saga Structure

Let's examine a saga from an example in this library (SMSkin\LaravelSaga\Example\SagaExample).

Property $context

This property describes the type (cast) of the stored object of the state machine. It can be any class inheriting from SagaContext. This object allows storing intermediate values obtained during interaction with other services during operation.

Method setup()

This method describes the state machine's operation algorithm.

Three key blocks of a saga:

  • correlation
  • initialization event\command
  • state machine transition logic

Correlation

This block describes the algorithm for obtaining the identifier of the state machine's context in the repository. It's described using two methods:

  • correlatedById - getting the object by ID.
  • correlatedBy - getting it by any storage field.
$this->builder()
     ->correlatedById(EUserCreated::class, static function (EUserCreated $event) {
            return $event->corrId;
     })

This block can be read as: Upon receiving the EUserCreated event, take the state machine's ID from the corrId property.

$this->builder()
     ->correlatedBy(EUserBlocked::class, 'userId', static function (EUserBlocked $event) {
            return $event->userId;
     });

This block can be read as: Upon receiving the EUserBlocked event, find the state machine by the userId field, taking the value from the userId event. Thus, the engine can search for the state machine's context both by UUID and by any context field.

Initialization Event\Command

This block describes the events\commands that will initialize the state machine.

The onInitEvent method takes two arguments:

  • The event class to be registered in Laravel.
  • A Closure for transforming the event into the state machine's context. With this method, you can save some initialization data in the context object.
 $this->builder()
      ->onInitEvent(CreateUserCommand::class, static function (CreateUserCommand $command) {
            return (new SagaExampleContext($command->correlationId))
                ->setEmail($command->email);
      });

This block can be read as:

  • Upon receiving the CreateUserCommand command, initialize the state machine.
  • Take the state machine's ID from the correlationId command.
  • Save the email from the command in the state machine's context.

State Machine Transition Logic

This block describes the state machine's algorithm. Key phrases:

  • duringState - while in the state machine's state.
  • on - upon receiving an event.
  • then - do (closure).
  • activity - perform a subroutine (class implementing the IActivity interface).
  • transitionTo - switch the state machine's status.
  • publish - publish an event.
  • initial - sugar for initialization (first stage).
  • finalize - sugar for finalization.
$this->builder()
     ->initial()
     ->transitionTo(SagaExampleStates::USER_CREATING)
     ->activity(UserCreatingActivity::class)
     ->then(function () {
            (new UserCommandService())->create(
                $this->context->getId(),
                $this->context->getEmail()
            );
    });

This block can be read as:

  • Upon initialization.
  • Switch the state to USER_CREATING.
  • Perform the UserCreatingActivity subprogram.
  • Execute the Closure - call UserCommandService->create, passing the context ID and email (which we stored in the context during initialization).
 $this->builder()
      ->duringState(SagaExampleStates::USER_CREATING)
      ->on(EUserCreated::class)
      ->then(function () {
            $event = $this->getHandledEvent();
            $this->context->setUserId($event->userId);
      })
      ->transitionTo(SagaExampleStates::USER_BLOCKING)
      ->then(function () {
            (new UserCommandService())->block(
                $this->context->getUserId()
            );
      });

This block can be read as:

  • While in the state USER_CREATING.
  • Upon receiving the EUserCreated event.
  • Execute the Closure, which will write the userId (from the event) into the state machine's context.
  • Switch the state to USER_BLOCKING.
  • Execute the Closure - call UserCommandService->block, passing the userId from the context.
$this->builder()
     ->duringState(SagaExampleStates::USER_BLOCKING)
     ->on(EUserBlocked::class)
     ->finalize()
     ->publish(function () {
            return new ESagaExampleFinalized($this->context->getId());
     });

This block can be read as:

  • While in the state USER_BLOCKING.
  • Upon receiving the EUserBlocked event.
  • Finalize the state machine.
  • Publish the event ESagaExampleFinalized, passing the saga ID.

Basic Operation Principle

The engine operates based on the Laravel Events. The events described in the setup() method are registered in the EventServiceProvider. The saga acts as a Listener.

When an event enters the bus, the Laravel broker executes the handle method of the sagas registered for that event.

Execution Optimization

Since the events to which the saga registers are described within the saga itself, Laravel will require time to compute these events from all sagas. To optimize this, an artisan command is written that saves a pre-computed cache of event=saga mapping ready for registration.

Caching

php artisan saga:cache

Cache Clearing

php artisan saga:cache:clear

Configuration Options

Changing the Saga Data Storage Repository

  1. Create a class implementing the ISagaRepository interface.
  2. Add it to the saga.repositories configuration.
  3. Specify it in the saga.repositories.default configuration variable.

Changing the Logger

  1. Create a class implementing the ISagaLogger interface.
  2. Specify it in the saga.logger configuration.

Creating Custom Sagas

  1. Create a class inheriting from BaseSaga.
  2. Describe the logic of the state machine in the setup() method.
  3. Specify the class in the saga.state-machines configuration.

smskin/laravel-saga 适用场景与选型建议

smskin/laravel-saga 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 22 次下载、GitHub Stars 达 1, 最近一次更新时间为 2024 年 04 月 26 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「state」 「laravel」 「machine」 「saga」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 smskin/laravel-saga 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 smskin/laravel-saga 我们能提供哪些服务?
定制开发 / 二次开发

基于 smskin/laravel-saga 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 22
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 1
  • 点击次数: 15
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 1
  • Watchers: 1
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2024-04-26