APIs asíncronas, LAGs y AWS sin humo
Un recorrido práctico desde async/await hasta un sistema simple con workers, colas, LLMs y Bedrock.
Prólogo
Este libro está escrito para alguien que ya programa, probablemente en Python, y quiere entender cómo se conectan varias piezas que hoy aparecen una y otra vez en sistemas reales:
- APIs
- procesamiento asíncrono
- colas y workers
- observabilidad
- LLMs
- Bedrock
- servicios básicos de AWS
No está pensado como enciclopedia. Está pensado como mapa mental + ejemplo continuo.
Durante todo el libro vamos a seguir un mismo caso:
El ejemplo del libro: TextFlow
TextFlow es un sistema simple que recibe textos, los procesa en segundo plano y deja listo un resultado para consultar después.
Al principio, el procesamiento será fake. Más adelante, el worker llamará a un LLM. Después veremos cómo ese mismo sistema podría vivir sobre AWS.
La idea es avanzar siempre sobre el mismo ejemplo, sin saltar a veinte proyectos distintos.
Qué vas a aprender
Al terminar el libro deberías poder razonar con claridad sobre estas preguntas:
- ¿Qué significa realmente
asyncyawait? - ¿Cuándo conviene responder rápido y procesar después?
- ¿Qué problema resuelven una cola y un worker?
- ¿Cómo se integra un LLM sin meterlo en cualquier parte?
- ¿Qué lugar ocupa Bedrock?
- ¿Para qué sirven VPC, SQS, ECS, IAM y compañía?
- ¿Cómo se ve un sistema simple, pero serio?
Qué no intenta hacer este libro
No busca:
- enseñarte todo Python
- cubrir todos los detalles de FastAPI
- convertirte en experto en Kubernetes
- darte una guía exhaustiva de seguridad cloud
- reemplazar la documentación oficial de AWS
Sí busca algo más útil para esta etapa: que entiendas el flujo completo.
Parte I — El problema que queremos resolver
Capítulo 1 — Por qué una API no debería hacer todo en el request
Imaginá que TextFlow recibe un texto grande y debe devolver un resumen.
La primera idea suele ser esta:
- llega el request
- la API procesa todo
- devuelve el resultado
Suena simple, pero enseguida aparecen problemas.
Problema 1: el cliente queda esperando
Si el procesamiento tarda mucho, la API tarda mucho.
Problema 2: si el procesamiento falla, se mezcla todo
No queda clara la frontera entre:
- recibir un pedido
- ejecutar trabajo
- guardar resultado
- exponer estado
Problema 3: si querés escalar, todo está acoplado
La API y el procesamiento viven pegados.
La idea central del libro
En vez de hacer todo dentro del request, TextFlow va a funcionar así:
- el cliente envía un texto
- la API crea una tarea
- la API responde rápido con un
task_id - un worker procesa esa tarea aparte
- el cliente consulta el estado después
Diagrama base
flowchart TB A[Cliente] --> B[POST o tasks] B --> C[Crear tarea] C --> D[Encolar tarea] D --> E[Responder task_id] F[Worker] --> G[Tomar tarea de cola] G --> H[Procesar texto] H --> I[Guardar resultado] A --> J["GET /tasks/{id}"] J --> K[Consultar estado y resultado]
Esto ya nos obliga a pensar mejor.
No construimos “una API que hace magia”. Construimos un sistema con responsabilidades separadas.
Capítulo 2 — El modelo mental de TextFlow
Antes del código, definamos el dominio.
Qué hace TextFlow
TextFlow recibe un texto y produce un resultado procesado.
Más adelante ese resultado podrá ser:
- un resumen
- una clasificación
- una extracción de puntos clave
- una llamada a un LLM
Pero por ahora alcanza con pensar en una entidad principal.
La entidad Task
Toda tarea en TextFlow va a tener, como mínimo:
idinput_textstatusresulterrorcreated_atupdated_at
Estados
Vamos a usar cuatro estados simples:
pendingprocessingdonefailed
Diagrama de estados
stateDiagram-v2 [*] --> pending pending --> processing processing --> done processing --> failed done --> [*] failed --> [*]
Ese diagrama parece chico, pero es una gran mejora mental.
Ya no pensamos “la API resume textos”.
Ahora pensamos:
- existen tareas
- las tareas cambian de estado
- distintas partes del sistema participan en esas transiciones
Parte II — Async sin misterio
Capítulo 3 — Qué significa async y await
Cuando alguien empieza con Python async, muchas veces piensa que async es “hacer todo más rápido”.
No exactamente.
La idea principal es otra:
permitir que el programa no se quede bloqueado esperando operaciones que tardan, especialmente operaciones de I/O.
I/O contra CPU
Hay dos tipos de trabajo que conviene distinguir:
Trabajo I/O-bound
Es el trabajo que pasa mucho tiempo esperando:
- red
- disco
- base de datos
- cola externa
- servicios HTTP
- LLMs
Trabajo CPU-bound
Es el trabajo que consume cálculo intenso:
- compresión pesada
- visión por computadora intensa
- algoritmos matemáticos costosos
- embeddings locales grandes
async brilla más en el primer grupo.
Qué hace await
await no significa “pausá todo”.
Significa algo más parecido a esto:
esta coroutine necesita esperar algo; mientras tanto, el event loop puede dejar correr otras tareas.
El event loop
El event loop es el coordinador.
No “hace el trabajo” por sí solo. Organiza la ejecución de coroutines y les da tiempo para avanzar cuando pueden hacerlo.
Diagrama mental
flowchart TB A[Coroutine A espera red] --> L[Event Loop] B[Coroutine B espera cola] --> L C[Coroutine C puede avanzar] --> L L --> C
Si una tarea está esperando una respuesta externa, otra puede avanzar.
Ese es el corazón del modelo.
Lo que sí y lo que no
async sí ayuda cuando:
- una API espera respuestas de servicios externos
- un worker consume una cola
- varias tareas viven concurrentemente
- una app necesita atender muchos eventos I/O
async no arregla mágicamente:
- cálculos pesados de CPU
- código mal diseñado
- arquitectura confusa
- bloqueos introducidos por librerías no async
Capítulo 4 — Cómo pensar un sistema async
No conviene empezar escribiendo funciones async al azar.
Conviene empezar por el flujo.
Flujo de TextFlow
flowchart TD A[Cliente envía texto] --> B[API crea Task] B --> C[Storage guarda pending] C --> D[Queue recibe task_id] D --> E[Worker toma task_id] E --> F[Storage cambia a processing] F --> G[Procesamiento] G --> H{Éxito?} H -->|Sí| I[Storage cambia a done] H -->|No| J[Storage cambia a failed]
Este diseño nos deja ver con claridad:
- quién produce trabajo
- quién consume trabajo
- dónde vive el estado
- dónde se maneja el error
Regla útil
Si una parte del flujo puede demorar, fallar o escalar por separado, probablemente merece estar desacoplada.
En TextFlow, el procesamiento cumple con las tres.
Parte III — La API de nuestro ejemplo
Capítulo 5 — El contrato HTTP de TextFlow
TextFlow necesita muy poco para empezar.
Endpoint 1: crear tarea
POST /tasks
Recibe algo así:
{
"input_text": "Quiero resumir este texto"
}Y devuelve algo así:
{
"task_id": "abc123",
"status": "pending"
}La clave no está en el JSON. La clave está en el comportamiento:
- crea una tarea
- la guarda
- la encola
- responde rápido
No procesa.
Endpoint 2: consultar tarea
GET /tasks/{task_id}
Puede devolver:
{
"task_id": "abc123",
"status": "processing",
"result": null,
"error": null
}O más adelante:
{
"task_id": "abc123",
"status": "done",
"result": "Resumen generado",
"error": null
}Por qué este contrato es bueno para aprender
Porque te obliga a pensar en:
- estados
- persistencia mínima
- asincronía real
- separación entre aceptación y ejecución
Capítulo 6 — Storage simple: dónde vive el estado
Para aprender, TextFlow no necesita base de datos todavía.
Alcanza con un almacenamiento en memoria.
Qué guarda el storage
El storage es responsable de:
- crear tareas
- obtener tareas por id
- actualizar estado
- guardar resultado
- guardar error
Qué no debería hacer el storage
No debería:
- saber de HTTP
- saber cómo se procesa el texto
- llamar al LLM
- conocer detalles del worker
Una idea importante
Aunque estés usando un simple diccionario en memoria, ya te conviene pensar como si existiera una pequeña capa de acceso a datos.
Eso evita que el diccionario quede manoseado desde todos lados.
Diagrama de responsabilidades
flowchart LR A[API] --> S[Storage] W[Worker] --> S P[Procesamiento] --> S
La API y el worker comparten el mismo estado, pero no deberían compartir el caos.
Parte IV — Cola y worker
Capítulo 7 — Qué es una cola y por qué importa
Una cola resuelve un problema muy concreto:
separar el ritmo al que se generan tareas del ritmo al que se procesan.
La API puede recibir diez pedidos en un segundo.
Eso no significa que tenga que procesarlos todos en ese segundo.
La cola sirve como buffer y como punto de desacople.
Cola local
En la primera versión de TextFlow vamos a usar una cola en memoria.
No es distribuida. No es persistente. No es enterprise.
Pero enseña la idea correcta.
Productor y consumidor
- la API es el productor
- el worker es el consumidor
Diagrama
flowchart LR A[API] --> B[Queue] B --> C[Worker]
Qué encolamos
Lo más simple y limpio es encolar el task_id.
Eso obliga al worker a buscar la tarea en el storage.
Ese detalle es bueno porque:
- desacopla el mensaje del estado completo
- evita duplicar información
- se parece más a cómo pensarías un sistema real
Capítulo 8 — El worker como pieza separada
El worker es quien realmente ejecuta el trabajo.
Qué hace el worker
En loop:
- espera una tarea en la cola
- la toma
- cambia el estado a
processing - ejecuta el procesamiento
- guarda
doneofailed
Qué no hace el worker
No atiende requests HTTP.
No genera IDs.
No decide el contrato de la API.
Por qué esta separación importa
Porque después podrás escalar:
- más instancias de API
- más instancias de worker
- otra cola
- otra base
- otro proveedor LLM
sin reescribir la idea base.
Diagrama más completo
flowchart TD A[Cliente] --> B[API] B --> C[Storage] B --> D[Queue] D --> E[Worker] E --> C E --> F[Procesador] F --> C
Capítulo 9 — Simular procesamiento de forma útil
Al principio, TextFlow no necesita un modelo real.
Conviene usar un procesamiento fake, por dos razones:
- separás la arquitectura del proveedor
- podés practicar estados y errores sin distraerte
Qué debería hacer el procesador fake
- esperar un poco
- transformar el texto de una forma simple
- a veces fallar a propósito
Por qué forzar fallos
Porque los sistemas reales no se miden por el camino feliz.
Se miden por cómo se comportan cuando algo sale mal.
En TextFlow, forzar un fallo te deja practicar:
- captura de excepciones
- actualización a
failed - separación entre error de una tarea y caída total del sistema
Parte V — Ciclo de vida y diseño limpio
Capítulo 10 — Startup, shutdown y tareas vivas
En una aplicación async, no alcanza con definir coroutines.
También hay que decidir cuándo nacen y cuándo terminan.
En TextFlow, el worker tiene que arrancar junto con la aplicación.
Startup
Cuando la app levanta:
- crea la cola
- inicializa el storage
- lanza el worker
Shutdown
Cuando la app termina:
- debería poder cerrar de forma ordenada
- idealmente sin dejar tareas a medio camino sin saberlo
En nuestra primera versión no vamos a resolver todo esto perfecto.
Pero sí vamos a incorporar una idea sana:
una app no es solo endpoints; también tiene procesos vivos asociados.
Capítulo 11 — Diseño limpio sin ponerse solemne
Hay una tentación común: cuando uno aprende arquitectura, quiere meter veinte capas.
No hace falta.
Para TextFlow alcanza con distinguir cuatro zonas:
- capa HTTP
- capa de dominio o flujo
- storage
- worker/procesamiento
Una estructura mental razonable
flowchart TB H[HTTP / Endpoints] --> D[Dominio / Casos de uso] D --> S[Storage] D --> Q[Queue] W[Worker] --> S W --> P[Procesamiento]
No es una religión. Es una forma de no mezclar todo.
La pregunta útil
Cada vez que escribís algo, preguntate:
¿esta pieza está donde corresponde o está acá porque me dio pereza separar?
Capítulo 12 — Errores y resiliencia básica
TextFlow puede fallar por muchas razones.
- input inválido
- tarea inexistente
- error de procesamiento
- proveedor externo caído
- timeout
Qué queremos lograr
No evitar todo fallo. Eso no existe.
Queremos que el sistema:
- falle de forma entendible
- marque estado correcto
- no se rompa entero por una sola tarea
Fallar bien
“Fallar bien” en TextFlow significa algo simple:
- si una tarea falla, termina en
failed - el worker sigue vivo
- otras tareas pueden seguir procesándose
Diagrama de fallo controlado
flowchart TD A[Worker toma tarea] --> B[Procesa] B --> C{Error?} C -->|No| D[Marcar done] C -->|Sí| E[Capturar excepción] E --> F[Marcar failed] F --> G[Continuar con siguiente tarea]
Parte VI — Observabilidad desde temprano
Capítulo 13 — Logs que sirven
Mucha gente agrega logs tarde y mal.
Conviene pensar logs desde el principio, aunque el sistema sea local.
Qué eventos vale la pena registrar en TextFlow
- creación de tarea
- encolado
- inicio de procesamiento
- fin correcto
- fin con error
El task_id como hilo conductor
Si todo el flujo menciona el mismo task_id, ya tenés una trazabilidad básica.
Eso parece mínimo, pero es una enorme mejora operativa.
Logs buenos y malos
Malo
- logs vagos
- mensajes repetidos
- no incluir identificadores
Bueno
- eventos concretos
- mensajes cortos
- identificador de tarea
- etapa del flujo
Capítulo 14 — Métricas mentales, aunque no tengas sistema de métricas todavía
Aunque todavía no tengas Prometheus ni CloudWatch, ya conviene pensar qué medirías.
En TextFlow las métricas naturales son:
- cantidad de tareas creadas
- cantidad de tareas completadas
- cantidad de tareas fallidas
- tiempo de procesamiento por tarea
- tareas pendientes en cola
Por qué importa pensar esto temprano
Porque después te obliga a diseñar mejor.
Si ni siquiera sabés qué te gustaría medir, probablemente todavía no entendés del todo qué hace tu sistema.
Parte VII — Escalar el ejercicio mentalmente
Capítulo 15 — Qué cambia cuando hay más de un worker
Hasta ahora imaginamos un solo worker.
Pero si TextFlow creciera, podríamos tener varios.
Qué mejora
- más throughput
- menos espera en cola
Qué se vuelve interesante
- orden de procesamiento
- competencia por mensajes
- estados consistentes
Diagrama con múltiples workers
flowchart LR A[API] --> B[Queue] B --> W1[Worker 1] B --> W2[Worker 2] B --> W3[Worker 3]
La idea importante
La cola deja de ser solo un buffer. Se vuelve un punto de distribución de trabajo.
Capítulo 16 — Qué cambia cuando el storage deja de ser en memoria
El storage local sirve para aprender, pero tiene límites obvios.
Si reiniciás la app, perdés todo.
Si corrés varias instancias, la memoria deja de ser compartida.
Qué aparecería en una siguiente versión
- base de datos real
- persistencia entre reinicios
- mejor soporte para múltiples instancias
Pero la arquitectura conceptual no cambia.
Seguís teniendo:
- API
- cola
- worker
- estado persistente
Eso es importante: la idea sobrevive al cambio de herramientas.
Parte VIII — LLMs sin humo
Capítulo 17 — Qué es un LLM en el contexto de TextFlow
Ahora sí metamos inteligencia artificial al ejemplo.
Hasta acá, el procesamiento era fake. Ahora imaginemos que TextFlow quiere generar un resumen real usando un LLM.
Qué es un LLM, en términos prácticos
Un LLM es una pieza a la que le das:
- una instrucción
- un contexto
- parámetros
Y te devuelve una salida generada.
No es una base de datos.
No es garantía de verdad.
No es lógica de negocio confiable por sí sola.
Es una herramienta probabilística útil para ciertas tareas.
En TextFlow, el LLM va a reemplazar al procesador fake
Antes teníamos:
- esperar unos segundos
- transformar el texto de forma simple
Ahora tendremos:
- construir un prompt
- invocar el modelo
- recibir una respuesta
- guardar el resultado
Diagrama actualizado
flowchart TD A[API] --> B[Queue] B --> C[Worker] C --> D[Construir prompt] D --> E[LLM] E --> F[Guardar resultado]
Capítulo 18 — Prompt, contexto y límites
No alcanza con “llamar un modelo”.
También hay que decidir qué le pedís y cómo.
Prompt
Es la instrucción que le das al modelo.
En TextFlow podría ser algo como:
- resumí el siguiente texto en tres puntos
- identificá ideas clave
- devolvé JSON con ciertos campos
Contexto
Es la información que acompaña el pedido.
En nuestro caso, el contexto principal es el input_text.
Más adelante podría haber:
- metadata del usuario
- tipo de tarea
- idioma
- reglas de formato
Límites importantes
Un LLM:
- puede tardar
- puede fallar
- puede responder mal
- puede alucinar
- tiene costo
Esa es una de las razones por las que encaja tan bien detrás de una cola y un worker.
Capítulo 19 — Integrar un LLM sin ensuciar todo
Una mala integración se ve así:
- el endpoint llama directo al modelo
- el prompt está hardcodeado en cualquier lado
- no hay manejo claro de errores
- no se registra nada útil
Una integración un poco más sana separa estas responsabilidades:
- la API acepta el trabajo
- el worker decide cuándo ejecutarlo
- una capa de cliente LLM se encarga de invocar al proveedor
- el resultado vuelve al storage
Cliente LLM
Conviene pensar una pequeña capa cliente que tenga a su cargo:
- armar la request al proveedor
- pasar parámetros relevantes
- devolver una respuesta limpia a tu dominio
- traducir errores del proveedor a algo manejable
Por qué esto importa
Porque más adelante podrías cambiar:
- de Bedrock a otro proveedor
- de un modelo a otro
- de resumen a clasificación
sin reescribir toda la app.
Capítulo 20 — Timeouts, retries y costo
Cuando integrás un LLM aparecen tres preguntas prácticas:
¿Cuánto tarda?
Si tarda mucho, no conviene dejar al cliente colgado.
¿Qué pasa si falla?
Necesitás decidir si:
- marcás
failed - reintentás
- degradás a un modo más simple
¿Cuánto cuesta?
Cada llamada puede tener costo por tokens o por uso.
Eso cambia cómo diseñás el sistema.
Qué conviene medir cuando entra un LLM
- duración de invocación
- cantidad de errores
- tamaño aproximado de input
- tamaño aproximado de output
- cuántas tareas usan el modelo
Parte IX — Amazon Bedrock dentro del mapa
Capítulo 21 — Qué es Bedrock
Bedrock es una capa administrada de AWS para acceder a modelos fundacionales.
La ventaja conceptual es simple:
- seguís dentro del ecosistema AWS
- usás IAM y permisos consistentes
- integrás inferencia con el resto de tu arquitectura cloud
Qué cambia en TextFlow si usamos Bedrock
Cambia el proveedor de inferencia.
No cambia la arquitectura principal.
Eso es bueno. Significa que el diseño estaba bien desacoplado.
Diagrama
flowchart TB A[Cliente] --> B[API] B --> C[Queue] C --> D[Worker] D --> E[Cliente Bedrock] E --> F[Modelo] D --> G[Storage]
Qué sigue igual
- la API no procesa
- el worker sigue siendo quien ejecuta
- el estado sigue viviendo aparte
Qué aparece nuevo
- permisos AWS
- configuración del cliente
- observabilidad del proveedor
- límites y errores específicos de inferencia
Capítulo 22 — Cómo pensar la integración con Bedrock
No hace falta entrar a SDKs para entender el diseño.
La integración sana de TextFlow con Bedrock tendría estas piezas:
- un worker que toma tareas
- un cliente Bedrock que sabe invocar el modelo
- prompts definidos con criterio
- manejo de excepciones del proveedor
- persistencia del output
Qué no conviene hacer
No conviene que Bedrock “invada” todo el sistema.
Por ejemplo, no hace falta que:
- el storage sepa de Bedrock
- el endpoint sepa cómo se arma el prompt
- el modelo de dominio dependa de detalles del proveedor
Bedrock es una dependencia de infraestructura, no el corazón conceptual de tu aplicación.
Parte X — RAG y agentes, pero sin confundir niveles
Capítulo 23 — Qué es RAG y cuándo entra
RAG significa, en esencia:
- buscar contexto relevante
- dárselo al modelo
- generar con esa base
En TextFlow, RAG entraría si el resumen no dependiera solo del texto enviado, sino también de documentos relacionados.
Ejemplo:
- el usuario sube un texto
- el sistema busca políticas internas relacionadas
- el modelo resume usando ambas cosas
Qué agrega RAG
- una capa de recuperación
- mejor grounding
- más complejidad
Qué no conviene hacer todavía
No metas RAG antes de entender bien:
- la API
- la cola
- el worker
- la persistencia
- el cliente LLM
RAG es una capa nueva. No una excusa para tapar una base floja.
Diagrama con RAG
flowchart TD A[Worker toma tarea] --> B[Buscar contexto] B --> C[Armar prompt con contexto] C --> D[LLM / Bedrock] D --> E[Guardar resultado]
Capítulo 24 — Qué es un agente y cuándo no hace falta
La palabra “agente” se usa demasiado. Conviene bajarla.
En términos simples, un agente es un flujo donde el modelo no solo genera texto, sino que también:
- decide pasos
- elige herramientas
- ejecuta acciones
- vuelve a razonar
En TextFlow, un agente sería necesario si…
En vez de solo resumir, el sistema tuviera que:
- clasificar el pedido
- decidir si buscar contexto
- consultar una herramienta
- sintetizar el resultado final
Cuándo no hace falta agente
Si solo querés resumir un texto, probablemente no hace falta.
Un error común es meter agentes donde alcanza con un pipeline claro.
Regla práctica
Si podés describir el flujo con pasos fijos, tal vez no necesitás un agente. Necesitás una orquestación simple.
Parte XI — AWS explicado simple
Capítulo 25 — Cómo pensar AWS sin memorizar servicios
AWS se entiende mejor si lo dividís por problemas, no por siglas.
Grandes categorías
- red
- compute
- storage
- bases de datos
- mensajería
- seguridad
- observabilidad
- IA
La pregunta correcta
En vez de preguntar “¿qué hace SQS?”, conviene preguntar:
tengo componentes desacoplados que necesitan intercambiar trabajo; ¿qué servicio de AWS resuelve eso?
Recién ahí aparece SQS.
Esa forma de pensar reduce muchísimo la confusión.
Capítulo 26 — VPC: para qué sirve de verdad
Una VPC es, conceptualmente, tu red privada dentro de AWS.
No es “para desplegar una API” en sí.
Es para controlar conectividad y aislamiento entre recursos.
Cuándo te importa una VPC
Cuando querés decidir cosas como:
- qué queda expuesto a internet
- qué queda privado
- qué puede hablar con qué
- cómo sale tráfico hacia afuera
Ejemplo con TextFlow
Imaginemos una versión cloud de TextFlow.
Podrías querer:
- un balanceador público
- servicios privados de API y worker
- una base privada
Diagrama simple de red
flowchart TB U[Usuario en Internet] --> ALB[ALB público] ALB --> API[API en subnet privada] API --> DB[(Base privada)] API --> Q[SQS] Q --> W[Worker en subnet privada] W --> DB
Qué no conviene pensar
No pienses la VPC como una pieza aislada y misteriosa.
Pensala como el contenedor de tu topología de red.
Capítulo 27 — SQS: para qué sirve una cola real
SQS es una cola administrada.
En el libro empezamos con una cola en memoria. SQS es la versión cloud seria de esa idea.
Para qué lo usarías en TextFlow
Para desacoplar:
- la API que recibe tareas
- del worker que las procesa
Qué ventajas te da
- absorber picos
- desacoplar ritmos
- distribuir trabajo entre workers
- manejar reintentos
Diagrama
flowchart LR A[API] --> B[SQS] B --> C[Worker 1] B --> D[Worker 2] B --> E[Worker 3]
Qué idea conviene guardar
SQS no procesa nada. Solo ordena el intercambio de trabajo.
Capítulo 28 — ECS, EKS, Lambda y EC2: dónde corre tu código
Acá aparece una de las confusiones más comunes.
La pregunta no es “cuál es mejor”.
La pregunta es:
qué modelo operativo quiero para correr mi aplicación.
EC2
Una máquina virtual.
Te da control, pero también responsabilidad.
Lambda
Funciones que corren por evento.
Muy útil cuando el trabajo es pequeño, stateless y encaja bien en ese modelo.
ECS
Contenedores administrados por AWS.
Suele ser una opción muy razonable para APIs y workers si querés Docker sin meterte de lleno en Kubernetes.
EKS
Kubernetes administrado.
Útil cuando realmente necesitás el ecosistema y el modelo de Kubernetes. No siempre conviene por defecto.
Qué elegiría para TextFlow
Para una versión simple y seria:
- API en ECS
- worker en ECS
- cola en SQS
- storage en base o DynamoDB
Diagrama
flowchart TB U[Usuario] --> ALB[ALB] ALB --> ECS1[Servicio API en ECS] ECS1 --> SQS[SQS] SQS --> ECS2[Servicio Worker en ECS]
Capítulo 29 — S3, RDS, DynamoDB y Redis: qué guardar y dónde
Una app seria necesita decidir dónde vive cada tipo de dato.
S3
Para archivos y objetos.
RDS / Aurora
Para datos relacionales y consultas estructuradas.
DynamoDB
Para modelos clave-valor o documento, especialmente cuando el acceso por clave está muy claro.
Redis / ElastiCache
Para caché, estados rápidos, colas en algunos diseños, o datos temporales.
En TextFlow
Podrías imaginar algo así:
- texto original grande en S3
- estado de tarea en DynamoDB o Postgres
- caché o rate-limiting en Redis
Diagrama simple
flowchart TD API[API] --> STATE[(Estado de tareas)] API --> FILES[(S3 para archivos)] Worker[Worker] --> STATE Worker --> FILES
La lección no es memorizar siglas. Es asociar tipos de datos con herramientas razonables.
Capítulo 30 — IAM y secretos: acceso sin caos
Cuando una app corre en AWS, necesita permisos.
No querés access keys pegadas por todos lados.
IAM
IAM resuelve identidad y permisos.
Roles
Una app debería tener un rol con los permisos mínimos que necesita.
Por ejemplo, el worker de TextFlow podría:
- leer de SQS
- escribir estado
- invocar Bedrock
Y nada más.
Secretos
Para configuración sensible existen servicios específicos.
La idea de fondo es simple:
los permisos y secretos no deberían estar mezclados arbitrariamente con el código.
Capítulo 31 — CloudWatch y observabilidad en AWS
Todo lo que aprendimos localmente sobre logs y trazabilidad sigue siendo válido en AWS.
Solo cambia el entorno.
Qué querés observar en TextFlow cloud
- logs de la API
- logs del worker
- errores del cliente LLM
- tiempos de procesamiento
- cantidad de tareas fallidas
La idea no cambia
Seguís queriendo poder responder preguntas como:
- ¿entró la tarea?
- ¿se encoló?
- ¿la tomó un worker?
- ¿falló Bedrock?
- ¿se guardó el resultado?
Parte XII — Arquitectura completa del ejemplo
Capítulo 32 — La arquitectura final de TextFlow
A esta altura ya podemos mostrar el sistema completo, de punta a punta.
Versión local
flowchart TB U[Usuario] --> API[API async] API --> ST[(Storage en memoria)] API --> Q[Queue en memoria] Q --> W[Worker async] W --> LLM[Procesador fake o LLM] W --> ST U --> GET["GET /tasks/{id}"] GET --> ST
Versión cloud
flowchart TB U[Usuario] --> DNS[Route 53] DNS --> ALB[ALB o API Gateway] ALB --> API[ECS API] API --> DB[(RDS o DynamoDB)] API --> SQS[SQS] SQS --> W[ECS Worker] W --> DB W --> B[Bedrock] API --> CW[CloudWatch] W --> CW
Qué cambió entre la primera y la última versión
No cambió la idea principal.
Cambió la infraestructura que sostiene la idea.
Eso es exactamente lo que uno quiere cuando el diseño base es bueno.
Capítulo 33 — Qué ideas nuevas aparecieron y por qué valieron la pena
A lo largo del libro agregamos piezas nuevas cuando el ejemplo las necesitó.
Primero
- task
- estados
- API
- cola
- worker
Después
- logs
- fallos controlados
- múltiples workers
- storage persistente mental
Más adelante
- cliente LLM
- prompt y contexto
- tiempo, costo y errores de inferencia
Finalmente
- Bedrock
- VPC
- SQS real
- ECS
- IAM
- observabilidad cloud
La lógica del aprendizaje fue esta:
no meter tecnología por moda, sino cuando el ejemplo la reclama.
Ese es también un buen criterio para la vida real.
Parte XIII — Cierre
Capítulo 34 — Qué deberías llevarte de verdad
Si este libro funcionó, no solo aprendiste nombres.
Aprendiste a pensar sistemas.
Lo esencial
1. async y await
No son decoración. Son una forma de modelar espera sin bloquear.
2. Una API no tiene por qué hacer todo
Aceptar trabajo y procesarlo después suele ser mejor.
3. Una cola desacopla
No resuelve magia. Resuelve ritmos distintos y separación de responsabilidades.
4. Un worker ejecuta trabajo fuera del request
Eso te da flexibilidad y claridad.
5. Un LLM es una dependencia del sistema, no el sistema entero
Tiene que entrar en una arquitectura sana.
6. Bedrock es una forma de consumir modelos dentro del mapa AWS
No reemplaza diseño.
7. AWS se entiende mejor por problemas que por nombres
- red
- compute
- storage
- mensajería
- seguridad
- observabilidad
- IA
La mejor conclusión posible
Si mañana tuvieras que diseñar una versión chica de TextFlow en serio, ya no arrancarías a ciegas.
Probablemente pensarías algo como esto:
- defino entidad y estados
- defino contrato API
- desacoplo procesamiento
- agrego cola y worker
- mido logs y errores
- recién después meto LLM y cloud
Ese orden mental vale muchísimo.
Apéndice A — Resumen de servicios AWS del libro
VPC
Para controlar red y aislamiento.
SQS
Para colas y desacople.
ECS
Para correr contenedores sin administrar Kubernetes directamente.
EKS
Para Kubernetes administrado si realmente lo necesitás.
Lambda
Para funciones por evento.
S3
Para archivos y objetos.
RDS / Aurora
Para datos relacionales.
DynamoDB
Para acceso clave-valor/documento a escala.
IAM
Para permisos.
CloudWatch
Para logs y métricas.
Bedrock
Para inferencia con modelos dentro del ecosistema AWS.
Apéndice B — Roadmap sugerido después de este libro
- implementar TextFlow local
- dockerizar API y worker
- pasar storage a SQLite o Postgres
- reemplazar cola en memoria por Redis o SQS
- integrar un LLM real
- mover el sistema a AWS
- agregar observabilidad seria
- experimentar con RAG
- recién después evaluar agentes
Apéndice C — Preguntas para verificar si entendiste
- ¿Por qué el endpoint de creación no debería procesar el texto directamente?
- ¿Qué problema resuelve una cola?
- ¿Qué diferencia hay entre API y worker?
- ¿Qué rol cumple el storage en el sistema?
- ¿Qué cambia cuando el procesador fake pasa a ser un LLM?
- ¿Qué valor agrega Bedrock y qué cosas no resuelve?
- ¿Para qué sirve una VPC en el ejemplo de TextFlow?
- ¿Cuándo elegirías ECS por sobre Lambda en este caso?
- ¿Qué métricas mínimas mirarías si el sistema falla?
Epílogo
El error más común al estudiar estos temas es aprenderlos por separado:
- por un lado async
- por otro APIs
- por otro AWS
- por otro LLMs
En la práctica, lo difícil no es memorizar cada pieza. Lo difícil es ver cómo encajan.
Por eso seguimos un solo ejemplo durante todo el libro.
Si al terminar sentís que ahora podés mirar un diagrama de arquitectura y entenderlo con bastante más tranquilidad, entonces el libro cumplió su trabajo.