mimic 👓 (Api gateway + Lambda + Dynamo) en golang

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!
Referencias
Subscribe to my newsletter
Read articles from Oscar Cortes Bracho directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Oscar Cortes Bracho
Oscar Cortes Bracho
Cloud Software Engineer