Перейти до вмісту
AWS Modulo 3: Lambda con Go
Source: Dev.to | Dec 14, 2025 (UTC)
Compilé para Linux sin Salir de mi Mac (y Costó $0.06)
📚 Serie: AWS Zero to Architect – Módulo 3
⏱️ Tiempo de lectura: 20 minutos
💻 Tiempo de implementación: 120 minutos
En los módulos anteriores configuramos AWS, Terraform e IAM. Ahora viene lo divertido: crear tu primera función Lambda en Go que cuesta centavos y es 3x más rápida que Python.
🤔 El Problema que Todos Tenemos
Escenario común:
“Tengo una API simple. ¿Monto un servidor 24/7 que me cuesta $50/mes aunque solo reciba 100 requests/día?”
Respuesta: NO. Usa Lambda.
⚡ Lambda Explicada: Food Truck vs Restaurant
Imagina que tienes un negocio de comida:
🏢 Restaurant (EC2 – Servidor Tradicional)
- Rentas local completo: $500/mes - Pagas luz/agua/gas 24/7 - Staff de tiempo completo - Si hay 3 clientes o 300, pagas lo mismo - Si hay demanda, el local se satura Costo fijo: $500/mes
Enter fullscreen mode
Exit fullscreen mode
🚚 Food Truck (Lambda – Serverless)
- Solo pagas cuando sirves un plato - $0.20 por cada 1 millón de platos - Sin staff fijo (AWS lo maneja) - Escala automáticamente - 3 clientes = $0.0006 - 300 clientes = $0.06 Costo variable: $0-$100/mes
Enter fullscreen mode
Exit fullscreen mode
¿Cuál tiene más sentido para una API que recibe tráfico esporádico?
💰 Pricing Real (Sin Marketing BS)
Mi Lambda Actual
100,000 requests/mes 128MB RAM 200ms promedio de duración Cálculo: Requests: 100,000 ÷ 1,000,000 × $0.20 = $0.02 Compute: 0.128GB × 0.2s × 100,000 × $0.0000166667 = $0.04 Total: $0.06/mes
Enter fullscreen mode
Exit fullscreen mode
Free Tier:
- 1 millón de requests/mes
- 400,000 GB-segundos
Traducción: Gratis para desarrollo y apps pequeñas.
🐹 ¿Por Qué Go y No Python/Node.js?
Benchmarks Honestos (Mis Propias Mediciones)
Lenguaje
Cold Start
Memory Used
Warm Invoke
Go
156ms
48MB
45ms
Node.js
342ms
89MB
89ms
Python
478ms
127MB
134ms
Java
1,240ms
186MB
203ms
Resultado: Go es 3x más rápido en cold start.
¿Qué es un Cold Start?
Primera invocación del día: 1. AWS crea un container → 100ms 2. Carga tu runtime (Node.js/Python) → 200ms 3. Carga tu código → 100ms Total: ~400ms Con Go: 1. AWS crea container → 100ms 2. Ejecuta binario → 50ms Total: ~150ms
Enter fullscreen mode
Exit fullscreen mode
Para el usuario final: Go responde en 150ms, Python en 400ms.
Ventaja de Costos
Python (512MB): $0.0000083 por 100ms Go (128MB): $0.0000021 por 100ms Ahorro: 75%
Enter fullscreen mode
Exit fullscreen mode
Con 1M requests/mes:
- Python: $83/mes
- Go: $21/mes
Diferencia: $62/mes × 12 meses = $744/año
🏗️ Arquitectura: Hexagonal en Serverless
¿Qué es Arquitectura Hexagonal?
Idea simple: La lógica de negocio NO debe depender de AWS.
❌ MAL (Acoplado a AWS): func CreateSession(userID string) { dynamodb.PutItem(...) // Directo a AWS } ✅ BIEN (Desacoplado): // Domain (puro Go, sin AWS) type Session struct { ID string UserID string } func NewSession(userID string) *Session { ... } // Adapter (traduce a AWS) type DynamoDBRepo struct { ... } func (r *DynamoDBRepo) Save(session *Session) { ... }
Enter fullscreen mode
Exit fullscreen mode
Ventajas:
- ✅ Puedes cambiar DynamoDB por Postgres sin tocar el dominio
- ✅ Testeable sin AWS
- ✅ Lógica de negocio clara
Mi Estructura
go-hexagonal-auth/ ├── cmd/lambda/main.go # Lambda handler ├── internal/ │ ├── core/domain/ │ │ └── session.go # Lógica de negocio │ └── adapters/repository/ │ └── dynamodb_session.go # AWS adapter └── terraform/ └── lambda.tf # Infrastructure
Enter fullscreen mode
Exit fullscreen mode
💻 El Código que Importa
Domain Model (Sin AWS)
package domain import ( "time" "github.com/google/uuid" ) type Session struct { SessionID string UserID string ExpiresAt time.Time CreatedAt time.Time } func NewSession(userID string, ttl time.Duration) *Session { now := time.Now() return &Session{ SessionID: uuid.New().String(), UserID: userID, CreatedAt: now, ExpiresAt: now.Add(ttl), } } func (s *Session) IsValid() bool { return s.SessionID != "" && s.UserID != "" }
Enter fullscreen mode
Exit fullscreen mode
Nota: CERO imports de AWS. Lógica pura.
DynamoDB Adapter
package repository import ( "context" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "go-hexagonal-auth/internal/core/domain" ) type DynamoDBRepo struct { client *dynamodb.Client tableName string } func (r *DynamoDBRepo) Save(ctx context.Context, session *domain.Session) error { // Conversión domain → DynamoDB item := map[string]interface{}{ "session_id": session.SessionID, "user_id": session.UserID, "expires_at": session.ExpiresAt.Unix(), } // PutItem en DynamoDB _, err := r.client.PutItem(ctx, &dynamodb.PutItemInput{ TableName: aws.String(r.tableName), Item: marshalMap(item), }) return err }
Enter fullscreen mode
Exit fullscreen mode
Lambda Handler
package main import ( "github.com/aws/aws-lambda-go/lambda" "go-hexagonal-auth/internal/core/domain" "go-hexagonal-auth/internal/adapters/repository" ) func handler(ctx context.Context, request APIGatewayRequest) (Response, error) { // 1. Parse input var req CreateSessionRequest json.Unmarshal([]byte(request.Body), &req) // 2. Create session (domain logic) session := domain.NewSession(req.UserID, 24*time.Hour) // 3. Save (adapter) repo := repository.NewDynamoDBRepo(ctx) repo.Save(ctx, session) // 4. Response return Response{ StatusCode: 201, Body: json.Marshal(session), }, nil } func main() { lambda.Start(handler) }
Enter fullscreen mode
Exit fullscreen mode
Flujo:
- Parse JSON
- Lógica de negocio (domain)
- Persistencia (adapter)
- Respuesta
🔨 Compilación Cross-Platform (La Magia de Go)
El Problema
Desarrollo en: macOS ARM64 (M1/M2/M3) Lambda ejecuta: Linux ARM64 ¿Cómo compilo para Linux sin salir de macOS?
Enter fullscreen mode
Exit fullscreen mode
La Solución de Go
# Un solo comando GOOS=linux GOARCH=arm64 go build -o bootstrap ./cmd/lambda
Enter fullscreen mode
Exit fullscreen mode
Eso es todo.
No necesitas:
- ❌ Docker
- ❌ Máquina virtual Linux
- ❌ Compilar en CI/CD
Go compila nativamente para otras plataformas.
Makefile (Automatización)
build: GOOS=linux GOARCH=arm64 CGO_ENABLED=0 \ go build -ldflags="-s -w" \ -o build/bootstrap ./cmd/lambda zip: build cd build && zip lambda.zip bootstrap
Enter fullscreen mode
Exit fullscreen mode
Comandos:
make build # Compila para Lambda make zip # Crea lambda.zip
Enter fullscreen mode
Exit fullscreen mode
Tamaños:
build/bootstrap: 7.2 MB (sin comprimir) build/lambda.zip: 2.8 MB (comprimido)
Enter fullscreen mode
Exit fullscreen mode
Flags Importantes
-ldflags="-s -w" # Reduce tamaño 30-40% CGO_ENABLED=0 # Binario estático (sin deps C)
Enter fullscreen mode
Exit fullscreen mode
🚀 Deploy con Terraform
Lambda Configuration
resource "aws_lambda_function" "create_session" { filename = "../build/lambda.zip" function_name = "go-hexagonal-auth-dev-create-session" role = aws_iam_role.lambda_execution.arn handler = "bootstrap" runtime = "provided.al2023" # ARM64 (Graviton2 - 20% más barato) architectures = ["arm64"] memory_size = 128 timeout = 10 environment { variables = { DYNAMODB_TABLE_NAME = aws_dynamodb_table.sessions.name } } }
Enter fullscreen mode
Exit fullscreen mode
Características:
-
runtime = "provided.al2023": Custom runtime para Go
-
architectures = ["arm64"]: Graviton2 (procesadores de AWS)
-
memory_size = 128: Suficiente para Go
¿Por qué ARM64?
Benchmarks:
Arquitectura
Precio/GB-seg
Performance
Relación
x86_64
$0.0000166667
Baseline
1.0x
ARM64
$0.0000133334
+10-15%
1.15x
Resultado: ARM64 es 20% más barato Y 10% más rápido.
Deploy
# 1. Compilar make build zip # 2. Deploy cd terraform terraform apply
Enter fullscreen mode
Exit fullscreen mode
Tiempo de deploy: ~15 segundos
🧪 Testing (La Parte Satisfactoria)
Test Básico
# Invocar Lambda aws lambda invoke \ --function-name go-hexagonal-auth-dev-create-session \ --payload '{"body": "{\"user_id\": \"test-123\"}"}' \ response.json # Ver resultado cat response.json | jq .
Enter fullscreen mode
Exit fullscreen mode
Respuesta:
{ "statusCode": 201, "body": { "session_id": "f7a3b2c1-4d5e-6789-abcd-ef0123456789", "user_id": "test-123", "expires_at": 1734393600, "message": "Session created successfully" } }
Enter fullscreen mode
Exit fullscreen mode
✅ Primera invocación: 156ms (cold start)
✅ Segunda invocación: 45ms (warm)
Verificar en DynamoDB
SESSION_ID=$(cat response.json | jq -r '.body.session_id') aws dynamodb get-item \ --table-name sessions \ --key "{\"session_id\": {\"S\": \"$SESSION_ID\"}}"
Enter fullscreen mode
Exit fullscreen mode
¡Ahí está la sesión! 🎉
CloudWatch Logs
aws logs tail /aws/lambda/go-hexagonal-auth-dev-create-session --follow
Enter fullscreen mode
Exit fullscreen mode
Output:
START RequestId: abc-123 Received request: {"body": "{\"user_id\":\"test-123\"}"} Session created: f7a3b2c1... for user: test-123 END RequestId: abc-123 REPORT Duration: 156ms Memory: 48MB
Enter fullscreen mode
Exit fullscreen mode
Métricas:
- Duration: 156ms
- Memory Used: 48MB (de 128MB)
- Billed Duration: 200ms
🎯 Resultados Reales
Performance
Cold Start (primera invocación del día):
- Mi Lambda Go: 156ms
- Lambda Node.js equivalente: 342ms
- Mejora: 2.2x más rápido
Warm Invocations:
Costos (100k requests/mes)
Go Lambda: 128MB × 200ms × 100,000 requests = $0.06/mes Node.js Lambda (mismo workload): 256MB × 200ms × 100,000 requests = $0.12/mes Ahorro: $0.06/mes × 12 = $0.72/año
Enter fullscreen mode
Exit fullscreen mode
Multiplicado por 10 Lambdas: $7.20/año de ahorro.
🆘 Problemas que Tuve (Y Cómo los Resolví)
Error: “Runtime.ExitError exit status 2”
Causa: Faltaba import de net/http en main.go
Fix:
import ( "net/http" // ← Agregar esto // ... otros imports )
Enter fullscreen mode
Exit fullscreen mode
Error: “DYNAMODB_TABLE_NAME not set”
Causa: Variable de entorno mal configurada en Terraform
Fix:
environment { variables = { DYNAMODB_TABLE_NAME = aws_dynamodb_table.sessions.name # NO: DYNAMODB_TABLE_SESSIONS } }
Enter fullscreen mode
Exit fullscreen mode
Error: “exec format error”
Causa: Compilé para x86 en lugar de ARM64
Fix:
# Verificar arquitectura file build/bootstrap # Debe decir: ARM aarch64 # Recompilar GOARCH=arm64 make build
Enter fullscreen mode
Exit fullscreen mode
🔐 Seguridad: ¿Es Pública Mi Lambda?
Respuesta Corta: NO
Situación actual (Módulo 3):
Internet → ❌ NO PUEDE ACCEDER AWS CLI con credenciales → ✅ PUEDE INVOCAR
Enter fullscreen mode
Exit fullscreen mode
Solo es invocable con:
- AWS CLI + credenciales
- IAM permissions
Nadie en internet puede ejecutarla.
Módulo 4: API Gateway (Próximo)
Ahí sí la haremos pública con:
- ✅ HTTPS endpoint
- ✅ Throttling (límite de requests)
- ✅ API Keys (opcional)
💡 Lo Que Aprendí
-
Go es RÁPIDO para serverless
- Cold starts 3x más rápidos
- 75% más barato en memoria
-
Compilación cross-platform es magia
-
Arquitectura Hexagonal vale la pena
- Testeable sin AWS
- Fácil cambiar DynamoDB
-
ARM64 > x86_64
- 20% más barato
- 10% más rápido
-
Terraform > ClickOps
📊 Comparación Final
Característica
EC2 t3.micro
Lambda Go
Costo base
$7.50/mes
$0.00
Escalado
Manual
Automático
Cold start
N/A
156ms
Warm invoke
N/A
45ms
Mantenimiento
Tú
AWS
100k req/mes
$7.50
$0.06
Ganador: Lambda (para tráfico esporádico)
🎓 Lo Que Lograste
Si llegaste hasta aquí e implementaste todo:
- ✅ Primera Lambda function en Go
- ✅ Compilación cross-platform
- ✅ Arquitectura Hexagonal
- ✅ Integración con DynamoDB
- ✅ Deploy con Terraform
- ✅ Testing funcional
Y todo por < $0.10/mes. 🎉
🚀 Próximo Paso: API Gateway
En el Módulo 4 vamos a:
- Crear endpoint público HTTPS
- Configurar CORS
- Agregar throttling
- Testear con Postman
La Lambda será accesible desde cualquier lugar (con controles de seguridad).
📦 Código Completo
Todo el código está en GitHub:
Un microservicio de autenticación robusto, escalable y listo para producción, escrito en Go siguiendo principios de Arquitectura Hexagonal.
Diseñado con la seguridad como prioridad, implementando las mejores prácticas de OWASP para la gestión de identidad, sesiones y protección contra ataques.
Este no es solo un login básico. Este proyecto implementa capas de defensa en profundidad:
- 🔒 Arquitectura Hexagonal (Ports & Adapters): Desacoplamiento total entre la lógica de negocio, la base de datos y la API HTTP. Código testable y mantenible.
- 🔑 Estrategia de Tokens Duales (JWT):
- Access Token (15 min): JWT firmado (HS256) de vida corta para minimizar riesgos en caso de robo.
- Refresh Token (7 días): Token opaco rotativo almacenado en BD para renovar sesiones sin exponer credenciales.
- 🛡️ Protección contra Fuerza Bruta (Rate Limiting): Middleware distribuido usando Redis. Bloquea IPs/Usuarios tras 5 intentos fallidos por 15 minutos.
- …
Read original article →
Залишити відповідь