Patrón Chain of Responsibility: Pipelines de Procesamiento
design-patternsbehavioralchain-of-responsibility

Intención
Chain of Responsibility evita acoplar el emisor de una solicitud a su receptor dando a más de un objeto la oportunidad de manejar la solicitud. Encadena los objetos receptores y pasa la solicitud a lo largo de la cadena.
El Problema
Sin este patrón, el código de validación se vuelve un if-else interminable:
function handleRequest(req: Request) {
if (!req.headers.auth) {
return { status: 401, body: 'Unauthorized' };
}
if (rateLimiter.isExceeded(req.ip)) {
return { status: 429, body: 'Too many requests' };
}
if (!validateBody(req.body)) {
return { status: 400, body: 'Invalid body' };
}
// Finalmente, la lógica de negocio...
}La Solución: Chain of Responsibility
classDiagram
class Handler {
<<interface>>
+setNext(handler: Handler): Handler
+handle(request): Response
}
class BaseHandler {
-next: Handler
+setNext(handler: Handler): Handler
+handle(request): Response
}
class AuthHandler {
+handle(request): Response
}
class RateLimitHandler {
+handle(request): Response
}
Handler <|.. BaseHandler
BaseHandler <|-- AuthHandler
BaseHandler <|-- RateLimitHandler
BaseHandler o--> HandlerImplementación TypeScript
interface HttpContext {
request: {
method: string;
path: string;
headers: Record<string, string>;
body?: unknown;
user?: { id: string; roles: string[] };
};
response: {
status: number;
body?: unknown;
};
}
abstract class MiddlewareHandler {
private next: MiddlewareHandler | null = null;
setNext(handler: MiddlewareHandler): MiddlewareHandler {
this.next = handler;
return handler;
}
async handle(ctx: HttpContext): Promise<void> {
const shouldContinue = await this.process(ctx);
if (shouldContinue && this.next) {
await this.next.handle(ctx);
}
}
protected abstract process(ctx: HttpContext): Promise<boolean>;
}
// Handlers concretos
class AuthenticationMiddleware extends MiddlewareHandler {
protected async process(ctx: HttpContext): Promise<boolean> {
const token = ctx.request.headers['authorization'];
if (!token?.startsWith('Bearer ')) {
ctx.response.status = 401;
ctx.response.body = { error: 'Authentication required' };
return false;
}
ctx.request.user = { id: 'user_123', roles: ['user'] };
return true;
}
}
class RateLimitMiddleware extends MiddlewareHandler {
private requests = new Map<string, number[]>();
constructor(
private limit: number,
private windowMs: number
) {
super();
}
protected async process(ctx: HttpContext): Promise<boolean> {
const ip = ctx.request.headers['x-forwarded-for'] || 'unknown';
const now = Date.now();
const windowStart = now - this.windowMs;
const timestamps = (this.requests.get(ip) || []).filter(t => t > windowStart);
if (timestamps.length >= this.limit) {
ctx.response.status = 429;
ctx.response.body = { error: 'Too many requests' };
return false;
}
timestamps.push(now);
this.requests.set(ip, timestamps);
return true;
}
}
class LoggingMiddleware extends MiddlewareHandler {
protected async process(ctx: HttpContext): Promise<boolean> {
console.log(`[${new Date().toISOString()}] ${ctx.request.method} ${ctx.request.path}`);
return true;
}
}
// Pipeline builder
class MiddlewarePipeline {
private first: MiddlewareHandler | null = null;
private last: MiddlewareHandler | null = null;
use(handler: MiddlewareHandler): this {
if (!this.first) {
this.first = handler;
this.last = handler;
} else {
this.last!.setNext(handler);
this.last = handler;
}
return this;
}
async execute(ctx: HttpContext): Promise<void> {
if (this.first) {
await this.first.handle(ctx);
}
}
}
// Uso
const pipeline = new MiddlewarePipeline()
.use(new LoggingMiddleware())
.use(new RateLimitMiddleware(100, 60000))
.use(new AuthenticationMiddleware());
await pipeline.execute(ctx);Cuándo Usar
| Situación | Recomendación |
|---|---|
| Middlewares HTTP | ✅ Recomendado |
| Validación en capas | ✅ Recomendado |
| Procesamiento de eventos | ✅ Recomendado |
| Flujo simple lineal | ❌ Considerar alternativas |
Conclusión
Chain of Responsibility permite construir pipelines flexibles donde cada handler tiene una responsabilidad clara.