Skip to content

IYoni/safe_transaction

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Transaction Handler

npm: microspace-transaction-handler

A lightweight yet powerful transaction handler for MongoDB + Express, built with microservices architecture in mind.
It abstracts away session handling, coordination, and rollback logic so your services can run distributed transactions reliably.

Features

  • 🚀 MongoDB Transaction Support: Built-in support with automatic session handling
  • 🔄 Express Middleware: Preconfigured endpoints for transaction control
  • 🏗️ Microservices Ready: Designed for service isolation and inter-service coordination
  • 🌐 Distributed Transactions: Saga and event-driven orchestration patterns supported
  • Auto-Expiration: Configurable TTL with automatic cleanup
  • ⚙️ Environment Config: Customize TTL, limits, and session options via env vars
  • 🛡️ Enhanced Errors: Specific error types with detailed information
  • 🧪 Test Coverage: Comprehensive unit and integration tests
  • 📝 TypeScript: Full type definitions and strict typing

Installation

Package on npm: microspace-transaction-handler

npm install microspace-transaction-handler

Note: This package includes Mongoose as a dependency, so you don't need to install it separately. Mongoose 7.0+ is automatically included with the package.

Why Transaction Handler?

Managing distributed transactions across microservices with MongoDB is complex. This package abstracts away session handling, coordination, and error recovery so you can focus on business logic.

Prerequisites

  • Node.js 16+
  • MongoDB 4.0+ (with replica set for transactions)
  • Express.js 4.18+
  • Mongoose 7.0+

Microservices Architecture Support

This package is specifically designed for microservices environments and provides:

  • Service Isolation: Each microservice can manage its own transactions independently
  • Inter-Service Communication: HTTP endpoints for coordinating transactions across services
  • Distributed Patterns: Support for Saga patterns and distributed transaction coordination
  • Observability: Built-in logging and monitoring for distributed transaction tracking

Architecture Overview

Each service runs its own transaction handler. A coordination layer ensures distributed consistency using Saga/event-driven patterns.

┌───────────────────────────────────────────────────────────────┐
│                    Microservices Architecture                 │
├───────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐            │
│  │User Service │  │Order Service│  │Payment Service│          │
│  │             │  │             │  │             │            │
│  │┌───────────┐│  │┌───────────┐│  │┌───────────┐│            │
│  ││Transaction││  ││Transaction││  ││Transaction││            │
│  ││Handler    ││  ││Handler    ││  ││Handler    ││            │
│  │└───────────┘│  │└───────────┘│  │└───────────┘│            │
│  └─────────────┘  └─────────────┘  └─────────────┘            │
│         │                │                │                   │
│         └────────────────┼────────────────┘                   │
│                          │                                    │
│  ┌───────────────────────┴─────────────────────────────────┐  │
│  │             Transaction Coordination Layer              │  │
│  │  • Saga Pattern Orchestration                           │  │
│  │  • Event-Driven Coordination                            │  │
│  └─────────────────────────────────────────────────────────┘  │
│                          │                                    │
│  ┌───────────────────────┴────────────────────────────────┐   │
│  │                    Data Layer                          │   │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐     │   │
│  │  │User DB      │  │Order DB     │  │Payment DB   │     │   │
│  │  │(MongoDB)    │  │(MongoDB)    │  │(MongoDB)    │     │   │
│  │  └─────────────┘  └─────────────┘  └─────────────┘     │   │
│  └────────────────────────────────────────────────────────┘   │
└───────────────────────────────────────────────────────────────┘

Transaction Flow in Microservices

1. Service A creates transaction
   ┌─────────────┐
   │Service A    │───► Create Transaction
   └─────────────┘

2. Service A performs operations
   ┌─────────────┐
   │Service A    │───► DB Operations (with session)
   └─────────────┘

3. Service A notifies Service B
   ┌─────────────┐    HTTP Call    ┌─────────────┐
   │Service A    │───────────────►│Service B    │
   └─────────────┘                 └─────────────┘

4. Service B joins transaction
   ┌─────────────┐
   │Service B    │───► Join Transaction (via HTTP)
   └─────────────┘

5. All services commit
   ┌─────────────┐    Commit       ┌─────────────┐
   │Service A    │───────────────►│Service B    │
   └─────────────┘                 └─────────────┘

Quick Start

import express from 'express';
import { createTransactionEndpoints } from '@microspace/transaction-handler';

const app = express();
app.use(express.json());

// Set up transaction endpoints
createTransactionEndpoints(app);

app.listen(3000, () => {
  console.log('Server running on port 3000');
  console.log('Transaction endpoints available at /transaction/*');
});

That's it! Your Express server now has transaction management endpoints. See the API Reference for available endpoints.

Basic Transaction Usage

import { TransactionContainer } from '@microspace/transaction-handler';

// Create a transaction
const transactionContainer = TransactionContainer.getInstance();
const transaction = await transactionContainer.createTransaction();

// Use the transaction in your operations
// ... your database operations here ...

// Commit the transaction
await transactionContainer.commitTransaction(transaction.id);

Microservices Patterns

1. Service-to-Service Transaction Coordination

// User Service
import { TransactionContainer, createTransactionEndpoints } from '@microspace/transaction-handler';

const userService = express();

createTransactionEndpoints(userService);

// Create a transaction for user operations
const transaction = await TransactionContainer.getInstance().createTransaction();
// User Service Operations
const user = await User.create(userData, { session: transaction.session });
const profile = await Profile.create(profileData, { session: transaction.session });

// Notify other services about the transaction
await notifyServices('user-created', { transactionId: transaction.id, userId: user.id });
// Commit when all services confirm
await TransactionContainer.getInstance().commitTransaction(transaction.id);

2. Saga Pattern Implementation

// Order Service - Saga Orchestrator
class OrderSaga {
  async createOrder(orderData: OrderData) {
    const transaction = await TransactionContainer.getInstance().createTransaction();
    
    try {
      // Step 1: Create Order
      const order = await this.createOrderInDB(orderData, transaction.session);
      
      // Step 2: Reserve Inventory
      await this.reserveInventory(order.items, transaction.id);      
      // Step 3: Process Payment
      await this.processPayment(order.payment, transaction.id);      
      // Step 4: Send Notification
      await this.sendOrderConfirmation(order, transaction.id);      
      // All steps successful - commit
      await TransactionContainer.getInstance().commitTransaction(transaction.id);
      
    } catch (error) {
      // Rollback all operations
      await this.compensateOrder(order, transaction.id);
      await TransactionContainer.getInstance().rollbackTransaction(transaction.id);
      throw error;
    }
  }
  
  private async compensateOrder(order: Order, transactionId: string) {
    // Compensation logic for each step
    await this.cancelInventoryReservation(order.items, transactionId);
    await this.refundPayment(order.payment, transactionId);
    await this.sendCancellationNotification(order, transactionId);
  }
}

3. Event-Driven Transaction Management

// Event-driven transaction coordination
class TransactionEventHandler {
  async handleUserCreated(event: UserCreatedEvent) {
    const transaction = await TransactionContainer.getInstance().createTransaction();
    
    try {
      // Create user profile
      await Profile.create(event.userData, { session: transaction.session });      
      // Send welcome email
      await this.emailService.sendWelcome(event.userData.email, transaction.id);      
      // Initialize user preferences
      await this.preferencesService.initialize(event.userId, transaction.id);
       // Commit
      await TransactionContainer.getInstance().commitTransaction(transaction.id);
      
    } catch (error) {
      await TransactionContainer.getInstance().rollbackTransaction(transaction.id);
      // Publish compensation event
      await this.eventBus.publish('user-creation-failed', { userId: event.userId });
    }
  }
}

API Reference

TransactionContainer

The main class for managing transactions.

Methods

getInstance(): TransactionContainer

Returns the singleton instance of TransactionContainer.

createTransaction(ttl?: number): Promise<Transaction>

Creates a new transaction with an auto-generated UUID.

  • ttl (optional): Time-to-live in milliseconds (default: 30000)
getTransaction(id: string): Transaction | undefined

Retrieves a transaction by its ID.

commitTransaction(id: string): Promise<void>

Commits a transaction and removes it from the container.

rollbackTransaction(id: string): Promise<void>

Rolls back a transaction (undoes all changes) and removes it from the container.

cancelTransaction(id: string): Promise<void>

Cancels a transaction (marks as aborted and rolls back all changes).

getAllTransactions(): Map<string, Transaction>

Returns all active transactions.

clearAllTransactions(): void

Clears all transactions (useful for testing).

Express Endpoints

The middleware creates the following endpoints:

POST /transaction/commit/:transactionId

Commits a transaction.

Response:

{
  "message": "Transaction committed successfully",
  "transactionId": "uuid-here"
}

POST /transaction/rollback/:transactionId

Rolls back a transaction (undoes all changes).

Response:

{
  "message": "Transaction rolled back successfully",
  "transactionId": "uuid-here"
}

POST /transaction/cancel/:transactionId

Cancels a transaction (marks as aborted and rolls back).

Response:

{
  "message": "Transaction canceled successfully",
  "transactionId": "uuid-here"
}

GET /transaction/status/:transactionId

Gets the status of a transaction.

Response:

{
  "transactionId": "uuid-here",
  "status": "pending",
  "createdAt": 1234567890,
  "ttl": 30000,
  "canceled": false,
  "operationsCount": 0
}

Transaction Interface

interface Transaction {
  id: string;
  ttl: number;
  createdAt: number;
  status: 'pending' | 'committed' | 'rolledback' | 'expired';
  canceled: boolean;
  operations: TransactionOperation[];
  session: ClientSession;
}

TransactionOperation Interface

type TransactionOperation = {
  name: string;
  commit: (session?: ClientSession) => Promise<void>;
  rollback: (session?: ClientSession) => Promise<void>;
};

Advanced Usage

Custom Transaction Operations

import { Transaction, TransactionOperation } from '@microspace/transaction-handler';

const transaction = await transactionContainer.createTransaction();
// Add custom operations
const operation: TransactionOperation = {
  name: 'user-creation',
  commit: async (session) => {
    // Your commit logic here
    await User.create([userData], { session });
  },
  rollback: async (session) => {
    // Your rollback logic here
    await User.deleteOne({ _id: userData._id }, { session });
  }
};

transaction.operations.push(operation);

Error Handling

try {
  const transaction = await transactionContainer.createTransaction();
  
  // Your operations here
  
  await transactionContainer.commitTransaction(transaction.id);
} catch (error) {
  // Handle errors appropriately
  if (transaction) {
    await transactionContainer.rollbackTransaction(transaction.id);
  }
  throw error;
}

Configuration

Environment Variables

  • LOG_LEVEL: Set the logging level (default: 'info')
  • TRANSACTION_DEFAULT_TTL: Default TTL for transactions in milliseconds (default: 30000)
  • TRANSACTION_CLEANUP_INTERVAL: Cleanup interval for expired transactions in milliseconds (default: 30000)
  • TRANSACTION_MAX_CONCURRENT: Maximum number of concurrent transactions (default: 100)
  • TRANSACTION_ENABLE_AUTO_CLEANUP: Enable automatic cleanup of expired transactions (default: true)

Programmatic Configuration

import { updateConfig, getConfig } from '@microspace/transaction-handler';

// Update configuration
updateConfig({
  defaultTtl: 60000, // 60 seconds
  maxConcurrentTransactions: 50,
  enableAutoCleanup: true,
  cleanupInterval: 30000
});

// Get current configuration
const config = getConfig();

console.log('Current TTL:', config.defaultTtl);

MongoDB Configuration

Ensure your MongoDB instance is configured as a replica set for transaction support:

// MongoDB connection with transaction support
mongoose.connect('mongodb://localhost:27017/your-db', {
  replicaSet: 'rs0' // Required for transactions
});

Testing

# Run all tests
npm test

# Run unit tests only
npm run test:unit

# Run integration tests only (requires MongoDB)
npm run test:integration

# Run linting
npm run lint

Microservices Deployment

Development

# Install dependencies
npm install

# Build the package
npm run build

Error Handling

The package provides comprehensive error handling with specific error types:

Error Types

Error Description
TransactionNotFoundError Transaction ID not found
TransactionExpiredError Transaction TTL exceeded
TransactionCanceledError Attempt to commit canceled transaction
MaxConcurrentTransactionsError Limit reached
TransactionOperationError Operation commit/rollback failed
MongoSessionError MongoDB session operations failed
TransactionConfigError Invalid configuration
TransactionValidationError Validation failures

Error Usage

import { 
  TransactionNotFoundError, 
  isTransactionError, 
  getErrorCode 
} from '@microspace/transaction-handler';

try {
  await transactionContainer.commitTransaction('invalid-id');
} catch (error) {
  if (isTransactionError(error)) {
    console.log('Error code:', getErrorCode(error));
    console.log('Transaction ID:', error.transactionId);
  }
}

Automatic Cleanup

The package automatically cleans up expired transactions:

  • Configurable cleanup interval: Set how often to check for expired transactions
  • Automatic rollback: Expired transactions are automatically rolled back (changes undone)
  • Memory management: Prevents memory leaks from long-running transactions
  • Logging: All cleanup operations are logged for monitoring

General Best Practices

  1. Always handle errors: Wrap transaction operations in try-catch blocks
  2. Set appropriate TTL: Configure TTL based on your use case
  3. Monitor transactions: Use the status endpoint to monitor transaction health
  4. Clean up: Ensure transactions are properly committed or rolled back
  5. Test thoroughly: Use the provided test utilities for comprehensive testing
  6. Use environment variables: Configure service-specific settings via environment
  7. Implement health checks: Monitor service and transaction health
  8. Log comprehensively: Include transaction IDs in all log messages

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Ensure all tests pass
  6. Submit a pull request

License

License: MIT (see LICENSE section below for details)

Copyright (c) 2025 Celestial

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Support

For bugs and feature requests, please open an Issue. For private support, contact: [email protected]/[email protected]

Roadmap

We have exciting plans for future releases! Our goals include:

  • Advanced observability - Metrics, tracing integration, and comprehensive monitoring dashboards
  • Automatic Saga orchestration tools - Built-in patterns for complex distributed workflows
  • Performance optimizations - Enhanced throughput and reduced latency
  • Enhanced error handling - More granular error types and recovery strategies
  • Additional middleware options - More Express integration patterns
  • Support more databases beyond MongoDB - Extend to PostgreSQL, MySQL, and other popular databases

More releases coming soon! 🚀

About Microspace

Microspace is a collection of tools and libraries designed specifically for microservices architecture, providing developers with robust, production-ready solutions for building distributed systems.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published