miércoles, 25 de junio de 2025

Orquestación de Transacciones Distribuidas con Sagas en PHP

Orquestación de Transacciones Distribuidas con Sagas en PHP

En arquitecturas de microservicios, la consistencia de los datos a través de múltiples servicios puede ser un desafío. Una transacción distribuida, donde se realizan operaciones en varios servicios y se necesita garantizar que todas tengan éxito o todas fallen (atomicidad), requiere un manejo cuidadoso. El patrón Saga ofrece una solución elegante para este problema, particularmente en entornos donde no es posible utilizar transacciones ACID tradicionales.

Este artículo explora la implementación del patrón Saga en PHP, centrándonos en la orquestación, donde un orquestrador centralizado coordina las acciones de los servicios participantes.


// Interfaz para las acciones de compensación (rollback)
interface CompensatingAction
{
    public function compensate(): void;
}

// Clase abstracta para las acciones que participan en la Saga
abstract class SagaAction
{
    protected array $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    abstract public function execute(): bool;
}

// Orquestrador de la Saga
class SagaOrchestrator
{
    private array $actions = [];
    private array $executedActions = [];

    public function addAction(SagaAction $action, ?CompensatingAction $compensatingAction = null): void
    {
        $this->actions[] = ['action' => $action, 'compensatingAction' => $compensatingAction];
    }

    public function run(): bool
    {
        foreach ($this->actions as $actionSet) {
            $action = $actionSet['action'];
            $compensatingAction = $actionSet['compensatingAction'];

            if ($action->execute()) {
                $this->executedActions[] = $actionSet;
            } else {
                $this->compensate();
                return false; // Saga fallida
            }
        }

        return true; // Saga exitosa
    }

    private function compensate(): void
    {
        // Revertir las acciones en orden inverso
        foreach (array_reverse($this->executedActions) as $actionSet) {
            if ($actionSet['compensatingAction']) {
                $actionSet['compensatingAction']->compensate();
            }
        }
    }
}
    

El ejemplo anterior define una interfaz `CompensatingAction` para las acciones de rollback y una clase abstracta `SagaAction` para las acciones principales. El `SagaOrchestrator` coordina la ejecución de las acciones, manteniendo un registro de las acciones exitosas. Si una acción falla, el orquestrador invoca la función `compensate`, que recorre las acciones exitosas en orden inverso y llama al método `compensate()` de la acción de compensación asociada, si existe. Esto asegura que, en caso de fallo, se reviertan todos los cambios realizados.


// Ejemplo de uso:

// Acciones específicas de la Saga (ejemplos)
class CreateOrderAction extends SagaAction
{
    public function execute(): bool
    {
        // Lógica para crear una orden en el servicio de órdenes
        echo "Creando orden...\n";
        return true; // Simula éxito
    }
}

class CancelOrderAction implements CompensatingAction
{
    public function compensate(): void
    {
        // Lógica para cancelar una orden
        echo "Cancelando orden...\n";
    }
}

$orchestrator = new SagaOrchestrator();
$orchestrator->addAction(new CreateOrderAction(['order_id' => 123]), new CancelOrderAction());

if ($orchestrator->run()) {
    echo "Saga completada con éxito.\n";
} else {
    echo "Saga fallida.\n";
}
    

Este ejemplo ilustra cómo se pueden agregar acciones específicas a la saga, junto con sus respectivas acciones de compensación. En un escenario real, `CreateOrderAction` y `CancelOrderAction` interactuarían con un servicio de órdenes real. La implementación del patrón Saga requiere una cuidadosa consideración de las acciones de compensación y el manejo de errores para garantizar la consistencia eventual de los datos a través de los servicios involucrados.

No hay comentarios:

Publicar un comentario