Patrón Front Controller en Java, Python y PHP
Enruta todas las peticiones entrantes por un único controlador que las despacha al comando de página correspondiente, centralizando el procesamiento y la seguridad.
Visión General
El patrón Front Controller enruta todas las peticiones entrantes por un único punto de entrada — un controlador central que ejecuta la lógica compartida (autenticación, registro, normalización de entrada) y luego despacha al comando o controlador responsable de la página. En lugar de que cada página procese su propia petición, todo ese trabajo transversal vive en un solo lugar.
Es el patrón detrás de prácticamente cualquier framework web moderno. El DispatcherServlet de Spring, el index.php de arranque en Symfony y Laravel, el handler WSGI de Django y la cadena de middleware de Express son todos Front Controllers. Un solo componente recibe la petición, la preprocesa y delega al código que produce la página.
El Problema que Resuelve
En el modelo clásico de la era PHP, cada URL mapeaba a un archivo — home.php, user.php, admin.php — y cada archivo repetía el mismo arranque: conectar a la base de datos, iniciar la sesión, comprobar la autenticación, fijar el idioma. Las páginas divergían; corregir el chequeo de auth obligaba a editar veinte archivos, y uno olvidado era un agujero de seguridad.
Front Controller invierte eso. El servidor web envía todo a un solo archivo, y ese archivo es dueño de la lógica compartida antes de decidir qué código de página ejecutar. El patrón se formalizó en los primeros tiempos de J2EE (Core J2EE Patterns de Sun, después P of EAA de Fowler) como respuesta al manejo de peticiones duplicado, y es la razón por la que los frameworks convirtieron el reescribir a un punto de entrada único en convención en lugar de opción.
Cuándo Usarlo
Usa el patrón Front Controller cuando:
- Varias páginas comparten el mismo preprocesamiento — autenticación, registro, codificación.
- Quieres un enrutamiento de URLs limpio, desacoplado de la estructura de archivos.
- El manejo de peticiones debe ser consistente en toda la aplicación.
- Los controles de seguridad y errores centralizados pesan más que un poco de indirección extra.
Cuándo Evitarlo
- Sitios muy pequeños con unas pocas páginas — la indirección no te compra nada.
- Sitios estáticos donde cada página puede servirse directamente.
- Cuando el controlador empieza a conocer la lógica de páginas — eso es un objeto Dios, no un Front Controller.
Solución
La forma canónica (Patterns of Enterprise Application Architecture de Fowler, Core J2EE Patterns de Oracle) divide el trabajo en tres roles: el front controller que recibe la petición, los comandos que hacen el trabajo específico de cada página y un dispatcher que selecciona la vista. Los frameworks modernos los colapsan en un router más middleware, que es la forma que siguen los ejemplos.
Java — servlet y objetos comando
La forma clásica J2EE: un servlet recibe todas las peticiones, busca un Command y el comando decide qué renderizar.
// Archivo: Command.java
public interface Command {
String process(HttpServletRequest req) throws ServletException;
}
// Archivo: HomeCommand.java
public class HomeCommand implements Command {
public String process(HttpServletRequest req) {
return "/WEB-INF/views/home.jsp";
}
}
// Archivo: FrontControllerServlet.java — mapeado a /* en web.xml o @WebServlet("/*")
@WebServlet("/*")
public class FrontControllerServlet extends HttpServlet {
private final Map<String, Command> commands = new HashMap<>();
@Override
public void init() {
commands.put("/", new HomeCommand());
commands.put("/user", new UserCommand());
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// Preprocesamiento compartido para todas las peticiones
if (!isAuthenticated(req)) {
resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
Command command = commands.get(req.getPathInfo());
if (command == null) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// Despacho: el comando devuelve la vista a renderizar
String view = command.process(req);
req.getRequestDispatcher(view).forward(req, resp);
}
private boolean isAuthenticated(HttpServletRequest req) {
return "Bearer valid".equals(req.getHeader("Authorization"));
}
}
PHP — el bootstrap index.php
Todo framework PHP enruta el tráfico por un solo archivo. El servidor web reescribe cada URL a index.php, que arranca la aplicación, ejecuta el equivalente al middleware y despacha:
<?php
// index.php — todas las peticiones aterrizan aquí
declare(strict_types=1);
$routes = [
'/' => HomeController::class,
'/user' => UserController::class,
];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
// Preprocesamiento compartido: auth antes de cualquier controlador
if (($_SERVER['HTTP_AUTHORIZATION'] ?? '') !== 'Bearer valid') {
http_response_code(401);
exit('Unauthorized');
}
$controllerClass = $routes[$path] ?? null;
if ($controllerClass === null) {
http_response_code(404);
exit('Not Found');
}
$response = (new $controllerClass())->handle();
echo $response;
JavaScript — Express como front controller
El objeto app de Express es un front controller: app.use() registra pasos de preprocesamiento, app.get()/app.post() mapean rutas a handlers.
const express = require('express');
const app = express();
// Preprocesamiento compartido — todas las peticiones pasan por aquí
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`);
next();
});
app.use((req, res, next) => {
if (req.headers.authorization !== 'Bearer valid') {
return res.status(401).send('Unauthorized');
}
next();
});
// Handlers de ruta — los "comandos"
app.get('/', (req, res) => res.send('Welcome to the home page'));
app.get('/user', (req, res) => res.send('User profile page'));
// Despacho de fallback
app.use((req, res) => res.status(404).send('Not Found'));
app.listen(3000);
Explicación
Tres roles hacen funcionar el patrón:
- Front controller — el punto de entrada único. Parsea la petición, ejecuta autenticación y validación, y es dueño del manejo de errores.
- Comandos — la lógica específica de cada página. Cada comando atiende una ruta o acción y devuelve los datos o la vista a renderizar.
- Dispatcher — mapea la petición a un comando y entrega el resultado a la vista. En implementaciones simples el controlador despacha él mismo; los frameworks lo separan (
HandlerMapping+ViewResolverde Spring).
Los filtros de autenticación y validación de entrada suelen correr antes del front controller en su propia cadena — ese es el patrón compañero Intercepting Filter. Y cuando los comandos renderizan vistas, el front controller más los comandos forman la “C” de MVC: la capa de controlador, no toda la arquitectura.
Casos límite que conviene conocer
- Los upgrades a WebSocket saltan el despacho normal — inspecciona la cabecera
Upgradepronto y enrútalos a un handler dedicado. - Las APIs REST suelen saltarse la vista por completo; el “despacho” escribe JSON directamente.
- Los flujos asíncronos (jobs, callbacks) necesitan propagación manual del contexto — el ciclo petición/respuesta termina antes de que corran.
Variantes
| Variante | Mecanismo | Caso de uso |
|---|---|---|
| Cadena de filtros servlet | Los filtros corren antes del controlador | Autenticación, codificación, compresión |
| Stack de middleware | Middleware de Express / Django | Procesamiento transversal de peticiones |
| Servlet dispatcher | DispatcherServlet de Spring | MVC completo con resolución de vistas |
| Reverse proxy | Nginx / Apache en el borde | Balanceo, terminación TLS, caché |
Lo que funciona
- Mantén el controlador delgado. Despacha; no implementa lógica de páginas.
- Usa una tabla de rutas declarativa. Los mapas URL→comando ganan a los if-else anidados.
- Ejecuta primero las preocupaciones compartidas. Autenticación, protección CSRF y sanitización de entrada van en el punto de entrada.
- Soporta interceptores. Deja que el middleware modifique peticiones y respuestas sin tocar el núcleo.
- Fallo rápido. Rechaza peticiones inválidas antes de que lleguen a la lógica de página.
Errores Comunes
- Front Controller inflado que conoce los internos de las páginas. Si acumula lógica de negocio, extráela a comandos.
- Acoplamiento a una tecnología de vista. El controlador no debería generar HTML.
- Ignorar la semántica HTTP. Devolver 200 a todo esconde fallos a clientes y rastreadores.
- Sin barrera de errores. Las excepciones sin capturar filtran trazas de pila — envuelve el despacho en un único catch-all.
- Trabajo bloqueante en línea. Las operaciones largas van detrás del controlador, en una cola.
Ejemplos del Mundo Real
Spring MVC
DispatcherServlet es el front controller: recibe todas las peticiones HTTP, resuelve el handler con HandlerMapping, corre los interceptores y delega el renderizado en ViewResolver. El patrón viene incorporado — registras controladores y Spring se encarga del despacho.
Laravel / Symfony
Ambos frameworks PHP canalizan todas las peticiones por public/index.php, que arranca el kernel, ejecuta el middleware y entrega al router. Las reglas de rewrite de Apache/nginx hacen invisible el punto de entrada único.
Express.js
Las cadenas app.use() más las rutas app.METHOD() son el patrón sin disfraz: el middleware es el preprocesamiento compartido, las rutas son los comandos, y el middleware final de errores es la red de seguridad del dispatcher.
Lecturas Recomendadas
- Front Controller — catálogo P of EAA de Martin Fowler — la definición canónica y su estructura basada en comandos.
- Core J2EE Patterns: Front Controller — Oracle — el tratamiento original Java EE con dispatcher y gestión de vistas.
- Patrón Intercepting Filter — el patrón compañero para pre/post-procesamiento.
- Front controller — Wikipedia — historia y repaso de frameworks.
Preguntas frecuentes
¿Cuál es la diferencia entre Front Controller y Page Controller?
Page Controller usa un controlador por página — cada página maneja su propia petición. Front Controller pone un único punto de entrada delante de todas, lo que centraliza la lógica compartida pero añade un salto de indirección.
¿Es Front Controller lo mismo que MVC?
No. Front Controller es una forma de organizar la C de MVC. MVC también define el Modelo y la Vista; un front controller solo cubre cómo llegan las peticiones a los controladores.
¿Los frameworks modernos siguen usando este patrón?
Sí — la mayoría están construidos sobre él. El DispatcherServlet de Spring, el index.php de Laravel, el handler WSGI de Django y la cadena de middleware de Express son todos front controllers con maquinaria extra alrededor.
¿Puede un front controller manejar conexiones WebSocket?
Puede, siempre que inspeccione la cabecera Upgrade pronto y entregue la conexión a un handler dedicado antes de que corra el despacho HTTP. La mayoría de frameworks tratan los WebSockets como un pipeline separado precisamente por eso.
¿Cuándo es excesivo un front controller?
En un sitio de unas pocas páginas estáticas, o en una API diminuta con dos rutas. El patrón paga cuando varios endpoints comparten preprocesamiento — si no hay nada que compartir, no hay nada que centralizar.
Recursos Relacionados
Patrón Page Controller
Usa un objeto controlador dedicado para cada página lógica en una aplicación web, manejando el request y poblando la view para esa página específica.
PatternIntercepting Filter: Filtros de Petición Reutilizables
Compón responsabilidades transversales en una cadena de filtros reutilizables que interceptan peticiones y respuestas, permitiendo lógica de preprocesamiento y postprocesamiento compartida.
PatternPatrón MVC: Modelo, Vista y Controlador Explicados
Separa una aplicación en capas Modelo, Vista y Controlador. Cuándo aporta MVC, cuándo es sobre-ingeniería y cómo interactúan las piezas.
PatternPatrón Gatekeeper: Seguridad Centralizada en el Borde
Centraliza autenticación, limitación de tasa y sanitización de entrada en el borde del sistema. Patrón Gatekeeper con ejemplos en Python, Java y JavaScript.
PatternPatrón Model-View-Presenter (MVP)
Separa la lógica de presentación de la view introduciendo un presenter que media entre el model y una view pasiva, habilitando código UI testeable.