Skip to content

Instantly share code, notes, and snippets.

@gusdelact
Last active April 27, 2026 09:55
Show Gist options
  • Select an option

  • Save gusdelact/ce2193d2bdf7b71777b89081c8c6e5a4 to your computer and use it in GitHub Desktop.

Select an option

Save gusdelact/ce2193d2bdf7b71777b89081c8c6e5a4 to your computer and use it in GitHub Desktop.

Narrativa del Proyecto FTGO: De Monolito a Microservicios Serverless en AWS

Meetup: ULSA MX Cómputo en la Nube — 27 de Abril de 2026


Contexto y Motivación

Este proyecto es un ejercicio educativo completo que demuestra, paso a paso, cómo refactorizar una aplicación monolítica hacia una arquitectura de microservicios serverless en la nube de AWS. Está diseñado para estudiantes de programación web y web services hispanoparlantes, y se basa en el sistema FTGO (Food To Go Online) del libro "Microservice Patterns" segunda edición de Chris Richardson.

La premisa es simple pero poderosa: construir primero un monolito funcional, entender sus limitaciones en la práctica, y luego descomponerlo en microservicios independientes usando servicios nativos de la nube. Todo el código, comentarios y documentación están escritos en español para facilitar el aprendizaje.


Parte 1: El Monolito — Entendiendo el Punto de Partida

¿Qué es FTGO?

FTGO es un sistema de pedidos de comida en línea donde:

  • Los consumidores hacen pedidos de comida a restaurantes locales a través de una interfaz web.
  • Los restaurantes gestionan sus menús y aceptan o preparan los pedidos.
  • Los repartidores recogen y entregan los pedidos.
  • El sistema procesa los pagos de cada pedido.

Es esencialmente un Uber Eats o Rappi simplificado, con todos los componentes necesarios para ilustrar los problemas reales de una arquitectura monolítica.

Arquitectura del Monolito

El monolito FTGO está implementado como un solo proceso FastAPI en Python 3.13, con una base de datos SQLite3 almacenada en un único archivo llamado ftgo.db. Todos los módulos del sistema — consumidores, restaurantes, menús, pedidos, repartidores y pagos — viven dentro del mismo servidor, comparten la misma base de datos y se despliegan como una sola unidad.

La arquitectura sigue el patrón hexagonal:

  • Adaptadores de entrada: API REST (FastAPI Routers) y servidor de archivos estáticos para la UI.
  • Lógica de negocio: módulos separados lógicamente (consumidores, restaurantes, menú, pedidos, repartidores, pagos).
  • Adaptadores de salida: SQLAlchemy ORM conectado a SQLite3.
  • Servicios externos simulados: Stripe para pagos, Twilio para SMS, Amazon SES para email.

Stack Tecnológico del Monolito

Tecnología Uso
Python 3.13 Lenguaje de programación
FastAPI Framework web para la API REST
SQLite3 Base de datos relacional (archivo local)
SQLAlchemy ORM para interactuar con la base de datos
Pydantic Validación de datos de entrada/salida
uvicorn Servidor ASGI para ejecutar FastAPI
uv Gestor de paquetes y entornos virtuales
HTML/CSS/JS vanilla Interfaz web sin frameworks

Modelo de Datos Relacional

El esquema de base de datos tiene 7 tablas interrelacionadas:

  • Consumidores: clientes que hacen pedidos (id, nombre, email, teléfono, dirección).
  • Restaurantes: establecimientos que ofrecen comida (id, nombre, tipo de cocina, horarios).
  • Elementos del Menú: platillos de cada restaurante (id, restaurante_id, nombre, precio, disponibilidad).
  • Repartidores: couriers que entregan pedidos (id, nombre, vehículo, disponibilidad).
  • Pedidos: la entidad central que conecta consumidor, restaurante y repartidor (id, estado, total, dirección de entrega).
  • Elementos del Pedido: platillos específicos dentro de un pedido (cantidad, precio unitario, subtotal).
  • Pagos: registro de cobros por pedido (monto, método de pago, estado, referencia).

Las relaciones son: un consumidor hace muchos pedidos, un restaurante recibe muchos pedidos y tiene muchos platillos, un repartidor entrega muchos pedidos, un pedido contiene muchos elementos, y cada pedido tiene exactamente un pago.

Flujo de Negocio: Ciclo de Vida del Pedido

El pedido es la entidad más compleja del sistema y sigue una máquina de estados:

CREADO → ACEPTADO → PREPARANDO → LISTO → EN_CAMINO → ENTREGADO
   ↓         ↓          ↓
 CANCELADO CANCELADO  CANCELADO

El flujo completo es:

  1. El consumidor selecciona platillos y crea el pedido (estado: CREADO).
  2. El restaurante acepta el pedido (ACEPTADO) y comienza a prepararlo (PREPARANDO).
  3. Cuando la comida está lista (LISTO), se asigna un repartidor.
  4. El repartidor recoge el pedido (EN_CAMINO) y lo entrega (ENTREGADO).
  5. El consumidor procesa el pago.

Despliegue del Monolito

El monolito se despliega en una instancia EC2 de Amazon Linux 2023 con un Network Load Balancer (NLB) al frente:

  • El NLB recibe tráfico en el puerto 80 y lo reenvía al puerto 8000 de la EC2.
  • La aplicación corre como servicio systemd con uvicorn.
  • El costo estimado es de ~$25 USD/mes (EC2 t3.micro + NLB).

¿Por Qué el Monolito se Vuelve Problemático?

El libro de Chris Richardson documenta exactamente lo que le pasó a FTGO en la vida real:

  1. Complejidad abrumadora: a medida que el código crece, ningún desarrollador puede entender todo el sistema. La clase Order creció a miles de líneas de código.

  2. Desarrollo lento: el IDE se vuelve lento, el build tarda mucho, el ciclo edit-build-run-test se alarga.

  3. Despliegue doloroso: para corregir un bug en pagos hay que redesplegar toda la aplicación. FTGO solo podía desplegar una vez al mes, mientras Amazon hacía 130,000 deploys por día.

  4. Testing difícil: la complejidad del código hace que los tests sean lentos y frágiles. Bugs llegan a producción.

  5. Falta de aislamiento de fallos: un memory leak en un módulo tumba toda la aplicación. No hay forma de escalar módulos independientemente.

  6. Conflictos de recursos: el módulo de datos de restaurantes necesita mucha memoria, el de procesamiento de imágenes necesita mucho CPU. Con un monolito hay que comprometer.

  7. Stack tecnológico fijo: todo está en Python/FastAPI. No se puede usar Go para entregas en tiempo real o Rust para procesamiento intensivo.

  8. Espiral descendente: código difícil de entender → cambios incorrectos → código más difícil de entender.


Parte 2: La Refactorización — Microservicios Serverless

Identificación de Dominios de Negocio

El primer paso de la refactorización es identificar los bounded contexts (contextos delimitados) del sistema. Se identificaron 5 dominios:

# Dominio Responsabilidad Entidades
1 Consumidores Gestión de clientes Consumidor
2 Restaurantes Gestión de restaurantes y menús Restaurante, ElementoMenu
3 Pedidos Ciclo de vida de pedidos Pedido, ElementoPedido
4 Entregas Gestión de repartidores Repartidor
5 Pagos Procesamiento de pagos Pago

Cada dominio se convierte en un microservicio independiente con su propia base de datos, su propio API, su propio pipeline de despliegue y su propio repositorio de código.

Decisiones Arquitectónicas Clave

La migración a microservicios implica varias decisiones fundamentales:

1. Compute: De EC2 a AWS Lambda En lugar de mantener una instancia EC2 encendida 24/7, cada microservicio se implementa como una función Lambda. Lambda escala automáticamente de 0 a miles de instancias concurrentes, y solo se paga por el tiempo de ejecución real. Para un ejercicio educativo con poco tráfico, el costo es prácticamente $0.

2. Base de datos: De SQLite a DynamoDB (Database per Service) Se aplica el patrón "Database per Service": cada microservicio tiene su propia tabla DynamoDB. Esto elimina el acoplamiento a nivel de datos. Ya no hay foreign keys entre dominios; la integridad referencial se garantiza a nivel de aplicación mediante validaciones HTTP entre servicios.

3. API: De FastAPI Routers a API Gateway Cada dominio expone sus endpoints a través de un API Gateway REST independiente. Los endpoints se mantienen idénticos al monolito (mismas rutas, mismos métodos HTTP, mismos formatos de request/response) para facilitar la migración del frontend.

4. Frontend: De FastAPI Static Files a Lambda + API Gateway El frontend (HTML/CSS/JS vanilla) ya no se sirve desde el monolito. Se implementa como una Lambda propia que sirve el HTML con CSS y JS inline, expuesta a través de API Gateway (mismo patrón serverless que los microservicios). La única diferencia es que ahora el JavaScript invoca múltiples API Gateways en lugar de rutas relativas.

5. Infraestructura como Código: AWS SAM + CloudFormation Cada microservicio tiene su propio template SAM (template.yaml) que define todos sus recursos: Lambda, API Gateway, DynamoDB, IAM Roles, CloudWatch Logs. Un solo comando sam deploy crea o actualiza toda la infraestructura.

6. CI/CD: GitHub Actions Cada microservicio tiene su propio pipeline de GitHub Actions que se activa solo cuando cambian archivos en su directorio. El pipeline ejecuta tests, empaqueta con SAM y despliega automáticamente.

Diseño de Tablas DynamoDB

La migración de SQLite relacional a DynamoDB NoSQL requiere repensar el modelo de datos:

Consumidores y Repartidores: diseño simple con UUID como partition key y un GSI (Global Secondary Index) para búsquedas por email o disponibilidad.

Restaurantes: diseño de tabla única (single-table design) donde el restaurante y sus platillos comparten la misma tabla usando un esquema PK/SK:

  • PK=REST#<id>, SK=METADATA → datos del restaurante
  • PK=REST#<id>, SK=MENU#<menu_id> → cada platillo del menú

Pedidos: mismo patrón de tabla única:

  • PK=PED#<id>, SK=METADATA → datos del pedido
  • PK=PED#<id>, SK=ELEM#<elem_id> → cada elemento del pedido
  • GSI por consumidor_id para buscar pedidos de un cliente

Pagos: diseño simple con UUID como PK y GSI por pedido_id.

Comunicación entre Microservicios

En esta versión se usa comunicación síncrona (HTTP) entre servicios:

  • Pedidos → Consumidores: al crear un pedido, el servicio de pedidos llama al API de consumidores para validar que el cliente existe.
  • Pedidos → Restaurantes: obtiene el menú y los precios para calcular el total.
  • Pedidos → Entregas: al asignar repartidor, marca al repartidor como ocupado.
  • Pagos → Pedidos: al procesar un pago, consulta el total del pedido.

El frontend también actúa como orquestador, haciendo llamadas directas a cada microservicio según la operación.

Como evolución futura se podría usar Amazon EventBridge para comunicación asíncrona basada en eventos (patrón Saga).

Consistencia Eventual

Al separar las bases de datos, se pierde la transaccionalidad ACID entre dominios. Se acepta consistencia eventual con un patrón Saga simplificado: si falla la validación del consumidor al crear un pedido, se retorna error inmediato sin necesidad de compensación.

CORS (Cross-Origin Resource Sharing)

Como el frontend y los microservicios se exponen en diferentes API Gateways (diferentes dominios), cada microservicio configura CORS para permitir las llamadas cross-origin. Cada Lambda incluye los headers Access-Control-Allow-Origin: * en todas sus respuestas.


Parte 3: La Implementación — Código y Estructura

Estructura del Proyecto

ftgo-microservicios/
├── frontend/                    ← Frontend servido por Lambda + API Gateway
│   ├── template.yaml            ← IaC: Lambda + API Gateway
│   ├── src/
│   │   ├── handler.py           ← Lambda que sirve el HTML
│   │   └── static/
│   │       └── index.html       ← HTML con CSS/JS inline
│   └── .github/workflows/deploy.yml
│
├── servicios/
│   ├── consumidores/            ← Microservicio de Consumidores
│   │   ├── template.yaml        ← SAM: Lambda + API GW + DynamoDB
│   │   ├── src/handler.py       ← Código de la Lambda
│   │   ├── pyproject.toml       ← Dependencias (uv)
│   │   └── .github/workflows/deploy.yml
│   ├── restaurantes/            ← Microservicio de Restaurantes + Menú
│   ├── pedidos/                 ← Microservicio de Pedidos (el más complejo)
│   ├── entregas/                ← Microservicio de Repartidores
│   └── pagos/                   ← Microservicio de Pagos
│
└── scripts/
    └── migrar_sqlite_a_dynamodb.py  ← Migración de datos

Cada microservicio es autónomo: tiene su propio template de infraestructura, su propio código, sus propias dependencias y su propio pipeline de CI/CD. Esto permite que cada uno pueda vivir en un repositorio separado y eventualmente en una cuenta AWS diferente.

Anatomía de un Microservicio (Ejemplo: Consumidores)

El handler de Lambda es un solo archivo Python que:

  1. Recibe el evento HTTP del API Gateway.
  2. Enruta la petición según el método HTTP y la ruta.
  3. Ejecuta la lógica CRUD contra DynamoDB usando boto3.
  4. Retorna la respuesta HTTP con headers CORS.

No hay framework web (no FastAPI, no Flask). Lambda + API Gateway reemplazan al framework. El código es Python puro con boto3 como única dependencia externa.

El template SAM define:

  • La función Lambda con su runtime (Python 3.13), timeout, memoria y variables de entorno.
  • Los eventos del API Gateway (cada ruta/método es un evento).
  • La tabla DynamoDB con su esquema de claves y GSIs.
  • Los permisos IAM (política DynamoDBCrudPolicy).
  • Los outputs (URL del API, ARN de la Lambda, nombre de la tabla).

El Microservicio de Pedidos: El Más Complejo

El servicio de pedidos es el corazón del sistema porque:

  • Implementa la máquina de estados completa del pedido.
  • Se comunica con otros 3 microservicios (consumidores, restaurantes, entregas).
  • Usa el patrón single-table en DynamoDB para almacenar pedidos y sus elementos.
  • Calcula totales validando precios contra el menú del restaurante.

La comunicación con otros servicios se hace con urllib.request (librería estándar de Python), sin dependencias externas. Esto mantiene el paquete Lambda pequeño y los cold starts rápidos.

Script de Migración: SQLite → DynamoDB

El script migrar_sqlite_a_dynamodb.py es una pieza clave del ejercicio porque demuestra:

  • Cómo leer datos de una base relacional (SQLite con sqlite3).
  • Cómo transformar registros al formato NoSQL (con PKs/SKs compuestos).
  • Cómo manejar el cambio de IDs auto-incrementales (int) a UUIDs (string).
  • Cómo mantener un mapeo de IDs viejos a nuevos para preservar las relaciones lógicas.
  • Cómo escribir en batch a DynamoDB usando boto3.

El script migra en orden respetando dependencias: primero consumidores y restaurantes (sin dependencias), luego repartidores, después pedidos (que referencian a los anteriores), y finalmente pagos.


Parte 4: El Despliegue — De la Teoría a la Nube

Ambiente de Trabajo del Alumno

Los alumnos trabajan desde una instancia EC2 conectándose por SSH. En esa instancia instalan:

  • Python 3.13
  • uv (gestor de paquetes)
  • AWS CLI v2
  • AWS SAM CLI
  • Git

Las credenciales AWS se obtienen de IAM Identity Center (antes SSO), que proporciona credenciales temporales (Access Key + Secret Key + Session Token). Esto es más seguro que credenciales permanentes.

Orden de Despliegue

Los servicios se despliegan en orden de dependencias:

  1. ftgo-consumidores (sin dependencias)
  2. ftgo-restaurantes (sin dependencias)
  3. ftgo-repartidores (sin dependencias)
  4. ftgo-pedidos (depende de consumidores, restaurantes, repartidores)
  5. ftgo-pagos (depende de pedidos)
  6. ftgo-frontend (depende de todos — necesita las URLs de los APIs)

Después del primer despliegue, cada servicio se puede actualizar independientemente.

Pipeline de CI/CD con GitHub Actions

Cada servicio tiene un workflow que:

  1. Se activa al hacer push a main solo si cambian archivos en su directorio.
  2. Instala dependencias con uv.
  3. Ejecuta tests con pytest.
  4. Empaqueta con sam build.
  5. Despliega con sam deploy.

El pipeline del frontend empaqueta y despliega la Lambda que sirve el HTML, igual que los demás microservicios.

Configuración del Frontend Post-Despliegue

Después de desplegar todos los microservicios, se actualizan las URLs de cada API Gateway directamente en el JavaScript del HTML. Estas URLs se obtienen de los outputs de cada stack de CloudFormation.


Parte 5: Comparación Final — Monolito vs. Microservicios

Tabla Comparativa

Aspecto Monolito Microservicios
Compute EC2 + uvicorn (siempre encendida) AWS Lambda (pay-per-request)
Framework FastAPI Lambda handlers nativos (sin framework)
Base de datos SQLite3 (un archivo, todas las tablas) DynamoDB (una tabla por dominio)
API FastAPI routers (un proceso) API Gateway REST (uno por dominio)
Frontend Servido por FastAPI Lambda + API Gateway
Despliegue scp + systemd (manual) SAM + GitHub Actions (automatizado)
Escalamiento Vertical (instancia más grande) Automático (Lambda escala a 0 y a miles)
Costo en reposo ~$25/mes (EC2 siempre encendida) ~$0 (pay-per-request, Free Tier)
Aislamiento de fallos Ninguno (un bug tumba todo) Total (cada servicio es independiente)
Velocidad de despliegue Todo o nada Cada servicio por separado
IaC Manual CloudFormation/SAM (declarativo)
CI/CD No tiene GitHub Actions (un pipeline por servicio)

Beneficios Obtenidos

  1. Despliegue independiente: se puede actualizar el servicio de pagos sin tocar consumidores ni pedidos.
  2. Escalamiento granular: si pedidos tiene mucho tráfico, solo esa Lambda escala.
  3. Aislamiento de fallos: si el servicio de pagos falla, los demás siguen funcionando.
  4. Libertad tecnológica: cada servicio podría usar un lenguaje diferente (aunque aquí todos usan Python).
  5. Costo optimizado: sin tráfico no se paga nada (vs. $25/mes del EC2).
  6. Equipos autónomos: cada equipo puede trabajar en su servicio sin coordinarse con los demás.

Trade-offs y Complejidad Añadida

  1. Complejidad operacional: ahora hay 5 servicios + frontend que monitorear en lugar de 1.
  2. Consistencia eventual: ya no hay transacciones ACID entre dominios.
  3. Latencia de red: las llamadas entre servicios añaden latencia (HTTP entre Lambdas).
  4. Debugging distribuido: rastrear un error que cruza servicios es más difícil.
  5. Cold starts: la primera invocación de una Lambda tarda más (inicialización del runtime).
  6. Duplicación de código: cada servicio tiene su propia lógica de respuesta HTTP y CORS.

Parte 6: Lecciones para los Estudiantes

Principios Demostrados

  1. Domain-Driven Design (DDD): identificar bounded contexts para definir los límites de cada microservicio.
  2. Database per Service: cada servicio es dueño de sus datos. No hay base de datos compartida.
  3. API-First: los contratos de API se mantienen iguales durante la migración, permitiendo que el frontend funcione sin cambios significativos.
  4. Infrastructure as Code: toda la infraestructura se define en archivos YAML versionados en Git.
  5. CI/CD: automatizar el despliegue reduce errores humanos y permite iteraciones rápidas.
  6. Serverless: eliminar la gestión de servidores permite enfocarse en la lógica de negocio.

Patrón de Migración: Strangler Fig

Aunque en este ejercicio se hace la migración completa de una vez, en la vida real se usa el patrón Strangler Fig: se migra un módulo a la vez, manteniendo el monolito funcionando para los módulos no migrados, hasta que eventualmente el monolito desaparece.

Evolución Futura del Diseño

El diseño está preparado para evolucionar:

  • Multi-cuenta AWS: cada dominio puede vivir en su propia cuenta AWS con su propio pipeline.
  • Comunicación asíncrona: reemplazar llamadas HTTP síncronas con EventBridge para desacoplar aún más.
  • Observabilidad: agregar AWS X-Ray para tracing distribuido, CloudWatch Dashboards para métricas.
  • Autenticación: agregar Amazon Cognito para autenticar usuarios.
  • Dominio personalizado: usar Route 53 + certificados ACM para URLs amigables.

Parte 7: Aspectos Prácticos para el Meetup

¿Por Qué Este Ejercicio es Relevante?

  1. Es realista: FTGO es un caso de estudio del libro más referenciado en la industria sobre microservicios.
  2. Es completo: cubre desde el diseño hasta el despliegue automatizado, pasando por la migración de datos.
  3. Es accesible: usa Python (lenguaje que los estudiantes ya conocen), sin frameworks complejos.
  4. Es económico: con el Free Tier de AWS, el costo es prácticamente $0 para uso educativo.
  5. Es progresivo: primero se entiende el monolito, luego se entiende por qué duele, y finalmente se resuelve con microservicios.

Herramientas y Servicios AWS Utilizados

  • AWS Lambda: compute serverless (Python 3.13) — tanto para microservicios como para el frontend
  • API Gateway: exposición de APIs REST con CORS + hosting del frontend
  • DynamoDB: base de datos NoSQL serverless
  • CloudFormation/SAM: infraestructura como código
  • IAM: gestión de permisos y roles
  • CloudWatch: logs y monitoreo
  • IAM Identity Center: autenticación federada para los alumnos

Flujo de la Demostración

  1. Mostrar el monolito funcionando en EC2 (interfaz web, API, base de datos).
  2. Explicar los problemas del monolito con ejemplos concretos del código.
  3. Mostrar la identificación de dominios y el diseño de microservicios.
  4. Hacer un despliegue en vivo de un microservicio con sam deploy.
  5. Mostrar la migración de datos ejecutando el script.
  6. Demostrar el sistema completo funcionando con microservicios.
  7. Comparar costos, tiempos de despliegue y aislamiento de fallos.

Resumen Ejecutivo

Este proyecto demuestra la transformación completa de un sistema monolítico (Python/FastAPI/SQLite/EC2) a microservicios serverless (Python/Lambda/DynamoDB/API Gateway) en AWS. Es un ejercicio educativo diseñado para estudiantes universitarios que cubre:

  • Arquitectura de software: monolito vs. microservicios, patrones de diseño, DDD.
  • Cloud computing: servicios serverless de AWS, IaC, CI/CD.
  • Ingeniería de datos: migración de SQL relacional a NoSQL, diseño de tablas DynamoDB.
  • DevOps: pipelines automatizados, despliegue continuo, infraestructura reproducible.
  • Economía cloud: comparación de costos entre modelos de compute.

La narrativa va de lo simple a lo complejo: primero se construye algo que funciona (monolito), se experimenta el dolor de escalarlo, y luego se aplica la solución moderna (microservicios serverless). Este enfoque pedagógico asegura que los estudiantes no solo aprendan el "cómo" sino también el "por qué" de las arquitecturas distribuidas.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment