Recursos

Snippets de Código

Implementaciones concisas de patrones arquitectónicos que podés adaptar a tus proyectos.

🔌 Circuit Breaker

Protege tu sistema de llamadas repetidas a un servicio que está fallando.

class CircuitBreaker {
  private failures = 0;
  private lastFailure: number | null = null;
  private state: 'closed' | 'open' | 'half-open' = 'closed';

  constructor(
    private threshold: number = 5,
    private resetTimeout: number = 30000
  ) {}

  async call<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === 'open') {
      if (Date.now() - (this.lastFailure ?? 0) > this.resetTimeout) {
        this.state = 'half-open';
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess(): void {
    this.failures = 0;
    this.state = 'closed';
  }

  private onFailure(): void {
    this.failures++;
    this.lastFailure = Date.now();
    if (this.failures >= this.threshold) {
      this.state = 'open';
    }
  }
}

// Uso
const breaker = new CircuitBreaker(3, 10000);
const data = await breaker.call(() => fetch('/api/external'));

🔄 Retry con Backoff Exponencial

Reintenta operaciones fallidas con espera incremental entre intentos.

async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  options: {
    maxRetries?: number;
    baseDelay?: number;
    maxDelay?: number;
  } = {}
): Promise<T> {
  const { maxRetries = 3, baseDelay = 1000, maxDelay = 10000 } = options;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries) throw error;

      const delay = Math.min(
        baseDelay * Math.pow(2, attempt) + Math.random() * 1000,
        maxDelay
      );
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }

  throw new Error('Unreachable');
}

// Uso
const result = await retryWithBackoff(
  () => fetch('https://api.example.com/data'),
  { maxRetries: 3, baseDelay: 500 }
);

🗄️ Repository Pattern

Abstrae el acceso a datos detrás de una interfaz limpia.

// Interfaz del repositorio (dominio)
interface UserRepository {
  findById(id: string): Promise<User | null>;
  findByEmail(email: string): Promise<User | null>;
  save(user: User): Promise<void>;
  delete(id: string): Promise<void>;
}

// Implementación concreta (infraestructura)
class PostgresUserRepository implements UserRepository {
  constructor(private db: Database) {}

  async findById(id: string): Promise<User | null> {
    const row = await this.db.query(
      'SELECT * FROM users WHERE id = $1', [id]
    );
    return row ? this.toDomain(row) : null;
  }

  async findByEmail(email: string): Promise<User | null> {
    const row = await this.db.query(
      'SELECT * FROM users WHERE email = $1', [email]
    );
    return row ? this.toDomain(row) : null;
  }

  async save(user: User): Promise<void> {
    await this.db.query(
      `INSERT INTO users (id, name, email)
       VALUES ($1, $2, $3)
       ON CONFLICT (id) DO UPDATE SET name = $2, email = $3`,
      [user.id, user.name, user.email]
    );
  }

  async delete(id: string): Promise<void> {
    await this.db.query('DELETE FROM users WHERE id = $1', [id]);
  }

  private toDomain(row: any): User {
    return new User(row.id, row.name, row.email);
  }
}

📡 Event Emitter Simple

Patrón pub/sub básico para comunicación desacoplada entre componentes.

type EventHandler = (payload: any) => void | Promise<void>;

class EventBus {
  private handlers = new Map<string, EventHandler[]>();

  on(event: string, handler: EventHandler): void {
    const existing = this.handlers.get(event) ?? [];
    this.handlers.set(event, [...existing, handler]);
  }

  off(event: string, handler: EventHandler): void {
    const existing = this.handlers.get(event) ?? [];
    this.handlers.set(event, existing.filter((h) => h !== handler));
  }

  async emit(event: string, payload: any): Promise<void> {
    const handlers = this.handlers.get(event) ?? [];
    await Promise.all(handlers.map((h) => h(payload)));
  }
}

// Uso
const bus = new EventBus();

bus.on('order.created', async (order) => {
  await sendConfirmationEmail(order.email);
});

bus.on('order.created', async (order) => {
  await updateInventory(order.items);
});

await bus.emit('order.created', { id: '123', email: 'user@example.com' });

🔗 Middleware / Pipeline Pattern

Encadena procesadores que transforman un request de forma composable.

type Middleware<T> = (context: T, next: () => Promise<void>) => Promise<void>;

class Pipeline<T> {
  private middlewares: Middleware<T>[] = [];

  use(middleware: Middleware<T>): this {
    this.middlewares.push(middleware);
    return this;
  }

  async execute(context: T): Promise<void> {
    let index = 0;
    const next = async (): Promise<void> => {
      if (index < this.middlewares.length) {
        const middleware = this.middlewares[index++];
        await middleware(context, next);
      }
    };
    await next();
  }
}

// Uso
const pipeline = new Pipeline<Request>()
  .use(async (req, next) => {
    console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
    await next();
  })
  .use(async (req, next) => {
    if (!req.headers.authorization) throw new Error('Unauthorized');
    await next();
  })
  .use(async (req, next) => {
    // Handler principal
    await next();
  });