FLASK COMO NUEVA CAPA DE PRESENTACIÓN¶
Flask en la arquitectura por capas¶
Qué es Flask¶
Flask es un microframework web para Python. Permite escribir funciones Python normales y asociarlas a URLs: cuando el navegador pide una URL, Flask llama a la función correspondiente y envía al navegador lo que esa función devuelve.
No es un framework pesado por lo que encaja bien con una arquitectura por capas que hemos ido viendo hasta ahora.
Arquitectura modular de Flask¶
Flask está construido como un núcleo pequeño rodeado de extensiones opcionales. El núcleo solo se ocupa de lo imprescindible para una aplicación web: enrutamiento (asociar URLs a funciones), gestión de peticiones y respuestas HTTP, y un motor de plantillas (Jinja2). Cualquier otra cosa se añade como una pieza más.
| Funcionalidad | Cómo se consigue |
|---|---|
| Enrutamiento, peticiones, respuestas, plantillas | Núcleo de Flask (ya incluido) |
| Acceso a base de datos con ORM | Extensión: Flask-SQLAlchemy |
| Formularios con validación | Extensión: Flask-WTF |
| Autenticación de usuarios | Extensión: Flask-Login |
| Envío de correos | Extensión: Flask-Mail |
Cada extensión es un paquete Python independiente que se instala con pip solo si es necesario.
Esta modularidad hace sencillo integrar proyectos estructurados mediante arquitectura por capas: Flask se ocupa de la capa de presentación y no invade las demás. Si más adelante se cambia el tipo de base de datos, el núcleo de Flask no se entera — solo cambia el componente que habla con la BD.
Instalar solo lo que se usa
Cada extensión añadida es una dependencia que habrá que mantener, documentar y, eventualmente, actualizar. Conviene empezar con el núcleo y añadir extensiones solo cuando el proyecto lo pida de forma clara.
Cómo lo integramos¶
Flask no añade una capa nueva al proyecto. Añade un módulo nuevo dentro de la capa de presentación que ya tenemos.
La capa de presentación es la que interactúa con el usuario: le muestra datos y recoge su entrada. En la expendedora esa capa era menu.py, con print() e input(). Ahora vas a añadir app.py en la misma carpeta, como un segundo módulo de la misma capa — uno habla con consola, el otro con el navegador.
presentation/
├── menu.py ← interfaz de consola (ya existía)
└── app.py ← interfaz web con Flask (nuevo)
Las otras tres capas (
application,domain,infrastructure) no cambian. El servicio, la máquina y el repositorio no saben si los llamamenu.pyoapp.py— por eso puedes enchufar Flask sin tocarlos.
Del modelo standalone al modelo cliente/servidor¶
Hasta ahora tu aplicación era standalone: un único proceso Python corriendo en el equipo del usuario. Todo vive en la misma máquina, sin red.
Con Flask la aplicación pasa a ser cliente/servidor: el proceso Python (Flask) actúa como servidor y queda esperando peticiones; el cliente es cualquier navegador que apunte a la URL del servidor. La comunicación entre ambos va por HTTP, el protocolo de la web.
| Aspecto | Standalone (menu.py) |
Cliente/servidor (app.py) |
|---|---|---|
| Dónde corre el código | Equipo del usuario | Servidor (local o remoto) |
| Interfaz de usuario | Terminal | Navegador |
| Mecanismo de entrada | input() bloqueante |
Petición HTTP |
| Mecanismo de salida | print() a consola |
Respuesta HTTP con HTML |
| Número de usuarios simultáneos | Uno | Muchos |
HTTP en una frase
Cuando escribimos http://localhost:5000/productos en el navegador, enviamos una petición HTTP al servidor Flask con un método (GET), una URL y unas cabeceras. Flask ejecuta el código asociado a esa URL y nos devuelve una respuesta HTTP con un código de estado (200 OK, 404 Not Found…), unas cabeceras y un cuerpo (el HTML que veremos).
Equivalencia entre menu.py y Flask¶
Cambia la interfaz, no la lógica. Todo lo que hacías en menu.py tiene un equivalente directo en Flask.
Tabla de equivalencias¶
En menu.py |
En Flask |
|---|---|
print("...") |
HTML devuelto por la función de vista |
input("...") |
Datos recogidos de la petición (formulario, sesión 3) |
if opcion == "1": |
@app.route("/productos") |
Bucle while True: |
El servidor Flask (app.run()) |
| Una función por opción | Una función por ruta |
La URL es la opción. La función es la acción.
@app.routees lo que las conecta.
El bucle while True desaparece de tu código — lo sustituye el servidor Flask, que queda escuchando peticiones hasta que lo detengas con Ctrl+C.
La cadena de construcción del proyecto¶
El problema: Flask también necesita el servicio¶
Antes de responder a peticiones, Flask tiene que construir los mismos objetos iniciales del proyecto. En el caso de la expendedora, que ya construía menu.py: un repositorio, una máquina que lo use, y un servicio que coordine los casos de uso.
repo = RepositorioProductosSQLite("expendedora.db")
maquina = MaquinaExpendedora(repo)
servicio = ServicioExpendedora(maquina)
Esta secuencia repo → máquina → servicio es la cadena de construcción del proyecto. menu.py ya la montaba antes de entrar al bucle del menú. app.py necesita hacer exactamente lo mismo.
La función de inicio (bootstrap)¶
Para no repetir la cadena de construcción en dos sitios, el proyecto la tiene encapsulada en una función: crear_servicio_sqlite() en infrastructure/datos_iniciales.py.
A este tipo de función se le llama función de inicio o, en inglés, bootstrap. El término viene de la expresión pull yourself up by your bootstraps — el arranque de un sistema que se "autoinicia" ensamblando sus propias piezas, igual que un ordenador al encenderse ejecuta un programa mínimo que carga otro más grande, que carga el sistema operativo.
crear_servicio_sqlite()hace exactamente eso: monta repo, máquina y servicio en una sola llamada y te devuelve el servicio listo para usar. Tantomenu.pycomoapp.pyla usan — cero duplicación.
Centraliza el bootstrap en una sola función
Si en el futuro cambiamos la base de datos, añadimos caché o introducimos logging, solo tendremos que tocar la función de bootstrap. Ningún módulo de presentación se entera.
Routes y funciones de vista¶
El decorador @app.route()¶
Un route es la asociación entre una URL y una función Python. En Flask se declara con el decorador @app.route():
La función decorada se llama función de vista. Es una función Python normal — recibe argumentos, devuelve un valor. Lo único especial es su asociación con una URL.
Lo que la función de vista devuelve se convierte en el cuerpo de la respuesta HTTP que recibe el navegador. Flask se ocupa de envolver ese valor con el código de estado (
200 OKpor defecto), las cabeceras (Content-Type: text/html) y el resto del protocolo.
Qué devuelve Flask al navegador¶
Aunque tu función de vista solo escriba un return, la respuesta HTTP completa que sale por la red tiene tres partes:
HTTP/1.1 200 OK ← línea de estado
Content-Type: text/html ← cabeceras
Content-Length: 42
<h1>Expendedora</h1> ← cuerpo (lo que devolviste)
Nosotros escribimos solo el cuerpo. Flask construye las otras dos partes automáticamente.
Rutas con parámetro variable¶
A veces la URL tiene partes que cambian: el código del producto, un identificador de usuario, etc. Para capturar esas partes se usa un parámetro variable entre <>:
Cuando el navegador pide /producto/A1, Flask extrae "A1", lo pasa como argumento codigo a la función y ejecuta el resto.
| URL solicitada | Valor de codigo |
|---|---|
/producto/A1 |
"A1" |
/producto/D1 |
"D1" |
/producto/ZZ |
"ZZ" |
El nombre entre
<>en la URL debe coincidir con el nombre del parámetro en la función. Si escribimos/producto/<codigo>la función debe aceptar un parámetro llamadocodigo.
Excepciones en la capa de presentación¶
Dónde capturar las excepciones de dominio¶
El dominio lanza excepciones cuando algo no cuadra con las reglas de negocio: ProductoNoEncontradoError, ProductoYaExisteError, ErrorPersistencia. Estas excepciones viajan hacia arriba — las lanza el repositorio, pasan por el dominio, pasan por el servicio y llegan a la capa de presentación.
La capa de presentación es quien decide cómo informar al usuario. En menu.py esa decisión era "imprimir el mensaje por consola". En Flask, la decisión es "devolver una respuesta HTTP con un mensaje de error y un código 404".
En menu.py la captura estaba en el bucle del menú:
En Flask la captura está en la función de vista del route, por la misma razón: el route es la nueva capa de presentación.
El segundo elemento del return (404) le dice a Flask que el código de estado de la respuesta HTTP no es 200 OK sino 404 Not Found — el código estándar para "recurso no existe".
La estructura del
try/exceptes idéntica a la demenu.py. Solo cambia lo que hacemos con el error: antes un
Mala práctica: capturar la excepción en servicios.py
## Incorrecto: la capa de aplicación se convierte en responsable del formato de error
def obtener_producto(self, codigo):
try:
return self._maquina.obtener_producto(codigo)
except ProductoNoEncontradoError:
return "Error: producto no encontrado"
Si el servicio captura la excepción, pierdes la posibilidad de que distintas capas de presentación la traten de formas distintas: menu.py quiere hacer print, Flask quiere devolver un 404, una API quiere devolver JSON. La capa de aplicación propaga las excepciones; la capa de presentación decide qué hacer con ellas.