Saga Module for Nestjs framework
Description
This is a NestJS module designed to facilitate the implementation and management of sagas. Sagas are a pattern for managing distributed transactions and long-running processes in a reliable and consistent manner. This package provides a structured way to define, execute, and handle compensations for sagas, ensuring that your application can maintain data consistency across multiple services and operations.
Installation
npm install @mahyar-kd/nestjs-saga
OR
yard add @mahyar-kd/nestjs-saga
Usage
Create Saga Command
This class represents a command that will be used in the saga. It contains the data required for the saga to execute.
export class SampleSagaCommand {
constructor(
public readonly sampleData: any,
) {}
}
Create Saga Step
This class implements the SagaStep
interface and defines the steps of the saga. It includes methods for invoking the step and handling compensation in case of failure.
import { SagaStep } from '@mahyar-kd/nestjs-saga';
@Injectable()
export class SampleSagaStep implements SagaStep {
private logger: Logger = new Logger(SampleSagaStep.name, {
timestamp: true,
});
constructor(
@Inject(CommandBus)
public readonly commandBus: CommandBus,
@Inject(QueryBus)
private readonly queryBus: QueryBus,
) {}
/**
* Invokes the saga step with the given command.
* @param cmd - The command to be executed in this step.
*/
async invoke(cmd: SampleSagaCommand) {}
/**
* Handles compensation logic for the saga step in case of failure.
* @param cmd - The command to be compensated.
*/
async withCompensation(cmd: SampleSagaCommand) {}
}
Create Saga
This class defines the saga itself, using the SagaBuilder
to chain the steps together. It specifies the command to be used and the steps to be executed.
import { SagaStep } from '@mahyar-kd/nestjs-saga';
@Saga(SampleSagaCommand)
export class SampleSaga {
constructor(
@Inject(SampleSagaStep)
public readonly sampleSagaStep: SampleSagaStep,
) {}
// Define `saga` field using Builder<SampleSagaCommand> or if you want to return
// some value use: SagaBuilder<SampleSagaStep, SampleSagaResponse>
saga = new SagaBuilder<SampleSagaCommand>()
.step(SampleSagaStep.name)
.invoke(async (cmd: SampleSagaCommand) =>
this.sampleSagaStep.invoke(cmd),
)
.withCompensation(async (cmd: SampleSagaCommand) =>
this.sampleSagaStep.withCompensation(cmd),
)
}
Executing the Saga Command
This code snippet shows how to execute the saga command using the CommandBus
. It also demonstrates how to handle exceptions using the SagaExceptionConverterHelper
.
await this.commandBus
.execute(new SampleSagaCommand(sampleData))
.catch((error) =>
SagaExceptionConverterHelper.toHttpException(error.originalError),
);