yaoonline/phpsagas-orchestrator
Composer 安装命令:
composer require yaoonline/phpsagas-orchestrator
包简介
Main component controls and coordinates saga participants
关键字:
README 文档
README
Table Of Contents
Introduction
A saga is a data consistency maintaining mechanism used in distributed systems (such as microservices-based applications) each of which has own database, making it impossible to use ACID.
The saga pattern represents a local transactions sequence executing through asynchronous messaging communication.
It can be seen as an alternative to 2PC protocol saving important microservices architecture advantages such as the possibility of using an appropriate storage for concrete microservices nature, as well as asynchronous messaging for high availability and loose coupling. Also it increased flexibility and scalability due to avoidance of participants blocking.
There are two ways to coordinate the saga execution:
- choreography - services publish and subscribes to domain events;
- orchestration - execution is managed by special service that controls transactions sequence and said services that they must do. This framework implements that approach.
More details about sagas you can find on the Chris Richardson site or in his great book Microservices Patterns.
Requirements
- php: >= 7.1
- ext-json
- phpsagas/contracts
- psr/log: ^1.1
About package
This component is the heart of phpsagas framework and is responsible for central coordination of each saga participant local transactions execution. Implementation inspired by eventuate-tram-sagas framework. You can use the orchestrator by one of the following methods:
- as part of your project (just a vendor package) by including it in a service, which is a owner of distributed business-transaction;
- as a standalone project having own database.
This choice entirely dependent on your preferences. Each of the options carried advantages and drawbacks, for example usage of orchestrator as a separate service can provide possibility of another database usage, as well as deployment on high-performance hardware. However, there are some disadvantages such as an undesirable control logic centralization, as well as a single point of failure.
Usage of orchestrator as a project package is more simple and allows to reduce messaging (using current service transactions as local commands).
Installation
You can install the package using Composer:
composer require yaoonline/phpsagas-orchestrator
and add to composer.json
"repositories": [ { "type": "vcs", "url": "https://github.com/yaoonline/phpsagas-orchestrator.git" } ]
Getting started
Configuration
There are some interfaces you have to implement (or use existing implementations):
SagaFactoryInterface- for sagas creation (test example)SagaInstanceRepositoryInterface- sagas repository (doctrine implementation)MessagePayloadSerializerInterface- serialize participant commands data messages (symfony/serializer implementation)SagaSerializerInterface- serialize sagas data (symfony/serializer implementation)MessageProducerInterface- messages sender (symfony/messenger implementation))MessageIdGeneratorInterface- generate message ids (uuid implementation).
Next, it is necessary to configure base orchestrator services - Saga Creator and SagaReplyHandler. You can do it using your favourite service-container (symfony autowiring, pimple, PHP-DI, etc) or manually (see below).
After that, the orchestrator is ready to be used. Let's look how that works.
Saga creation
Saga must implement SagaInterface by providing a type with definition consisting of steps that each distributed transaction participant has to perform.
For example, let's consider Travel Tour Service. The tour buying may consist of next stages distributed by some services:
- hotel booking // Hotel Service
- tickets booking // Tickets Service
- visa obtaining // Visa Service
- tour buying // Tour Service with Orchestrator
So, BuyTourSaga definition may seem as follows:
class BuyTourSaga { // ... public function getSagaDefinition(): SagaDefinition { $steps = $this ->step() ->localCommand($buyTourCommand) // <-- compensatable transaction ->withCompensation($rejectTourCommand) // <-- compensating transaction ->step() ->remoteCommand($bookTicketsCommand) ->withCompensation($rejectTicketsBookingCommand) ->step() ->remoteCommand($bookHotelCommand) ->withCompensation($rejectHotelBookingCommand) ->step() ->remoteCommand($obtainVisaCommand) // <-- pivot transaction ->onReply($obtainVisaReplyHandler) ->step() ->remoteCommand($confirmHotelBookingCommand) // <-- retryable transaction ->step() ->remoteCommand($confirmTicketsBookingCommand) ->step() ->localCommand($confirmTourCommand); ; return $steps->build(); } public function onFinished(string $sagaId, SagaDataInterface $data): void { // notify request initiator about successful outcome } public function getSagaType(): string { return 'buy_tour_saga'; } private function step(): StepBuilder { return new StepBuilder(new SagaDefinitionBuilder()); } }
The state machine saga representation:

Be careful when creating steps sequence of the saga definition!
Let's say that the Visa Service is a third party project provided VisaObtain API only (with no possibility of cancellation). In that case all compensatable commands should be placed before the visa obtaining command being a pivot transaction defining the saga outcome (more details about transactions categories).
Also, the each saga lifecycle consists of creation, successful execution or failure that can include some logic implementation (e.g. using event dispatcher) as the request initiator notification upon the saga completion.
The saga can execute both local (same project with orchestrator) and remote commands. The main purpose of the command is to delegate business logic execution to application services and either update saga data (for local commands) or provide data for another microservice's logic execution (for remote commands).
A local command example:
class BuyTourCommand implements LocalCommandInterface { private $tourService; // inject your project service public function __construct(TourService $tourService) { $this->tourService = $tourService; } /** * @param SagaDataInterface|BuyTourSagaData $sagaData */ public function execute(SagaDataInterface $sagaData): void { // buyTour logic incapsulated behind project service, not here $tour = $this->tourService->buyTour( $sagaData->getCountry(), $sagaData->getCity(), $sagaData->getDateFrom(), $sagaData->getDateTill() ); // set created tour id to saga data for next commands usage $sagaData->setTourId($tour->getId()); } public function getSagaDataType(): string { return BuyTourSagaData::class; } }
A remote command example:
class BookTicketsCommand implements RemoteCommandInterface { public function getCommandType(): string { return 'book_tickets_command'; } public function getSagaDataClassName(): string { return BuyTourSagaData::class; } /** * Returns data using by another application services. * * @param SagaDataInterface|BuyTourSagaData $sagaData * * @return CommandDataInterface */ public function getCommandData(SagaDataInterface $sagaData): CommandDataInterface { return new BookTicketsData( $sagaData->getCountry(), $sagaData->getCountry(), $sagaData->getDateFrom(), $sagaData->getDateTill() ); }
Local commands results processing may be performed immediately after the current application service call (see above). In order to process remote commands execution results you have to use ReplyHandler:
class BookHotelReplyHandler implements ReplyHandlerInterface { /** * @param ReplyMessage $message * @param SagaDataInterface|BuyTourSagaData $sagaData */ public function handle(ReplyMessage $message, SagaDataInterface $sagaData): void { if ($message->isSuccess()) { $payload = json_decode($message->getPayload(), true); $sagaData->setHotelBookingId($payload['hotelBookingId']); } } }
Internal
There are three main parts or the orchestrator:
BuildEngine- responsible for saga definition and represents state on execution steps;InstantiationEngine- provides methods for saga and saga instance creation;ExecutionEngine- controls saga execution, manages saga state changes:SagaCreator- starts saga execution;SagaReplyHandler- performs remote commands handling (used by consumers);SagaActionsProcessor- controls saga execution, updates and saves saga state.
Saga execution sequence:
More saga usage details available in package tests.
License
Saga orchestrator is released under the MIT license.
yaoonline/phpsagas-orchestrator 适用场景与选型建议
yaoonline/phpsagas-orchestrator 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 90 次下载、GitHub Stars 达 0, 最近一次更新时间为 2024 年 10 月 18 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「orchestrator」 「sagas」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 yaoonline/phpsagas-orchestrator 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 yaoonline/phpsagas-orchestrator 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 yaoonline/phpsagas-orchestrator 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Enterprise architecture layer for Laravel and Symfony — CQRS, Event Sourcing, Durable Workflows (Sagas, Orchestrators), Projections, and Outbox messaging via PHP attributes.
Ecotone for Laravel — CQRS, Event Sourcing, Sagas, Durable Workflows, and Outbox on top of Laravel Queue, via PHP attributes.
Ecotone for Symfony — CQRS, Event Sourcing, Sagas, Durable Workflows, and Outbox on top of Symfony Messenger, via PHP attributes.
Common classes used by saga participants
Main component controls and coordinates saga participants
Saga pattern implementation
统计信息
- 总下载量: 90
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 23
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2024-10-18
