smart-saga-pattern
Version:
A library implementing the Saga pattern for microservice architecture
191 lines (190 loc) • 7.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.StructuredSagaCoordinator = void 0;
const Saga_1 = require("../core/Saga");
const types_1 = require("../types");
/**
* Coordinates a choreography-based Saga with structured context
* The context is structured as:
* {
* global: { ... },
* steps: {
* "Step Name": {
* input: { ... },
* output: { ... },
* compensation: { ... }
* }
* }
* }
* @template T Type of the input data
* @template R Type of the result data
*/
class StructuredSagaCoordinator extends Saga_1.Saga {
/**
* Creates a new StructuredSagaCoordinator
* @param participant Saga participant for communication
* @param options Options for the coordinator
* @param stateStore Optional state store for persistence
*/
constructor(participant, options, stateStore) {
super(options);
this.participant = participant;
this.stateStore = stateStore;
// Set up event handlers
this.setupEventHandlers();
}
/**
* Executes the Saga
* @param initialData Initial data for the Saga
*/
async execute(initialData) {
try {
// Update context with initial data
if (initialData) {
// Structure the initial data
const structuredData = this.structureInitialData(initialData);
this.updateContext(structuredData);
}
else {
// Initialize empty structured data
this.updateContext({
global: {},
steps: {}
});
}
// Save initial state if state store is provided
if (this.stateStore) {
await this.stateStore.save(this.getId(), this.getContext());
}
// Set up completion promise
this.completionPromise = new Promise((resolve, reject) => {
this.completionResolve = resolve;
this.completionReject = reject;
});
// Start the Saga by publishing the initial event
await this.participant.publish('SAGA_STARTED', this.context.data, this.getId());
// Set status to executing
this.setStatus(types_1.SagaStatus.EXECUTING);
// Update state store if provided
if (this.stateStore) {
await this.stateStore.update(this.getId(), this.context);
}
// Wait for completion
return await this.completionPromise;
}
catch (error) {
this.setStatus(types_1.SagaStatus.FAILED, error instanceof Error ? error.message : String(error));
// Update state store if provided
if (this.stateStore) {
await this.stateStore.update(this.getId(), this.context);
}
return this.getContext();
}
}
/**
* Structures the initial data into the format:
* {
* global: initialData,
* steps: {}
* }
* @param initialData Initial data
*/
structureInitialData(initialData) {
// If the data is already structured, return it as is
if (initialData && typeof initialData === 'object' &&
'global' in initialData && 'steps' in initialData) {
return initialData;
}
// Otherwise, structure it
return {
global: initialData || {},
steps: {}
};
}
/**
* Compensates the Saga (rolls back)
*/
async compensate() {
try {
this.setStatus(types_1.SagaStatus.COMPENSATING);
// Update state store if provided
if (this.stateStore) {
await this.stateStore.update(this.getId(), this.context);
}
// Publish compensation event
await this.participant.publish('SAGA_COMPENSATE', this.context.data, this.getId());
// Wait for completion
return await this.completionPromise;
}
catch (error) {
this.setStatus(types_1.SagaStatus.COMPENSATION_FAILED, error instanceof Error ? error.message : String(error));
// Update state store if provided
if (this.stateStore) {
await this.stateStore.update(this.getId(), this.context);
}
return this.getContext();
}
}
/**
* Sets up event handlers for the coordinator
*/
setupEventHandlers() {
// Handle saga completion
this.participant.on('SAGA_COMPLETED', async (event) => {
this.setStatus(types_1.SagaStatus.COMPLETED);
// Update context with event payload
this.updateContext(event.payload);
// Update state store if provided
if (this.stateStore) {
await this.stateStore.update(this.getId(), this.context);
}
// Resolve completion promise
if (this.completionResolve) {
this.completionResolve(this.getContext());
}
});
// Handle saga failure
this.participant.on('SAGA_FAILED', async (event) => {
this.setStatus(types_1.SagaStatus.FAILED, event.payload.error);
// Update context with event payload
this.updateContext(event.payload);
// Update state store if provided
if (this.stateStore) {
await this.stateStore.update(this.getId(), this.context);
}
// Reject completion promise
if (this.completionReject) {
this.completionReject(new Error(event.payload.error));
}
});
// Handle saga compensation completed
this.participant.on('SAGA_COMPENSATED', async (event) => {
this.setStatus(types_1.SagaStatus.COMPENSATED);
// Update context with event payload
this.updateContext(event.payload);
// Update state store if provided
if (this.stateStore) {
await this.stateStore.update(this.getId(), this.context);
}
// Resolve completion promise
if (this.completionResolve) {
this.completionResolve(this.getContext());
}
});
// Handle saga compensation failed
this.participant.on('SAGA_COMPENSATION_FAILED', async (event) => {
this.setStatus(types_1.SagaStatus.COMPENSATION_FAILED, event.payload.error);
// Update context with event payload
this.updateContext(event.payload);
// Update state store if provided
if (this.stateStore) {
await this.stateStore.update(this.getId(), this.context);
}
// Reject completion promise
if (this.completionReject) {
this.completionReject(new Error(event.payload.error));
}
});
}
}
exports.StructuredSagaCoordinator = StructuredSagaCoordinator;