Continuando con la serie de mimic, después de explorar la implementación básica en JavaScript y la relación con las distintas herramientas de IaC, vamos a probar esta implementación con Go y Terraform. En este artículo te muestro cómo crear una API completa de almacenamiento JSON usando Lambda en Go, API Gateway y DynamoDB.
¿Qué es mimic?
Como vimos en el artículo anterior, mimic es un stack serverless simple, que permite:
POST
/mimic
- Almacenar cualquier JSON y obtener un ID único
GET/mimic/{id}
- Recuperar el JSON almacenado por su ID
Es como una base de datos en memoria que acepta cualquier estructura JSON, perfecta para testing, mocking de servicios y entornos efímeros.
¿Por qué Go + Terraform?
En el artículo sobre Lambda en Go con Terraform, exploramos las ventajas del runtime provided.al2023
. Para mimic, estas ventajas se multiplican:
Rendimiento superior: Go compila a binarios nativos, ideal para APIs de alta frecuencia
Costo optimizado: ARM64 (Graviton2) reduce costos hasta 50%
Escalabilidad: Cada operación (POST/GET) tiene su propia Lambda
Infraestructura como código: Terraform nos da control total y reproducibilidad
Estructura del proyecto
Aquí Link dejaré el repositorio con el código completo.
01_GST_mimic/
├── src/
│ ├── request/
│ │ ├── go.mod # Módulo Go para POST
│ │ ├── main.go # Handler POST
│ │ └── bootstrap # Binario compilado
│ └── response/
│ ├── go.mod # Módulo Go para GET
│ ├── main.go # Handler GET
│ └── bootstrap # Binario compilado
├── apigateway.tf # API Gateway + API Key
├── dynamo.tf # Tabla DynamoDB
├── lambdarequest.tf # Lambda POST + IAM
├── lambdaresponse.tf # Lambda GET
├── random.tf # Sufijos aleatorios
├── variables.tf # Variables configurables
├── outputs.tf # Outputs del módulo
└── README.md # Documentación
Implementación en Go
Lambda POST (Request)
La lambda de almacenamiento acepta cualquier JSON válido(Esto está así a propósito):
package main
import (
"context"
"encoding/json"
"log"
"os"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
"github.com/google/uuid"
)
type MimicItem struct {
ID string `json:"id"`
Body map[string]interface{} `json:"body"`
}
var dynamoClient *dynamodb.DynamoDB
var tableName string
func init() {
sess := session.Must(session.NewSession())
dynamoClient = dynamodb.New(sess)
tableName = os.Getenv("MIMIC_TABLE")
if tableName == "" {
tableName = "mimic-table"
}
}
func createBodyResponse(body map[string]interface{}) (string, error) {
id := uuid.New().String()
mimicItem := MimicItem{
ID: id,
Body: body,
}
av, err := dynamodbattribute.MarshalMap(mimicItem)
if err != nil {
return "", err
}
_, err = dynamoClient.PutItem(&dynamodb.PutItemInput{
TableName: aws.String(tableName),
Item: av,
})
return id, err
}
func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
log.Printf("Creating new mimic item")
var body map[string]interface{}
err := json.Unmarshal([]byte(request.Body), &body)
if err != nil {
log.Printf("Error parsing JSON: %v", err)
return events.APIGatewayProxyResponse{
StatusCode: 400,
Body: "Invalid JSON",
}, nil
}
id, err := createBodyResponse(body)
if err != nil {
log.Printf("Error creating item: %v", err)
return events.APIGatewayProxyResponse{
StatusCode: 500,
Body: "Internal server error",
}, nil
}
log.Printf("Created item with ID: %s", id)
return events.APIGatewayProxyResponse{
StatusCode: 200,
Body: id,
}, nil
}
func main() {
lambda.Start(handler)
}
Clave: Usamos map[string]interface{}
para aceptar cualquier estructura JSON sin validaciones específicas.
Lambda GET (Response)
La lambda de recuperación devuelve el JSON original:
// get.go
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)
type MimicItem struct {
ID string `json:"id"`
Body map[string]interface{} `json:"body"`
}
var dynamoClient *dynamodb.DynamoDB
var tableName string
func init() {
sess := session.Must(session.NewSession())
dynamoClient = dynamodb.New(sess)
tableName = os.Getenv("MIMIC_TABLE")
if tableName == "" {
tableName = "mimic-table"
}
}
func getBodyResponse(id string) (*MimicItem, error) {
result, err := dynamoClient.GetItem(&dynamodb.GetItemInput{
TableName: aws.String(tableName),
Key: map[string]*dynamodb.AttributeValue{
"id": {
S: aws.String(id),
},
},
})
if err != nil {
return nil, err
}
if result.Item == nil {
return nil, fmt.Errorf("item not found")
}
var item MimicItem
err = dynamodbattribute.UnmarshalMap(result.Item, &item)
if err != nil {
return nil, err
}
return &item, nil
}
func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
id, exists := request.PathParameters["id"]
if !exists {
return events.APIGatewayProxyResponse{
StatusCode: 400,
Body: "Missing id parameter",
}, nil
}
log.Printf("Getting mimic item with ID: %s", id)
item, err := getBodyResponse(id)
if err != nil {
log.Printf("Error getting item: %v", err)
return events.APIGatewayProxyResponse{
StatusCode: 404,
Body: "Item not found",
}, nil
}
responseBody, err := json.Marshal(item)
if err != nil {
log.Printf("Error marshaling response: %v", err)
return events.APIGatewayProxyResponse{
StatusCode: 500,
Body: "Internal server error",
}, nil
}
return events.APIGatewayProxyResponse{
StatusCode: 200,
Body: string(responseBody),
Headers: map[string]string{
"Content-Type": "application/json",
},
}, nil
}
func main() {
lambda.Start(handler)
}
Infraestructura Terraform
DynamoDB en terraform.
resource "aws_dynamodb_table" "mimic_table" {
name = "${var.table_name}-${random_id.suffix.hex}"
billing_mode = "PAY_PER_REQUEST"
hash_key = "id"
attribute {
name = "id"
type = "S"
}
point_in_time_recovery {
enabled = true
}
tags = {
Name = "MimicTable"
}
}
API Gateway con API Key y Cuotas en terraform
# API Gateway con autenticación
resource "aws_api_gateway_rest_api" "mimic_api" {
name = "${var.api_name}-${random_id.suffix.hex}"
description = "Mimic API for storing and retrieving JSON data"
}
# API Key para autenticación
resource "aws_api_gateway_api_key" "mimic_api_key" {
name = "${var.api_name}-key-${random_id.suffix.hex}"
description = "API Key for Mimic API"
}
# Usage Plan con cuotas mensuales
resource "aws_api_gateway_usage_plan" "mimic_usage_plan" {
name = "${var.api_name}-usage-plan-${random_id.suffix.hex}"
quota_settings {
limit = var.api_quota_limit # 1000 requests/month
period = "MONTH"
}
throttle_settings {
rate_limit = var.api_rate_limit # 10 req/sec
burst_limit = var.api_burst_limit # 20 burst
}
}
Compilación automática separada (No implementar external en PROD ⚠️)
Esto es para el despliegue desde el local, lo recomendable es hacer el build en los pipelines y no usar external.
# Build REQUEST Lambda
data "external" "build_create_lambda" {
program = ["bash", "-c", "cd src/request && go mod tidy && env GOOS=linux GOARCH=arm64 go build -o bootstrap main.go && echo '{\"filename\":\"bootstrap\"}'"]
}
# Build RESPONSE Lambda
data "external" "build_get_lambda" {
program = ["bash", "-c", "cd src/response && go mod tidy && env GOOS=linux GOARCH=arm64 go build -o bootstrap main.go && echo '{\"filename\":\"bootstrap\"}'"]
}
Importante: Cada lambda tiene su propio módulo Go y se compila independientemente.
Despliegue
# Clonar e inicializar
git clone https://github.com/tu-usuario/golang-serverless-terraform.git
cd golang-serverless-terraform/01_GST_mimic
# Configurar credenciales AWS
# Ref: https://gist.github.com/olcortesb/a471797eb1d45c54ad51d920b78aa664
# Desplegar
terraform init
terraform plan
terraform apply
Probando la API
# Obtener valores de salida
API_URL=$(terraform output -raw api_gateway_url)
API_KEY=$(terraform output -raw api_key_value)
# Almacenar JSON de usuario
curl -X POST "${API_URL}/mimic" \
-H "x-api-key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "Alice",
"email": "alice@example.com",
"preferences": {
"theme": "dark",
"notifications": true
}
}'
# Respuesta: "550e8400-e29b-41d4-a716-446655440000"
# Recuperar JSON
curl -X GET "${API_URL}/mimic/550e8400-e29b-41d4-a716-446655440000" \
-H "x-api-key: ${API_KEY}"
Comparativa Js vs Go
Aspecto | JavaScript | Go |
---|---|---|
Cold Start | ~200ms | ~50ms |
Memoria | 128 MB mínimo | 128 MB eficiente |
Costo | x86_64 estándar | ARM64 (-50%) Aproximado |
Tipado | Dinámico | Estático |
Compilación | Runtime | Build time |
Monitoreo y observabilidad
Terraform automáticamente configura:
CloudWatch Logs: Para debugging de las lambdas
API Gateway Metrics: Latencia, errores, throttling
DynamoDB Metrics: Read/Write capacity, throttling
Usage Plan Monitoring: Cuotas y límites de rate
Limpieza
terraform destroy
Conclusiones
La implementación de mimic en Go + Terraform nos ofrece:
Rendimiento superior: Cold starts más rápidos y mejor throughput
Costo optimizado: ARM64 reduce significativamente los costos
Infraestructura reproducible: Terraform garantiza consistencia
POC: Utilizaré este código como una POC para ir mejorando, actualizando y realizando pruebas sobre Golang + AWS Lambda
Este stack es perfecto para:
Entornos de desarrollo y testing
Mocking de servicios externos
Prototipado rápido de APIs
Cache temporal de datos
🙋 Laboratorios de infraestructura (Fundamentalmente para lo que lo uso …)
En el próximo artículo exploraremos cómo integrar mimic con otros servicios AWS como S3 Events y SQS para crear arquitecturas event-driven más complejas.
¡Gracias por leer, saludos!
Top comments (0)