Skip to content

A TypeScript library for implementing the Saga pattern to manage distributed transactions and complex workflows πŸš€

License

Notifications You must be signed in to change notification settings

dngiang2003/saga-transaction-lib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Saga Transaction Library

A TypeScript library for implementing the Saga pattern to manage distributed transactions and complex workflows.

Features

  • TypeScript-first implementation
  • Flexible and extensible architecture
  • Built-in error handling and compensation
  • Customizable logging
  • Transaction context management
  • Decorators for fine-grained control over step execution and compensation

Installation

npm install saga-transaction-lib

Configuration

TypeScript Configuration

To use the decorators in this library, you need to enable experimental decorators in your tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true
    ... other options
  }
}

Basic Usage

import { Saga, IStep } from 'saga-transaction-lib';

// 1. Define your context type
interface MyContext {
  // your context properties
}

// 2. Implement your steps
class MyStep implements IStep<MyContext> {
  name = 'My Step';

  async invoke(context: MyContext): Promise<void> {
    // Implementation
  }

  async compensate(context: MyContext): Promise<void> {
    // Compensation logic
  }
}

// 3. Create and execute the saga
async function runMyTransaction() {
  const saga = new Saga<MyContext>();
  const context: MyContext = {
    /* ... */
  };
  const steps: IStep<MyContext>[] = [new MyStep()];

  try {
    const result = await saga.execute(context, steps);
    console.log('Transaction completed successfully');
  } catch (error) {
    console.error('Transaction failed:', error);
  }
}

Advanced Usage

Using Decorators

The library provides two decorators for fine-grained control over step execution and compensation:

@BeforeInvoke & @BeforeCompensate()

  • The @BeforeInvoke decorator checks if the step can proceed by returning true. If it does, the invoke method is executed.
  • The @BeforeCompensate decorator does not require any condition checks and will directly execute the compensation logic when necessary.
import { BeforeInvoke, IStep } from 'saga-transaction-lib';

class MyStep implements IStep<MyContext> {
  name = 'My Step';

  @BeforeInvoke<MyStep, MyContext>(async ({ instance, context }) => {
    console.log(`Preparing to execute ${instance.name}`);
    // Perform checks or preparations
    return true; // Return true to proceed, false to skip this step
  })
  async invoke(context: MyContext): Promise<void> {
    // Step implementation
  }

  @BeforeCompensate()
  async compensate(context: MyContext): Promise<void> {
    // Compensation logic
  }
}

Custom Logger

import { ILogger } from 'saga-transaction-lib';

class CustomLogger implements ILogger {
  log(message: string): void {
    // Custom implementation
  }

  error(message: string, error?: Error): void {
    // Custom implementation
  }

  warn(message: string): void {
    // Custom implementation
  }

  debug(message: string): void {
    // Custom implementation
  }
}

Custom Error Handler

import { IErrorHandler, TransactionContext } from 'saga-transaction-lib';

class CustomErrorHandler<TContext>
  implements IErrorHandler<TransactionContext<TContext>>
{
  async handleError(
    error: Error,
    context: TransactionContext<TContext>
  ): Promise<void> {
    // Custom error handling logic
  }
}

NestJS Saga Module

// saga.module.ts
import { Module, DynamicModule } from '@nestjs/common';
import { Saga, SagaOptions } from 'saga-transaction-lib';

@Module({})
export class SagaModule {
  static forRoot<TContext>(options?: SagaOptions<TContext>): DynamicModule {
    return {
      module: SagaModule,
      providers: [
        {
          provide: 'SAGA',
          useFactory: () => new Saga<TContext>(options),
        },
      ],
      exports: ['SAGA'],
    };
  }
}

// app.module.ts
@Module({
  imports: [
    SagaModule.forRoot<YourContextType>({
      logger: new CustomLogger(),
    }),
  ],
})
export class AppModule {}

// your.service.ts
@Injectable()
export class YourService {
  constructor(
    @Inject('SAGA')
    private readonly saga: Saga<YourContextType>
  ) {}

  async executeTransaction() {
    // Use saga here
  }
}

Best Practices

  1. Keep steps atomic and focused
  2. Implement proper compensation logic
  3. Use meaningful step names
  4. Handle errors appropriately
  5. Use typing to ensure compile-time safety
  6. Use decorators to add pre-execution and pre-compensation logic when needed
  7. Ensure isBreakStep is properly managed in your step implementations

Example Usage

A detailed example can be found in the main.ts file located in the example folder. This example demonstrates how to implement a transaction step using both the @BeforeInvoke and @BeforeCompensate decorators.

License

MIT

Contributing

Contributions are welcome! Please read our contributing guidelines and submit pull requests.

Support

If you have any questions or need help, please open an issue on GitHub.

About

A TypeScript library for implementing the Saga pattern to manage distributed transactions and complex workflows πŸš€

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published