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
🚚 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
¿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
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
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%
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) { ... }
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
💻 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 != ""
}
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
}
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)
}
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?
La Solución de Go
# Un solo comando
GOOS=linux GOARCH=arm64 go build -o bootstrap ./cmd/lambda
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
Comandos:
make build # Compila para Lambda
make zip # Crea lambda.zip
Tamaños:
build/bootstrap: 7.2 MB (sin comprimir)
build/lambda.zip: 2.8 MB (comprimido)
Flags Importantes
-ldflags="-s -w" # Reduce tamaño 30-40%
CGO_ENABLED=0 # Binario estático (sin deps C)
🚀 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
}
}
}
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
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 .
Respuesta:
{
"statusCode": 201,
"body": {
"session_id": "f7a3b2c1-4d5e-6789-abcd-ef0123456789",
"user_id": "test-123",
"expires_at": 1734393600,
"message": "Session created successfully"
}
}
✅ 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\"}}"
¡Ahí está la sesión! 🎉
CloudWatch Logs
aws logs tail /aws/lambda/go-hexagonal-auth-dev-create-session --follow
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
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:
- Go: 45ms
- Node.js: 89ms
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
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
)
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
}
}
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
🔐 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
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
- Un comando, no Docker
-
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
- Reproducible
- Versionado
📊 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:
edgar-macias-se
/
go-hexagonal-auth
Production-ready Authentication Microservice in Go. Implements Hexagonal Architecture, JWT, Redis Blacklisting, and Rate Limiting.
🛡️ Go Secure Authentication Service
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.
🚀 Key Security Highlights
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.
- …
Carpetas:
-
terraform/- Lambda config -
cmd/lambda/- Handler -
internal/- Domain y adapters
💬 Tu Turno
¿Has usado Lambda con otros lenguajes? ¿Cuál ha sido tu experiencia con cold starts?
¿Prefieres serverless o servidores tradicionales? Cuenta tu caso de uso en los comentarios 👇
🔗 Conecta
- GitHub: @edgar-macias-se
- LinkedIn: edgar-macias-devcybsec
- Website: edgarmacias.com/es
- Dev.to: @emp_devcybsec
Serie: AWS Zero to Architect
Anterior: Módulo 2 - IAM Roles & DynamoDB
Siguiente: Módulo 4 - API Gateway (próximamente)
💡 Tip: Si este tutorial te ahorró horas de debugging, compártelo con alguien que esté empezando con serverless.
Top comments (0)