AWS Modulo 3: Lambda con Go
Source: Dev.to
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 3× 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
🏢 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
128 MB RAM
200 ms promedio de duración
Cálculo:
Requests: 100,000 ÷ 1,000,000 × $0.20 = $0.02
Compute: 0.128 GB × 0.2 s × 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 | 156 ms | 48 MB | 45 ms |
| Node.js | 342 ms | 89 MB | 89 ms |
| Python | 478 ms | 127 MB | 134 ms |
| Java | 1,240 ms | 186 MB | 203 ms |
Resultado: Go es 3× más rápido en cold start.
¿Qué es un Cold Start?
Primera invocación del día:
1. AWS crea un container → 100 ms
2. Carga tu runtime (Node.js/Python) → 200 ms
3. Carga tu código → 100 ms
Total: ~400 ms
Con Go:
1. AWS crea container → 100 ms
2. Ejecuta binario → 50 ms
Total: ~150 ms
Para el usuario final: Go responde en ~150 ms, Python en ~400 ms.
Ventaja de Costos
Python (512 MB): $0.0000083 por 100 ms
Go (128 MB): $0.0000021 por 100 ms
Ahorro: 75 %
Con 1 M 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 # Infraestructura
💻 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/aws"
"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 {
item := map[string]interface{}{
"session_id": session.SessionID,
"user_id": session.UserID,
"expires_at": session.ExpiresAt.Unix(),
}
_, err := r.client.PutItem(ctx, &dynamodb.PutItemInput{
TableName: aws.String(r.tableName),
Item: marshalMap(item),
})
return err
}
Lambda Handler
package main
import (
"context"
"encoding/json"
"time"
"github.com/aws/aws-lambda-go/lambda"
"go-hexagonal-auth/internal/core/domain"
"go-hexagonal-auth/internal/adapters/repository"
)
type APIGatewayRequest struct {
Body string `json:"body"`
}
type CreateSessionRequest struct {
UserID string `json:"user_id"`
}
type Response struct {
StatusCode int `json:"statusCode"`
Body string `json:"body"`
}
func handler(ctx context.Context, request APIGatewayRequest) (Response, error) {
// 1. Parse input
var req CreateSessionRequest
if err := json.Unmarshal([]byte(request.Body), &req); err != nil {
return Response{StatusCode: 400, Body: err.Error()}, err
}
// 2. Create session (domain logic)
session := domain.NewSession(req.UserID, 24*time.Hour)
// 3. Save (adapter)
repo := repository.NewDynamoDBRepo(ctx)
if err := repo.Save(ctx, session); err != nil {
return Response{StatusCode: 500, Body: err.Error()}, err
}
// 4. Response
body, _ := json.Marshal(session)
return Response{
StatusCode: 201,
Body: string(body),
}, 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áquinas virtuales ni compilación 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 dependencias 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_exec.arn
handler = "bootstrap"
runtime = "provided.al2"
memory_size = 128
timeout = 10
# Opcional: variables de entorno, VPC, etc.
}
(Continúa la configuración de IAM, DynamoDB y demás recursos según tu infraestructura.)