mimic 👓 (Api gateway + Lambda + Dynamo) en golang
Continuando con la serie de mimic, despus de explorar la implementacin bsica en JavaScript y la relacin con las distintas herramientas de IaC, vamos a probar esta implementacin con Go y Terraform. En este artculo te muestro cmo crear una API completa de almacenamiento JSON usando Lambda en Go, API Gateway y DynamoDB.
Qu es mimic?
Como vimos en el artculo anterior, mimic es un stack serverless simple, que permite:
POST /mimic - Almacenar cualquier JSON y obtener un ID nicoGET /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 efmeros.
/>
Por qu Go + Terraform?
En el artculo 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 operacin (POST/GET) tiene su propia Lambda
Infraestructura como cdigo: Terraform nos da control total y reproducibilidad
Estructura del proyecto
Aqu Link dejar el repositorio con el cdigo completo.
01_GST_mimic/ src/ request/ go.mod # Mdulo Go para POST main.go # Handler POST bootstrap # Binario compilado response/ go.mod # Mdulo 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 mdulo README.md # Documentacin
Implementacin en Go
Lambda POST (Request)
La lambda de almacenamiento acepta cualquier JSON vlido(Esto est as a propsito):
package mainimport ( "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.DynamoDBvar tableName stringfunc 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 especficas.
Lambda GET (Response)
La lambda de recuperacin devuelve el JSON original:
// get.gopackage mainimport ( "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.DynamoDBvar tableName stringfunc 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 autenticacinresource "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 autenticacinresource "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 mensualesresource "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 }}
Compilacin automtica 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 Lambdadata "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 Lambdadata "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 mdulo Go y se compila independientemente.
Despliegue
# Clonar e inicializargit clone https://github.com/tu-usuario/golang-serverless-terraform.gitcd golang-serverless-terraform/01_GST_mimic# Configurar credenciales AWS # Ref: https://gist.github.com/olcortesb/a471797eb1d45c54ad51d920b78aa664# Desplegarterraform initterraform planterraform apply
Probando la API
# Obtener valores de salidaAPI_URL=$(terraform output -raw api_gateway_url)API_KEY=$(terraform output -raw api_key_value)# Almacenar JSON de usuariocurl -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 JSONcurl -X GET "${API_URL}/mimic/550e8400-e29b-41d4-a716-446655440000" \ -H "x-api-key: ${API_KEY}"
Comparativa Js vs Go
AspectoJavaScriptGo****Cold Start200ms50msMemoria128 MB mnimo128 MB eficienteCostox86_64 estndarARM64 (-50%) AproximadoTipadoDinmicoEstticoCompilacinRuntimeBuild time
Monitoreo y observabilidad
Terraform automticamente 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 lmites de rate
Limpieza
terraform destroy
Conclusiones
La implementacin de mimic en Go + Terraform nos ofrece:
Rendimiento superior: Cold starts ms rpidos y mejor throughput
Costo optimizado: ARM64 reduce significativamente los costos
Infraestructura reproducible: Terraform garantiza consistencia
POC: Utilizar este cdigo 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 rpido de APIs
Cache temporal de datos
🙋 Laboratorios de infraestructura (Fundamentalmente para lo que lo uso )
En el prximo artculo exploraremos cmo integrar mimic con otros servicios AWS como S3 Events y SQS para crear arquitecturas event-driven ms complejas.
Gracias por leer, saludos!
Referencias
https://github.com/olcortesb/mimic-src
https://docs.aws.amazon.com/lambda/latest/dg/lambda-golang.html
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_rest_api
Artículo original: mimic 👓 (Api gateway + Lambda + Dynamo) en golang