StackPractices
intermediate Por Mathias Paulenko

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.

Temas: design

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.

flowchart diagram: Cliente

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 + ViewResolver de 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 Upgrade pronto 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

VarianteMecanismoCaso de uso
Cadena de filtros servletLos filtros corren antes del controladorAutenticación, codificación, compresión
Stack de middlewareMiddleware de Express / DjangoProcesamiento transversal de peticiones
Servlet dispatcherDispatcherServlet de SpringMVC completo con resolución de vistas
Reverse proxyNginx / Apache en el bordeBalanceo, 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

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.