Skip to content
SP StackPractices
intermediate Por StackPractices

Patrón Front Controller

Enruta todas las peticiones entrantes a través de un único handler que despacha al comando de página apropiado, centralizando el procesamiento de requests y seguridad.

Temas: design

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Patrón Front Controller

Descripción General

El Patrón Front Controller enruta todas las peticiones entrantes a través de un único punto de entrada — un handler central que procesa cada request y lo despacha al comando o controlador apropiado. En lugar de que cada página maneje su propio parsing de request, autenticación y logging, el Front Controller centraliza estos concerns transversales.

Este es el patrón detrás de virtualmente todos los frameworks web modernos. El DispatcherServlet de Spring Boot, el handler WSGI de Django y las cadenas de middleware de Express.js encarnan principios de Front Controller. Un handler recibe cada request, realiza procesamiento común, luego delega a un controlador específico de página.

Cuándo Usar

Usa el Patrón Front Controller cuando:

  • Múltiples páginas comparten preprocesamiento común (autenticación, logging, encoding)
  • Quieres un sistema de routing de URLs limpio e independiente de la estructura de archivos
  • El manejo de requests necesita ser consistente a través de toda la aplicación
  • Necesitas manejo centralizado de errores y chequeos de seguridad

Cuándo Evitar

  • Aplicaciones muy simples con solo unas pocas páginas (overhead sin beneficio)
  • Sitios estáticos donde cada página puede servirse directamente
  • Cuando el front controller se convierte en un God object manejando demasiadas responsabilidades

Solución

Python

from typing import Callable, Dict
from http.server import BaseHTTPRequestHandler, HTTPServer

class Request:
    def __init__(self, path: str, method: str, headers: dict):
        self.path = path
        self.method = method
        self.headers = headers

class Response:
    def __init__(self, body: str, status: int = 200):
        self.body = body
        self.status = status

class FrontController:
    def __init__(self):
        self._handlers: Dict[str, Callable] = {}

    def register(self, path: str, handler: Callable):
        self._handlers[path] = handler

    def dispatch(self, request: Request) -> Response:
        # Preprocesamiento común
        if not self._authenticate(request):
            return Response("Unauthorized", 401)

        handler = self._handlers.get(request.path)
        if handler:
            return handler(request)
        return Response("Not Found", 404)

    def _authenticate(self, request: Request) -> bool:
        return request.headers.get("Authorization") == "Bearer valid"


def home_handler(request: Request) -> Response:
    return Response("Welcome to the home page")

def user_handler(request: Request) -> Response:
    return Response("User profile page")


# Setup
controller = FrontController()
controller.register("/", home_handler)
controller.register("/user", user_handler)

# Uso
req = Request("/user", "GET", {"Authorization": "Bearer valid"})
resp = controller.dispatch(req)
print(resp.status, resp.body)

Java

import java.util.*;

public record Request(String path, String method, Map<String, String> headers) {}
public record Response(String body, int status) {
    public Response(String body) { this(body, 200); }
}

@FunctionalInterface
interface RequestHandler {
    Response handle(Request request);
}

class FrontController {
    private final Map<String, RequestHandler> handlers = new HashMap<>();

    public void register(String path, RequestHandler handler) {
        handlers.put(path, handler);
    }

    public Response dispatch(Request request) {
        if (!authenticate(request)) {
            return new Response("Unauthorized", 401);
        }
        RequestHandler handler = handlers.get(request.path());
        if (handler != null) {
            return handler.handle(request);
        }
        return new Response("Not Found", 404);
    }

    private boolean authenticate(Request request) {
        return "Bearer valid".equals(request.headers().get("Authorization"));
    }
}

// Handlers
class HomeHandler implements RequestHandler {
    public Response handle(Request request) {
        return new Response("Welcome to the home page");
    }
}

class UserHandler implements RequestHandler {
    public Response handle(Request request) {
        return new Response("User profile page");
    }
}

// Uso
FrontController controller = new FrontController();
controller.register("/", new HomeHandler());
controller.register("/user", new UserHandler());

Request req = new Request("/user", "GET", Map.of("Authorization", "Bearer valid"));
Response resp = controller.dispatch(req);
System.out.println(resp.status() + " " + resp.body());

JavaScript

class Request {
  constructor(path, method, headers) {
    this.path = path;
    this.method = method;
    this.headers = headers;
  }
}

class Response {
  constructor(body, status = 200) {
    this.body = body;
    this.status = status;
  }
}

class FrontController {
  constructor() {
    this.handlers = new Map();
  }

  register(path, handler) {
    this.handlers.set(path, handler);
  }

  dispatch(request) {
    if (!this.authenticate(request)) {
      return new Response('Unauthorized', 401);
    }
    const handler = this.handlers.get(request.path);
    if (handler) {
      return handler(request);
    }
    return new Response('Not Found', 404);
  }

  authenticate(request) {
    return request.headers.authorization === 'Bearer valid';
  }
}

// Handlers
const homeHandler = (req) => new Response('Welcome to the home page');
const userHandler = (req) => new Response('User profile page');

// Setup
const controller = new FrontController();
controller.register('/', homeHandler);
controller.register('/user', userHandler);

// Uso
const req = new Request('/user', 'GET', { authorization: 'Bearer valid' });
const resp = controller.dispatch(req);
console.log(resp.status, resp.body);

Explicación

El Front Controller centraliza:

  • Parsing de requests: URL decoding, extracción de parámetros, content negotiation
  • Preprocesamiento: Autenticación, autorización, validación de input
  • Routing: Mapeo de URLs al controlador de página correcto
  • Postprocesamiento: Logging, métricas, formato de respuesta
  • Manejo de errores: Conversión de excepciones en códigos de estado HTTP

Variantes

VarianteMecanismoCaso de Uso
Servlet FilterCadena de filters en Java web appsAutenticación, encoding, compresión
MiddlewareStack de middleware Express.js / DjangoProcesamiento cross-cutting de requests
DispatcherSpring MVC DispatcherServletFramework MVC completo con view resolution
Reverse ProxyNginx / Apache como front controllerLoad balancing, SSL termination, caching

Mejores Prácticas

  • Mantén el controller lean. Delega lógica de página a command objects o sub-controllers dedicados.
  • Usa una routing table. Mapea patrones de URL a handlers declarativamente en lugar de if-else anidados.
  • Procesa concerns comunes primero. Autenticación, protección CSRF y sanitización de input pertenecen aquí.
  • Soporta interceptors. Permite que middleware modifique requests y responses sin cambiar el core.
  • Retorna temprano para errores. Requests inválidos deberían fallar fast antes de llegar a lógica de página.

Errores Comunes

  • Front Controller bloated que sabe demasiado sobre lógica de página. Debería despachar, no implementar.
  • Acoplamiento fuerte a tecnología de view específica. El controller no debería generar HTML directamente.
  • Ignorar semántica HTTP. Retornar 200 para cada response esconde errores de los clientes.
  • Missing error boundaries. Excepciones no manejadas filtran stack traces a los usuarios.
  • Bloqueo síncrono. El front controller no debería realizar operaciones de larga duración inline.

Ejemplos del Mundo Real

Spring MVC

DispatcherServlet es el Front Controller. Recibe todos los HTTP requests, resuelve controllers vía anotaciones y delega renderizado de views a ViewResolver.

Django

La aplicación WSGI de Django actúa como Front Controller. El routing de URLs (urls.py) mapea requests entrantes a views después del procesamiento de middleware.

Express.js

Las apps de Express usan un objeto app central con middleware. app.use(auth) y app.get('/user', handler) construyen un pipeline de Front Controller.

Preguntas Frecuentes

Q: Cuál es la diferencia entre Front Controller y Page Controller? A: Page Controller usa un controlador por página. Front Controller usa un único punto de entrada para toda la aplicación.

Q: Todos los web frameworks usan Front Controller? A: La mayoría de los frameworks modernos sí. El modelo original de PHP (un archivo por página) es el approach alternativo de Page Controller.

Q: Puede un Front Controller manejar conexiones WebSocket? A: Sí. El punto de entrada inspecciona el upgrade header y enruta a un WebSocket handler o un HTTP handler según corresponda.