Twelve-Factor App - Principios de Diseño Cloud Native

15 min de lectura | 2025.12.11

¿Qué es Twelve-Factor App?

Twelve-Factor App son 12 principios de diseño para construir aplicaciones web modernas, propuestos por Adam Wiggins, cofundador de Heroku, en 2011. Permiten lograr un diseño adecuado para SaaS (Software as a Service) que funciona en entornos cloud.

¿Por qué es importante? En la actualidad, donde la containerización, los microservicios y CI/CD son comunes, estos principios se han convertido en la base del diseño de aplicaciones cloud native.

Los 12 Principios

1. Codebase (Base de Código)

Un único codebase bajo control de versiones, con múltiples despliegues.

flowchart LR
    Repo["Repositorio"] --> Dev["Entorno de Desarrollo"]
    Repo --> Staging["Entorno de Staging"]
    Repo --> Prod["Entorno de Producción"]
  • ✗ Repositorios diferentes por entorno
  • ✗ Múltiples aplicaciones en un repositorio

2. Dependencies (Dependencias)

Declarar y aislar explícitamente las dependencias.

// package.json - Declaración explícita de dependencias
{
  "dependencies": {
    "express": "^4.18.0",
    "pg": "^8.10.0"
  }
}
# No depender de paquetes instalados globalmente en el sistema
✗ Depender de imagemagick global
✓ Instalar explícitamente en Dockerfile

3. Config (Configuración)

Almacenar la configuración en variables de entorno.

// ✗ Mal ejemplo: Configuración hardcodeada en el código
const dbHost = 'localhost';

// ✓ Buen ejemplo: Obtener de variables de entorno
const dbHost = process.env.DATABASE_HOST;
# Configuración mediante variables de entorno
DATABASE_HOST=db.example.com
DATABASE_USER=admin
API_KEY=secret123

4. Backing Services (Servicios de Respaldo)

Tratar los servicios de respaldo (DB, caché, colas, etc.) como recursos adjuntos.

flowchart LR
    App["Aplicación"] --> DB["PostgreSQL<br/>(Local o Amazon RDS)"]
    App --> Cache["Redis<br/>(Local o ElastiCache)"]
    App --> Storage["S3<br/>(Almacenamiento externo)"]

La migración es posible solo cambiando la conexión en variables de entorno

5. Build, Release, Run (Construir, Liberar, Ejecutar)

Separar estrictamente las tres etapas: build, release y run.

flowchart LR
    Code["Código"] --> Build["Build"]
    Build --> Artifact["Artefacto<br/>Ejecutable"]
    Config["Configuración"] --> Release["Release<br/>Versión"]
    Artifact --> Release
    Release --> Run["Proceso<br/>en Ejecución"]

✗ Editar código directamente en producción ✓ Desplegar una nueva release

6. Processes (Procesos)

Ejecutar la aplicación como uno o más procesos stateless.

// ✗ Mal ejemplo: Guardar sesiones en memoria
const sessions = {};
app.use((req, res, next) => {
  sessions[req.sessionId] = req.user;
});

// ✓ Buen ejemplo: Usar almacenamiento externo
const RedisStore = require('connect-redis');
app.use(session({
  store: new RedisStore({ client: redisClient })
}));

7. Port Binding (Enlace de Puertos)

Exponer servicios mediante enlace de puertos.

// La aplicación misma se convierte en servidor web
const express = require('express');
const app = express();

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

8. Concurrency (Concurrencia)

Escalar horizontalmente mediante el modelo de procesos.

Web     [■] [■] [■] [■]     ← 4 procesos
Worker  [■] [■]             ← 2 procesos
Clock   [■]                 ← 1 proceso

→ Ajustar número de procesos según la carga

9. Disposability (Desechabilidad)

Maximizar la robustez con inicio rápido y apagado graceful.

// Apagado graceful
process.on('SIGTERM', async () => {
  console.log('SIGTERM received, shutting down gracefully');

  // Dejar de aceptar nuevas solicitudes
  server.close();

  // Completar solicitudes en proceso
  await finishPendingRequests();

  // Cerrar conexión a DB
  await db.close();

  process.exit(0);
});

10. Dev/Prod Parity (Paridad Dev/Prod)

Mantener desarrollo, staging y producción lo más similares posible.

BrechaTradicionalTwelve-Factor
TiempoSemanas~mesesHoras~días
PersonalDesarrolladores y operadores separadosMisma persona despliega
HerramientasSQLite en desarrollo, PostgreSQL en producciónUsar el mismo servicio
# docker-compose.yml - Usar la misma DB en desarrollo que en producción
services:
  db:
    image: postgres:15
  redis:
    image: redis:7

11. Logs

Tratar los logs como flujos de eventos.

// ✗ Mal ejemplo: Escribir a archivo
fs.appendFileSync('/var/log/app.log', message);

// ✓ Buen ejemplo: Salida estándar
console.log(JSON.stringify({
  timestamp: new Date().toISOString(),
  level: 'info',
  message: 'User logged in',
  userId: '123'
}));
# Los logs se recolectan y agregan externamente
docker logs app | fluentd

12. Admin Processes (Procesos Administrativos)

Ejecutar tareas administrativas como procesos únicos.

# Migración
npx prisma migrate deploy

# Script de corrección de datos
node scripts/fix-data.js

# Depuración con REPL
node --inspect app.js

Puntos para la Práctica

Compatibilidad con Docker

# Dockerfile conforme a Twelve-Factor
FROM node:20-slim

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production  # 2. Dependencias

COPY . .

# 7. Enlace de puertos
EXPOSE 3000

# 6. Proceso stateless
CMD ["node", "server.js"]

Combinación con Kubernetes

# ConfigMap para gestionar configuración (3. Configuración)
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  NODE_ENV: production

# Deployment para escalar (8. Concurrencia)
apiVersion: apps/v1
kind: Deployment
spec:
  replicas: 3

Resumen

Los principios de Twelve-Factor App son conceptos fundamentales para el desarrollo de aplicaciones cloud native. Siguiendo estos principios, se pueden construir aplicaciones escalables, mantenibles y portables. No es necesario aplicar todo de una vez; se recomienda ir incorporándolos gradualmente según la situación del proyecto.

← Volver a la lista