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();
});