Saltar a contenido

3.3.-Estructura de una aplicación web

3.3. Estructura de una aplicación web

Idea principal

Antes de desplegar una aplicación hay que saber qué hemos recibido, qué proceso la ejecuta, qué dependencias necesita, qué configuración cambia y qué datos deben sobrevivir. Un inventario razonado evita tratar una web estática, una aplicación PHP, un proyecto Node.js y un WAR de Java como si fueran el mismo tipo de entrega.

Este punto concreta la arquitectura estudiada en el apartado 3.1 y utiliza la comunicación HTTP del apartado 3.2 para seguir los recursos que intervienen en una petición. Su finalidad es analizar la aplicación y sus recursos antes de decidir cómo se instalará el entorno. Los requisitos de CPU, memoria, sistema operativo y red se desarrollarán en el punto 3.4, Requisitos de implantación.

La normativa del módulo profesional 0614 relaciona este contenido con:

Código Descripción
RA1 Implanta arquitecturas web analizando y aplicando criterios de funcionalidad.
CE g Se ha analizado la estructura y recursos que componen una aplicación web.

Qué deberías saber al terminar

Al acabar este tema deberías poder:

  • distinguir código fuente, artefacto, configuración y datos en un proyecto;
  • reconocer los directorios y archivos relevantes de aplicaciones web habituales;
  • identificar las dependencias de construcción y de ejecución;
  • localizar entradas, puertos, integraciones y datos persistentes;
  • elaborar un inventario que justifique qué se despliega y qué se mantiene fuera de la aplicación.

Mapa del tema

Seguiremos esta secuencia:

  1. del código fuente a la aplicación en ejecución;
  2. clasificación de los recursos;
  3. lectura de estructuras habituales y artefactos;
  4. relación entre componentes en tiempo de ejecución;
  5. inspección e inventario antes del despliegue.

1. Del código fuente a la aplicación en ejecución

Analizar una aplicación no consiste en abrir todos sus archivos sin criterio. Consiste en encontrar evidencias que permitan responder preguntas concretas:

  • ¿Qué proceso recibe una petición y cuál es su punto de entrada?
  • ¿Qué archivos se sirven directamente y cuáles se interpretan o compilan?
  • ¿Qué dependencias se necesitan para construir y para ejecutar la aplicación?
  • ¿Qué valores cambian entre desarrollo, pruebas y producción?
  • ¿Qué información debe conservarse al reiniciar o recrear un proceso?
  • ¿Cuál es el artefacto que se ha de instalar, copiar o ejecutar?

Código fuente, artefacto y ejecución

El código fuente es el conjunto de archivos que el equipo modifica. Un artefacto es una salida identificable del proceso de construcción, preparada para distribuirse o desplegarse, como dist/, un archivo WAR o una imagen Docker. La ejecución es el momento en que un servidor o un runtime utiliza ese artefacto junto con su configuración y sus datos.

Estas tres cosas no son intercambiables. Por ejemplo, pom.xml describe cómo construir un proyecto Java, target/catalogo.war es una salida construida y Tomcat es el runtime que puede desplegar ese WAR. Del mismo modo, un Dockerfile es una receta; la imagen construida es el artefacto y el contenedor es una instancia en ejecución.

flowchart LR
  S["Codigo fuente<br/>src, public, package.json"] --> B["Construccion<br/>dependencias y pruebas"]
  B --> A["Artefacto<br/>dist, WAR o imagen"]
  A --> R["Ejecucion<br/>servidor y configuracion"]
  R --> D["Datos persistentes<br/>base de datos y ficheros"]

El esquema también explica qué no debe confundirse:

Elemento Qué representa Ejemplos
Fuente Lo que se edita y versiona src/, public/, pom.xml
Salida de construcción Resultado reproducible de una build dist/, target/app.war
Runtime Programa que ejecuta o sirve la aplicación Nginx, PHP-FPM, Node.js, Tomcat
Descriptor de despliegue Cómo se conectan servicios y recursos compose.yaml, manifiesto, configuración de Nginx
Datos Estado que la aplicación crea o consulta Tablas, cargas de usuarios, ficheros subidos

Un compose.yaml puede describir un entorno formado por varios servicios, redes y volúmenes, pero no sustituye al código ni convierte los datos en parte de la imagen. Esta distinción será esencial al trabajar con contenedores.

2. Clasificación de los recursos

Clasificar los recursos ayuda a saber quién los utiliza, cuándo cambian y qué protección necesitan. Un mismo proyecto puede contener recursos de varias categorías.

Tipo de recurso Función Ejemplos Tratamiento habitual
Código fuente Define comportamiento y reglas Controladores, servicios, server.js, index.php Se versiona y se construye o interpreta
Estático Se entrega sin ejecutar lógica de negocio HTML, CSS, JavaScript, imágenes, fuentes Lo sirve el servidor web o una CDN
Dinámico Se procesa en el servidor PHP, servlets, controladores, plantillas Necesita un runtime y, a menudo, datos
Configuración Adapta la aplicación al entorno YAML, XML, JSON, .env.example Se externaliza y se valida por entorno
Dependencias Aporta bibliotecas o herramientas externas pom.xml, package.json, composer.json Se fijan versiones y se instalan de forma reproducible
Persistente Conserva estado entre reinicios Base de datos, cargas y documentos Vive en un volumen o servicio de almacenamiento
Transitorio Se puede recrear o eliminar Caché, temporales, logs de una instancia Se gestiona fuera del artefacto o con una política clara
Distribución Permite transportar y desplegar WAR, dist/, imagen Docker Se etiqueta, verifica y publica

2.1. Estático no significa necesariamente «escrito a mano»

Un archivo que el servidor entrega sin interpretarlo es estático en tiempo de ejecución, aunque se haya generado durante la construcción. Por ejemplo, app.scss es una fuente, mientras que dist/app.css es un recurso estático generado. Esta diferencia permite localizar si un fallo está en la build o en el servidor web.

2.2. Configuración, secretos y datos

La configuración contiene valores necesarios para ejecutar el servicio, como el puerto o el nombre del host de la base de datos. Los secretos —contraseñas, tokens y claves privadas— son una clase especialmente sensible de configuración y no deben guardarse en el repositorio ni dentro de una imagen.

No confundas plantilla con secreto

.env.example puede documentar los nombres de las variables necesarias y valores de ejemplo no sensibles. El archivo .env real debe excluirse del control de versiones y sus valores deben proceder de un mecanismo seguro de configuración. Cambiar una variable no debería obligar a reconstruir la imagen si el cambio solo pertenece al entorno de ejecución.

Un archivo como schema.sql describe la estructura inicial de una base de datos; no es la base de datos en producción. Las filas creadas durante el uso de la aplicación deben persistir en el servicio de datos o en un volumen.

2.3. Dependencias de construcción y de ejecución

No todas las dependencias tienen el mismo destino:

  • una herramienta de compilación, como Maven o un bundler de JavaScript, puede ser necesaria solo durante la construcción;
  • una biblioteca incluida en un WAR o en node_modules puede viajar con el artefacto;
  • un runtime, como PHP, la JVM o Node.js, debe estar disponible donde se ejecute la aplicación;
  • un servicio externo, como una base de datos, no se incluye necesariamente en el artefacto, pero sí forma parte de sus dependencias operativas.

Los archivos de bloqueo, como package-lock.json, composer.lock o un catálogo de versiones equivalente, ayudan a que dos construcciones instalen las mismas versiones. package.json expresa el proyecto y sus rangos; el archivo de bloqueo concreta el árbol resuelto para una instalación repetible.

3. Estructuras y artefactos habituales

No existe un único árbol universal. Para reconocer una aplicación hay que combinar el nombre de las carpetas con su gestor de dependencias, su documentación y su punto de entrada.

3.1. Proyecto Java con Maven y WAR

Una aplicación Java web podría recibirse con esta estructura:

catalogo-web/
├── README.md
├── LICENSE
├── CHANGELOG.md
├── .gitignore
├── .env.example
├── .dockerignore
├── docs/
│   ├── arquitectura.md
│   ├── instalacion.md
│   └── configuracion.md
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/                 # Controladores, servicios y dominio
│   │   ├── resources/            # Configuración y plantillas
│   │   └── webapp/               # HTML, CSS, JS y WEB-INF
│   └── test/                     # Pruebas automatizadas
├── db/
│   └── schema.sql                # Inicialización, no datos de producción
├── compose.yaml                  # Entorno local de varios servicios
└── Dockerfile                    # Receta para construir una imagen

Después de una construcción correcta puede aparecer una salida como esta:

target/
├── classes/
├── test-classes/
├── catalogo-web.war
└── surefire-reports/

src/ y pom.xml son material de construcción; target/catalogo-web.war es el artefacto que se puede copiar al directorio webapps de Tomcat. Los directorios de resultados, informes y cachés no se deben confundir con la estructura fuente ni copiar sin revisión a producción.

3.2. Anatomía mínima de un WAR

Un WAR es un archivo ZIP con una convención que Tomcat reconoce. Una estructura simplificada podría ser:

catalogo-web.war
├── index.html
├── css/
├── js/
├── WEB-INF/
│   ├── web.xml
│   ├── classes/                  # Clases compiladas
│   └── lib/                      # Bibliotecas del runtime
└── META-INF/

WEB-INF/ no se sirve directamente al navegador. Su contenido lo utiliza el servidor de aplicaciones para configurar y ejecutar la aplicación. El nombre del WAR suele determinar el contexto, por ejemplo catalogo-web.war puede publicarse como /catalogo-web, salvo que la configuración del servidor indique otro contexto.

3.3. Estructuras de Node.js, PHP y una web estática

Tecnología Archivos o directorios que conviene localizar Salida o runtime habitual
Node.js package.json, archivo de bloqueo, src/, public/, server.js dist/ para frontend o proceso Node para backend
PHP composer.json, composer.lock, public/, vendor/, .env.example Código PHP servido por Apache o PHP-FPM
Web estática index.html, css/, js/, img/ o dist/ Directorio servido por Nginx, Apache o una CDN
Java web pom.xml o build.gradle, src/, WEB-INF/ WAR desplegado en Tomcat u otro servidor

En PHP, vendor/ contiene dependencias instaladas por Composer y no debe regenerarse de forma arbitraria en producción si el despliegue exige una instalación reproducible. En Node.js, npm ci utiliza el archivo de bloqueo y está pensado para instalaciones limpias; el comando exacto depende del proyecto y se debe comprobar en package.json.

3.4. Imágenes, contenedores, redes y volúmenes

Cuando la aplicación se distribuye como imagen Docker, el artefacto contiene el runtime y los archivos necesarios para ejecutar el servicio. El contenedor es una instancia de esa imagen; la red permite la comunicación entre servicios y el volumen conserva datos fuera del ciclo de vida del contenedor.

Relación entre imágenes, contenedores, redes y volúmenes Docker
La imagen y el contenedor cumplen funciones distintas; las redes y los volúmenes aportan conectividad y persistencia al entorno de ejecución.

Por ejemplo, en una aplicación PHP con MariaDB:

  • la imagen de la aplicación contiene PHP, sus extensiones y el código;
  • el contenedor de MariaDB ejecuta otro servicio y mantiene sus datos en un volumen;
  • las variables de entorno indican a PHP cómo localizar la base de datos;
  • la red interna permite usar el nombre del servicio como host sin publicar el puerto de MariaDB al exterior.

En una aplicación Java, la alternativa puede ser una imagen que contenga Tomcat y copie el WAR a webapps. En ambos casos, el contenido de la base de datos y las cargas de usuarios no deberían formar parte de la imagen de la aplicación.

4. Del árbol de archivos al mapa de ejecución

El árbol de directorios muestra cómo está organizado el proyecto, pero no explica por sí solo qué ocurre cuando llega una petición. El inventario debe relacionar cada recurso con el proceso que lo utiliza.

flowchart LR
  U["Navegador"] -->|"HTTP o HTTPS"| W["Servidor web<br/>Nginx o Apache"]
  W -->|"Estático"| U
  W -->|"Dinámico"| A["Runtime de la aplicacion<br/>PHP, Node o Tomcat"]
  A -->|"Consulta o actualizacion"| DB["Base de datos"]
  A -->|"Lectura y escritura"| F["Almacenamiento persistente"]
  C["Configuracion y secretos"] --> A

En este mapa se pueden identificar responsabilidades distintas:

Componente Recursos que utiliza Pregunta de inspección
Servidor web Sitio, rutas, certificados y recursos estáticos ¿Qué archivos sirve y a dónde reenvía lo dinámico?
Runtime Código, bibliotecas y variables de entorno ¿Qué comando o proceso inicia la aplicación?
Base de datos Esquema, migraciones y datos de trabajo ¿Dónde están el host, el puerto y las credenciales?
Almacenamiento Cargas, documentos y logs seleccionados ¿Qué sobrevive al recrear el proceso?
Red Puertos publicados y redes internas ¿Qué componentes deben comunicarse y cuáles no deben exponerse?

Un Dockerfile puede instalar dependencias y copiar el código, pero no es la aplicación en ejecución. Del mismo modo, compose.yaml puede conectar web, app y db, pero no contiene por sí solo las reglas de negocio de app.

Caso de referencia: catálogo web

Una petición a /productos/42 puede llegar a Nginx. Si solicita una hoja CSS, Nginx la sirve directamente. Si solicita el producto, reenvía la petición al runtime; este usa sus bibliotecas, consulta la base de datos y devuelve HTML o JSON. La ficha de inventario debe poder señalar qué archivo, proceso, configuración y servicio de datos intervienen en cada paso.

5. Cómo inspeccionar una aplicación antes de desplegarla

La inspección debe ser ordenada y dejar evidencias. Una secuencia útil es la siguiente.

5.1. Leer la documentación y localizar la entrada

Empieza por README.md, la documentación de instalación y los scripts del proyecto. Busca el comando de inicio, el puerto, el contexto de despliegue y los servicios externos. No supongas que server.js, index.php o target/*.war son la entrada correcta sin comprobarlo.

Para una primera inspección sin ejecutar el proyecto se pueden utilizar órdenes de solo lectura:

find . -maxdepth 3 -type f | sort
grep -RInE "(PORT|HOST|DATABASE|DB_|npm start|mvn package)" \
  README.md docs package.json pom.xml 2>/dev/null

La segunda orden es orientativa: no todos los archivos existen en todos los proyectos y nunca debe utilizarse para imprimir valores secretos.

5.2. Revisar dependencias y resultados de construcción

Localiza el gestor que corresponda y anota sus archivos:

Evidencia Qué permite conocer
pom.xml o build.gradle Versión de Java, plugins y bibliotecas declaradas
package.json y archivo de bloqueo Scripts, runtime de Node y versiones resueltas
composer.json y composer.lock Versión de PHP y paquetes de Composer
requirements.txt o pyproject.toml Dependencias y metadatos de Python
Dockerfile Imagen base, usuario, puertos, copias y comando de inicio
compose.yaml Servicios, redes, volúmenes y variables del entorno

Si ya existe un artefacto, inspecciónalo sin ejecutarlo. Por ejemplo, para conocer el contenido de un WAR:

jar tf target/catalogo-web.war | sed -n '1,40p'

La salida permite comprobar si contiene WEB-INF/, clases, bibliotecas y recursos web. Si falta una biblioteca o una plantilla, el problema pertenece a la construcción del artefacto, no a la configuración de Nginx.

5.3. Separar configuración, secretos y valores por entorno

Anota los nombres de las variables, no sus valores reales:

APP_PORT=8080
DB_HOST=db
DB_NAME=catalogo
DB_USER=app_user
DB_PASSWORD=            # Se proporciona fuera del repositorio

La configuración debe indicar qué valor necesita la aplicación y qué componente lo consume. También hay que saber si el valor se lee al construir la imagen o al iniciar el contenedor: incluir una contraseña en una instrucción RUN o en una capa de imagen puede dejarla recuperable incluso después de borrar el archivo original.

5.4. Identificar persistencia e integraciones

Busca migraciones, scripts de inicialización, rutas de subida, colas, cachés y clientes de servicios externos. Para cada uno, registra:

  1. qué datos produce o necesita;
  2. dónde se almacenan;
  3. qué sucede al reiniciar el proceso;
  4. cómo se configura la conexión;
  5. qué evidencia demuestra que funciona.

No basta con encontrar db/schema.sql: hay que comprobar dónde se ejecuta, si es idempotente y dónde quedan los datos después de crear las tablas.

6. Inventario de recepción y decisiones de despliegue

El resultado de la inspección puede resumirse en una ficha. Esta ficha evita que el despliegue dependa de recordar comandos sueltos.

Elemento Pregunta que se responde Evidencia que se debe guardar
Entrada ¿Qué proceso inicia la aplicación? README, Dockerfile, pom.xml o package.json
Artefacto ¿Qué se distribuye? dist/, WAR, paquete o digest de imagen
Runtime ¿Qué ejecuta el artefacto? Versión de Node, PHP, JVM, Tomcat u otro servidor
Puerto y ruta ¿Dónde escucha y qué contexto usa? Configuración, EXPOSE, servidor web o documentación
Dependencias ¿Qué bibliotecas y versiones necesita? Archivos de dependencias y bloqueo
Configuración ¿Qué cambia entre entornos? .env.example, variables documentadas y configuración
Persistencia ¿Qué datos deben conservarse? Volúmenes, tablas, rutas de carga y política de copias
Integraciones ¿Qué servicios externos utiliza? Hosts, puertos, contratos y permisos
Comprobación ¿Cómo se verifica que funciona? Prueba, comando, resultado, fecha y logs relevantes

Una ficha cumplimentada para el catálogo podría ser:

Campo Decisión documentada
Artefacto catalogo-web.war construido con Maven
Runtime Tomcat, con Java en la versión indicada por el proyecto
Entrada Contexto /catalogo-web y controlador de productos
Datos MariaDB en un servicio separado y red interna
Persistencia Volumen para los datos de MariaDB; no se guardan en el WAR
Configuración Host, puerto y credenciales suministrados al entorno
Evidencia jar tf, logs de Tomcat, petición GET y consulta de salud

Esta ficha no sustituye a la guía de instalación. Su función es demostrar que se ha entendido la estructura y que cada decisión se apoya en un archivo, una configuración o una prueba observable.

7. Buenas prácticas

Para trabajar este contenido con rigor conviene aplicar estas prácticas:

  • Inspeccionar antes de ejecutar: lee scripts, permisos, rutas y variables antes de lanzarlos con privilegios.
  • Distinguir fuente, salida y runtime: documenta qué se edita, qué se construye y qué se instala o ejecuta.
  • Fijar versiones reproducibles: conserva archivos de bloqueo, versiones de runtimes y la etiqueta o el digest del artefacto probado.
  • Externalizar la configuración: cambia puertos, hosts y opciones sin reconstruir el artefacto cuando pertenezcan al entorno.
  • Proteger los secretos: utiliza un mecanismo de secretos y no los copies a Git, logs, imágenes ni capturas.
  • Separar código y datos: la imagen o el WAR pueden recrearse; los datos de trabajo deben tener almacenamiento persistente y copias verificadas.
  • Reducir la exposición: publica solo el punto de entrada necesario y deja la base de datos en una red interna.
  • Verificar el contenido del artefacto: comprueba archivos, permisos, dependencias y procedencia antes de instalarlo.
  • Relacionar decisiones con evidencias: anota el comando o prueba, el resultado obtenido y la fecha de comprobación.

8. Errores frecuentes

Error frecuente Por qué ocurre Cómo evitarlo
Copiar el repositorio entero al servidor Se confunde fuente con entrega Identifica y verifica el artefacto que corresponde al runtime
Confundir Dockerfile, imagen y contenedor Se mezclan receta, paquete e instancia Explica qué ocurre en build y qué ocurre al iniciar
Guardar la base de datos dentro de la imagen Se ignora el ciclo efímero del contenedor Separa el servicio de datos y usa almacenamiento persistente
Tratar schema.sql como datos de producción Se confunde inicialización con estado de trabajo Ejecuta migraciones controladas y conserva los datos fuera del artefacto
Instalar dependencias sin archivo de bloqueo Cada entorno resuelve versiones distintas Versiona y utiliza el mecanismo reproducible del gestor
Publicar .env o una contraseña en el Dockerfile Se prioriza la rapidez sobre la seguridad Usa .env.example y un gestor de secretos
Suponer que una carpeta indica la tecnología Muchos proyectos comparten nombres parecidos Comprueba documentación, punto de entrada y gestor de dependencias
Ejecutar scripts no revisados como administrador Se confía en el nombre del archivo Inspecciona comandos, permisos, rutas y variables antes de ejecutarlos
Exponer el puerto de la base de datos sin necesidad Se confunde comunicación interna con publicación Publica solo la entrada web y prueba la red interna
No comprobar el artefacto generado La build «termina» pero falta un recurso Lista el contenido y ejecuta una prueba funcional controlada

9. Resumen y conclusión

En este tema has aprendido que:

  • una aplicación web se entiende siguiendo el recorrido desde la fuente hasta el runtime y los datos;
  • los recursos estáticos, dinámicos, de configuración, dependencias, persistencia y distribución tienen funciones y ciclos de vida diferentes;
  • pom.xml, package.json, composer.json, Dockerfile y compose.yaml aportan información distinta y no son intercambiables;
  • la estructura del proyecto no es universal: el runtime, el artefacto y la documentación deben confirmar lo que sugiere el nombre de una carpeta;
  • un inventario útil relaciona archivos con procesos, puertos, servicios, redes, almacenamiento y pruebas;
  • la base de datos y las cargas de usuarios no deben perderse al recrear una imagen o un contenedor.

Idea clave

Analizar la estructura de una aplicación es convertir un conjunto de archivos en un mapa de ejecución comprobable. Cuando sabes qué se construye, qué se ejecuta, qué se configura y qué debe persistir, puedes elegir el despliegue y diagnosticar sus fallos con fundamento.

10. Para seguir practicando

  • Inspecciona la estructura de la aplicación PHP con MariaDB y relaciona index.php, schema.sql, script.sh, la imagen y el volumen.
  • Abre la práctica de Java y Tomcat y dibuja la relación entre el WAR, WEB-INF, webapps y el runtime de Tomcat.
  • Analiza WordPress con MariaDB y separa recursos de aplicación, configuración, red y persistencia.
  • Completa una ficha de inventario para un proyecto que contenga package.json, dist/, un Dockerfile y una base de datos externa.
  • Comprueba el contenido de un WAR o una imagen sin modificar el entorno y anota qué evidencia responde a cada campo de la ficha.

Bibliografía y fuentes

Presentación

Presentación DAW-U3.3 · Estructura de una aplicación web.