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

7 min de lectura Ver original en Hashnode
Compartir:

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.

Imagen: Diagrama de arquitectura mimic con Go />

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://www.terraform.io/

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_rest_api


Artículo original: mimic 👓 (Api gateway + Lambda + Dynamo) en golang