AWS Modulo 3: Lambda con Go

Published: (December 14, 2025 at 05:21 PM EST)
5 min read
Source: Dev.to

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)

LenguajeCold StartMemory UsedWarm Invoke
Go156 ms48 MB45 ms
Node.js342 ms89 MB89 ms
Python478 ms127 MB134 ms
Java1,240 ms186 MB203 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.)

Back to Blog

Related posts

Read more »

AWS Terraform IAM User Management

Introduction Managing IAM users manually in AWS can quickly become complex, error‑prone, and difficult to scale. As teams grow, you need a repeatable, auditabl...

Day 16.Create IAM User

!Cover image for Day 16.Create IAM Userhttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads...