Main component controls and coordinates saga participants

Overview

Saga Orchestrator

codecov

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

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 phpsagas/orchestrator

Getting started

Configuration

There are some interfaces you have to implement (or use existing implementations):

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: Buy tour saga

The saga execution:
Buy tour saga

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: saga internal More saga usage details available in package tests.

License

Saga orchestrator is released under the MIT license.

You might also like...
Implementing programming best practices and patterns, and creating a custom PHP framework from scratch.

Implementing programming best practices and patterns, and creating a custom PHP framework from scratch.

An issue tracking tool based on hyperf+reactjs for small and medium-sized enterprises, open-source and free, similar to Jira.
An issue tracking tool based on hyperf+reactjs for small and medium-sized enterprises, open-source and free, similar to Jira.

介绍 本项目以 actionview 为蓝本,使用 Hyperf 框架进行重写。 本项目为 Hyperf 框架的 DEMO 项目 原 ActionView 介绍 English | 中文 一个类Jira的问题需求跟踪工具,前端基于reactjs+redux、后端基于php laravel-frame

AbuseIO is a toolkit to receive, process, correlate and notify about abuse reports received by network operators, typically hosting and access providers.

AbuseIO - Abusemanagement tools AbuseIO is a toolkit to receive, process, correlate and notify end users about abuse reports received by network opera

Dictionary of attack patterns and primitives for black-box application fault injection and resource discovery.

FuzzDB was created to increase the likelihood of finding application security vulnerabilities through dynamic application security testing. It's the f

Provides TemplateView and TwoStepView using PHP as the templating language, with support for partials, sections, and helpers.

Aura View This package provides an implementation of the TemplateView and TwoStepView patterns using PHP itself as the templating language. It support

Kit is a lightweight, high-performance and event-driven web services framework that provides core components such as config, container, http, log and route.

Kit What is it Kit is a lightweight, high-performance and event-driven web services framework that provides core components such as config, container,

Yii 2: The Fast, Secure and Professional PHP Framework
Yii 2: The Fast, Secure and Professional PHP Framework

Yii 2 is a modern framework designed to be a solid foundation for your PHP application. It is fast, secure and efficient and works right out of the bo

Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs.

Slim Framework Slim is a PHP micro-framework that helps you quickly write simple yet powerful web applications and APIs. Installation It's recommended

A simple, secure, and scalable PHP application framework

Opulence Introduction Opulence is a PHP web application framework that simplifies the difficult parts of creating and maintaining a secure, scalable w

Owner
null
The Runtime Component enables decoupling applications from global state.

Runtime Component Symfony Runtime enables decoupling applications from global state. This Component is experimental. Experimental features are not cov

Symfony 409 Jan 3, 2023
Prado - Component Framework for PHP 7

Prado PHP Framework PRADO is a component-based and event-driven programming framework for developing Web applications in PHP 7. PRADO stands for PHP R

The PRADO Group 182 Dec 17, 2022
The Semaphore Component manages semaphores, a mechanism to provide exclusive access to a shared resource.

Semaphore Component The Semaphore Component manages semaphores, a mechanism to provide exclusive access to a shared resource. Resources Documentation

Symfony 29 Nov 16, 2022
🎁 Datagrid component project skeleton based on Nette Framework

?? Datagrid component project skeleton based on Nette Framework

Contributte 4 Dec 14, 2022
Slim Framework view helper built on top of the Twig templating component

Slim Framework Twig View This is a Slim Framework view helper built on top of the Twig templating component. You can use this component to create and

Slim Framework 321 Dec 16, 2022
The component provides an array-based DSL to construct complex validation chains.

Spiral Validator The component provides an array-based DSL to construct complex validation chains. Requirements Make sure that your server is configur

Spiral Scout 2 Sep 14, 2022
A easy way to install your basic yii projetc, we have encrypt database password in phpfile, my class with alot funtions to help you encrypt and decrypt and our swoole server install just run ./yii swoole/start and be happy!

Yii 2 Basic Project Template with swoole and Modules Yii 2 Basic Project Template is a skeleton Yii 2 application best for rapidly creating small proj

null 3 Apr 11, 2022
Leaf is a PHP framework that helps you create clean, simple but powerful web apps and APIs quickly and easily.

Leaf is a PHP framework that helps you create clean, simple but powerful web apps and APIs quickly and easily. Leaf introduces a cleaner and much simpler structure to the PHP language while maintaining it's flexibility. With a simple structure and a shallow learning curve, it's an excellent way to rapidly build powerful and high performant web apps and APIs.

Leaf Framework 706 Jan 3, 2023
Symprowire is a PHP MVC Framework based and built on Symfony, using the ProcessWire CMS as DBAL and Service Provider.

Symprowire - PHP MVC Framework for ProcessWire 3.x Symprowire is a PHP MVC Framework based and built on Symfony using ProcessWire 3.x as DBAL and Serv

Luis Mendez 7 Jan 16, 2022
FlyCubePHP is an MVC Web Framework developed in PHP and repeating the ideology and principles of building WEB applications, embedded in Ruby on Rails.

FlyCubePHP FlyCubePHP is an MVC Web Framework developed in PHP and repeating the ideology and principles of building WEB applications, embedded in Rub

Anton 1 Dec 21, 2021