Event‑Driven Architecture (EDA) es un patrón de diseño en el cual los componentes del sistema se comunican a través de eventos. Este enfoque permite construir sistemas desacoplados, escalables y reactivos, donde cada parte del sistema responde a eventos en lugar de seguir un flujo de control predefinido.
Amazon S3 puede generar eventos cuando se sube un objeto s3:ObjectCreated:*
y estos eventos pueden invocar funciones AWS Lambda automáticamente. Con este patrón puedes automatizar tareas como redimensionado, análisis, notificaciones, o incluso sobreponer una marca de agua a imágenes subidas.
En este artículo construiremos un pipeline para:
- Procesar imágenes subidas a S3.
- Aplicar una marca de agua.
- Almacenarlas en otro bucket o prefijo.
Conceptos Fundamentales
Event Source y Event Target
- Event Source: Amazon S3 que emite notificaciones.
- Event Target: AWS Lambda que consume el evento.
Comunicación Asíncrona
Cada imagen es procesada como un evento independiente, lo que permite procesamiento paralelo, alta tolerancia y escalabilidad.
S3 Events & Lambda Triggers
S3 permite configurar notificaciones como:
"Events": ["s3:ObjectCreated:*"]
Estas notificaciones invocan funciones Lambda cuando se produce un evento compatible.
Implementación Práctica: Aplicar Marca de Agua
Flujo
- Usuario sube una imagen a bucket S3.
- Lambda aplica marca de agua con el logo.
- Imagen procesada se guarda en un prefijo o bucket definido.
Arquitectura
El flujo arquitectónico básico se ve así:
Configuración S3
{
"NotificationConfiguration": {
"LambdaFunctionConfigurations": [
{
"LambdaFunctionArn": "arn:aws:lambda:region:account:function:processImage",
"Events": ["s3:ObjectCreated:*"],
"Filter": {
"Key": {
"FilterRules": [
{ "Name": "suffix", "Value": ".jpg" }
]
}
}
}
]
}
}
Lambda Function
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import sharp from 'sharp';
const s3 = new S3Client({ region: 'us-east-1' });
async function streamToBuffer(stream) {
const chunks = [];
for await (const chunk of stream) {
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
}
return Buffer.concat(chunks);
}
export const handler = async (event) => {
const bucket = event.Records[0].s3.bucket.name;
const key = event.Records[0].s3.object.key;
// Obtener logo
const watermarkObj = await s3.send(new GetObjectCommand({
Bucket: 'assets-bucket',
Key: 'community-day-logo.png'
}));
const watermarkBuffer = await streamToBuffer(watermarkObj.Body);
// Redimensionar logo
const resizedWatermark = await sharp(watermarkBuffer)
.resize({ width: 300 })
.png()
.toBuffer();
// Obtener imagen original
const imageObj = await s3.send(new GetObjectCommand({
Bucket: bucket,
Key: key
}));
const originalBuffer = await streamToBuffer(imageObj.Body);
// Obtener metadatos de la imagen original para calcular padding
const metadata = await sharp(originalBuffer).metadata();
const paddingX = 20; // 20px desde el borde derecho
const paddingY = 20; // 20px desde el borde inferior
// Obtener dimensiones del logo redimensionado
const logoMetadata = await sharp(resizedWatermark).metadata();
const top = (metadata.height ?? 0) - (logoMetadata.height ?? 0) - paddingY;
const left = (metadata.width ?? 0) - (logoMetadata.width ?? 0) - paddingX;
// Componer con marca de agua redimensionada y padding
const watermarked = await sharp(originalBuffer)
.composite([
{
input: resizedWatermark,
top,
left
},
])
.jpeg()
.toBuffer();
// Subir imagen final con marca de agua
await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: `processed/mark-${key}`,
Body: watermarked,
ContentType: 'image/jpeg',
}));
};
Configuración Trigger
- Seleccione "Add trigger".
- En "Bucket", seleccione su bucket de origen.
- En "Event types", seleccione "All object create events".
- En "Recursive invocation", marque la casilla para confirmar que no se recomienda usar el mismo depósito de Amazon S3 para entrada y salida.
- Seleccione "Add".
Consideraciones
- Idempotencia: identifica la imagen procesada para no hacerlo dos veces.
- Observabilidad: CloudWatch Logs y métricas para seguimiento.
- Optimización de costos: memoria adecuada, timeouts y tamaño del paquete bien definidos.
Top comments (0)