Storage Layer
Storage Interface
Section titled “Storage Interface”All storage implementations must implement the ITransactionStorage interface:
interface ITransactionStorage { // Lifecycle close?(): Promise<void>;
// Distributed lock acquireLock(key: string, ttl: number): Promise<string | null>; releaseLock(key: string, token: string): Promise<boolean>;
// Transaction log saveTransaction(tx: TransactionLog): Promise<void>; getTransaction(id: string): Promise<TransactionLog | null>; updateTransactionState(id: string, state: TransactionState): Promise<void>; updateOperationState(txId: string, opIndex: number, state: string, error?: string): Promise<void>; getPendingTransactions(serverId?: string): Promise<TransactionLog[]>; deleteTransaction(id: string): Promise<void>;
// Data operations get<T>(key: string): Promise<T | null>; set<T>(key: string, value: T, ttl?: number): Promise<void>; delete(key: string): Promise<boolean>;}MemoryStorage
Section titled “MemoryStorage”In-memory storage, suitable for development and testing.
import { MemoryStorage } from '@esengine/transaction';
const storage = new MemoryStorage({ maxTransactions: 1000, // Maximum transaction log count});
const manager = new TransactionManager({ storage });Characteristics
Section titled “Characteristics”- ✅ No external dependencies
- ✅ Fast, good for debugging
- ❌ Data only stored in memory
- ❌ No true distributed locking
- ❌ Data lost on restart
Test Helpers
Section titled “Test Helpers”// Clear all datastorage.clear();
// Get transaction countconsole.log(storage.transactionCount);RedisStorage
Section titled “RedisStorage”Redis storage, suitable for production distributed systems. Uses factory pattern with lazy connection.
import Redis from 'ioredis';import { RedisStorage } from '@esengine/transaction';
// Factory pattern: lazy connection, connects on first operationconst storage = new RedisStorage({ factory: () => new Redis('redis://localhost:6379'), prefix: 'tx:', // Key prefix transactionTTL: 86400, // Transaction log TTL (seconds)});
const manager = new TransactionManager({ storage });
// Close connection when doneawait storage.close();
// Or use await using for automatic cleanup (TypeScript 5.2+)await using storage = new RedisStorage({ factory: () => new Redis('redis://localhost:6379')});// Automatically closed when scope endsCharacteristics
Section titled “Characteristics”- ✅ High-performance distributed locking
- ✅ Fast read/write
- ✅ Supports TTL auto-expiration
- ✅ Suitable for high concurrency
- ❌ Requires Redis server
Distributed Lock Implementation
Section titled “Distributed Lock Implementation”Uses Redis SET NX EX for distributed locking:
// Acquire lock (atomic operation)SET tx:lock:player:123 <token> NX EX 10
// Release lock (Lua script for atomicity)if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1])else return 0endKey Structure
Section titled “Key Structure”tx:lock:{key} - Distributed lockstx:tx:{id} - Transaction logstx:server:{id}:txs - Server transaction indextx:data:{key} - Business dataMongoStorage
Section titled “MongoStorage”MongoDB storage, suitable for scenarios requiring persistence and complex queries. Uses shared connection from @esengine/database-drivers.
import { createMongoConnection } from '@esengine/database-drivers';import { createMongoStorage, TransactionManager } from '@esengine/transaction';
// Create shared connectionconst mongo = createMongoConnection({ uri: 'mongodb://localhost:27017', database: 'game'});await mongo.connect();
// Create storage using shared connectionconst storage = createMongoStorage(mongo, { transactionCollection: 'transactions', // Transaction log collection (optional) dataCollection: 'transaction_data', // Business data collection (optional) lockCollection: 'transaction_locks', // Lock collection (optional)});
// Create indexes (run on first startup)await storage.ensureIndexes();
const manager = new TransactionManager({ storage });
// Close storage (does not close shared connection)await storage.close();
// Shared connection can continue to be used by other modulesconst userRepo = new UserRepository(mongo); // @esengine/database
// Finally close the shared connectionawait mongo.disconnect();Characteristics
Section titled “Characteristics”- ✅ Persistent storage
- ✅ Supports complex queries
- ✅ Transaction logs are traceable
- ✅ Suitable for audit requirements
- ❌ Slightly lower performance than Redis
- ❌ Requires MongoDB server
Index Structure
Section titled “Index Structure”// transactions collection{ state: 1 }{ 'metadata.serverId': 1 }{ createdAt: 1 }
// transaction_locks collection{ expireAt: 1 } // TTL index
// transaction_data collection{ expireAt: 1 } // TTL indexDistributed Lock Implementation
Section titled “Distributed Lock Implementation”Uses MongoDB unique index for distributed locking:
// Acquire lockdb.transaction_locks.insertOne({ _id: 'player:123', token: '<token>', expireAt: new Date(Date.now() + 10000)});
// If key exists, check if expireddb.transaction_locks.updateOne( { _id: 'player:123', expireAt: { $lt: new Date() } }, { $set: { token: '<token>', expireAt: new Date(Date.now() + 10000) } });Storage Selection Guide
Section titled “Storage Selection Guide”| Scenario | Recommended Storage | Reason |
|---|---|---|
| Development/Testing | MemoryStorage | No dependencies, fast startup |
| Single-machine Production | RedisStorage | High performance, simple |
| Distributed System | RedisStorage | True distributed locking |
| Audit Required | MongoStorage | Persistent logs |
| Mixed Requirements | Redis + Mongo | Redis for locks, Mongo for logs |
Custom Storage
Section titled “Custom Storage”Implement ITransactionStorage interface to create custom storage:
import { ITransactionStorage, TransactionLog, TransactionState } from '@esengine/transaction';
class MyCustomStorage implements ITransactionStorage { async acquireLock(key: string, ttl: number): Promise<string | null> { // Implement distributed lock acquisition }
async releaseLock(key: string, token: string): Promise<boolean> { // Implement distributed lock release }
async saveTransaction(tx: TransactionLog): Promise<void> { // Save transaction log }
// ... implement other methods}