AWS Event-Driven Image Processing Platform
Plataforma fullstack de procesamiento de imágenes con pipeline event-driven. Sube una imagen y Lambda genera 3 variantes automáticamente vía S3 → SQS → Lambda → DynamoDB.
Arquitecturas AWS utilizadas
Problema & Solución
Problema
Una plataforma necesita procesar imágenes subidas por usuarios (resize a múltiples resoluciones) sin bloquear el request del cliente. El usuario debe poder subir la imagen y ver los resultados en tiempo real a medida que Lambda los genera, sin recargar la página.
Solución
Frontend en Astro 6 + React 19 con polling automático post-upload. Backend Express 5 que sube imágenes al bucket S3 input, disparando el pipeline event-driven: S3 → SQS → Lambda (Python + Pillow) → 3 variantes en S3 output + metadata en DynamoDB. Las imágenes se sirven con Signed URLs de CloudFront. El frontend hace polling cada 4s hasta detectar las imágenes procesadas.
Diagrama de Arquitectura
Browser (Astro + React 19)
│
├─ POST /api/v1/files/upload → S3 input (dispara pipeline)
├─ GET /api/v1/files → lista resized/ (polling cada 4s)
├─ GET /api/v1/files/signed-url → URL firmada CloudFront
└─ DELETE /api/v1/files → elimina 3 variantes en paralelo
│
▼
┌──────────────────────────────────┐
│ Express 5 Backend │
│ • Sube a image-resize/input/ │
│ • Lista image-resize/resized/ │
│ • Genera Signed URLs CloudFront │
└──────────────┬───────────────────┘
│ S3 Event (ObjectCreated)
▼
┌──────────────────────────────────────┐
│ SQS Queue → Lambda (Python + Pillow)│
│ → 800×600, 400×300, 150×150 │
│ → DynamoDB (metadata + status) │
│ → SNS (notificación) │
└──────────────────────────────────────┘
│
▼ Signed URLs
CloudFront Distribution (OAC → S3)Implementación
Frontend — Upload con React 19 + polling automático
ImageUploader valida MIME type en cliente antes de enviar. Tras un upload exitoso, ImageProcessor activa isProcessing=true e incrementa refreshTrigger. ImageGallery detecta el cambio y comienza polling cada 4 segundos consultando GET /api/v1/files. Cuando el número de grupos de imágenes aumenta (las variantes están listas), el polling se detiene automáticamente.
Backend — Upload a S3 y disparo del pipeline
El backend sube la imagen al prefijo image-resize/input/ del bucket S3. Este upload dispara automáticamente la S3 Event Notification → SQS → Lambda. El backend responde al cliente con la URL firmada de la imagen original antes de que Lambda la procese.
Pipeline Lambda — 3 variantes + DynamoDB
Lambda (Python + Pillow) descarga la imagen de S3 input, genera 3 versiones (800×600, 400×300, 150×150) y las sube a S3 resized/. Implementa idempotencia via DynamoDB: si la imagen ya fue procesada, no la reprocesa. SQS con MaxReceiveCount=3 y DLQ para mensajes fallidos.
Frontend — Galería agrupada con preview y eliminación
ImageGallery agrupa las imágenes por filename extrayéndolo del S3 key (image-resize/resized/800x600/abc.jpg). Muestra las 3 variantes en una grid. Al eliminar un grupo, ejecuta DELETE en paralelo (Promise.all) para las 3 variantes. ImagePreviewModal muestra las dimensiones reales del archivo usando img.naturalWidth/naturalHeight.
Tech Stack
Frontend
Backend
Pipeline AWS (Lambda)
Infraestructura
Decisiones Técnicas
Polling vs WebSockets para actualizar la galería
Elegido
Polling con intervalo adaptativo (4s)
Alternativas
- —WebSockets — tiempo real verdadero, requiere servidor adicional y más complejidad
- —Server-Sent Events — unidireccional, pero requiere conexión HTTP persistente al backend
- —AppSync Subscriptions — managed WebSockets, más costo y configuración
Razón
WebSockets requieren un servidor stateful adicional (ECS task o API Gateway WebSockets). Polling cada 4s es suficiente para una demo — Lambda procesa imágenes en 2-5 segundos. El polling se detiene automáticamente al detectar las imágenes nuevas, minimizando requests innecesarios.
Astro Islands con React vs SPA pura
Elegido
Astro Islands (React como isla interactiva)
Alternativas
- —Next.js SPA — hidrata toda la página, más JS en el bundle
- —Remix — server-side forms, más complejo para esta demo
Razón
La página tiene contenido estático (explicación de la arquitectura) que no necesita JavaScript. Solo la sección de demo (upload + galería) es interactiva. Astro Islands permite hidratación selectiva: solo los componentes React que necesitan interactividad se envían al cliente.
Snippets de Código
// Polling automático cuando isProcessing = true
// Se detiene cuando detecta nuevas imágenes o tras 30s de timeout
useEffect(() => {
if (!isProcessing) return;
let attempts = 0;
const MAX_ATTEMPTS = 30000 / 4000; // 30s / 4s = 7 intentos
const interval = setInterval(async () => {
attempts++;
const result = await listImages();
if (!result.success) return;
const newGroups = groupByFilename(result.data.files);
if (newGroups.length > prevGroupCount) {
setGroups(newGroups);
onProcessingComplete();
clearInterval(interval);
}
if (attempts >= MAX_ATTEMPTS) {
onProcessingComplete();
clearInterval(interval);
}
}, 4000);
return () => clearInterval(interval);
}, [isProcessing, prevGroupCount]);async function handleDeleteGroup(group: ImageGroup) {
const keys = [
group.variants['800x600']?.key,
group.variants['400x300']?.key,
group.variants['150x150']?.key,
].filter(Boolean) as string[];
// Elimina las 3 variantes en paralelo
const results = await Promise.all(keys.map(key => deleteImage(key)));
const allOk = results.every(r => r.success);
if (allOk) {
setGroups(prev => prev.filter(g => g.filename !== group.filename));
}
}