Prueba de concepto: Boletia, una aplicación para venta de boletos, basada en microservicios event-driven. Desarrollada sobre AWS Serverless: Api Gateway, Lambda, DynamoDB, DynamoDB Streams

Prueba de concepto: Boletia, una aplicación para venta de boletos, basada en microservicios event-driven.

Desarrollada sobre AWS Serverless: Api Gateway, Lambda, DynamoDB, DynamoDB Streams

Boletia En repositorios anteriores realizamos la prueba de concepto de la aplicación Boletia en un ambiente Kubernetes con Kafka (3 brokers) y MongoDB (3 réplicas).

https://github.com/avaco2312/boletia-kubernetes-kafka-mongodb https://github.com/avaco2312/kubernetes-kafka-cluster https://github.com/avaco2312/kubernetes-mongodb-replicaset

El objetivo ahora es realizar una prueba similar, pero soportada esta vez sobre los servicios Serverless de Amazon Web Services. Usaremos los servicios Api Gateway, Lambda, DynamoDB y DynamoDB Streams.

Los puntos de entrada del backend Boletia serán los mismos, lo que ahora el acceso es a través de Api Gateway, conectados a funciones Lambda. Los datos estarán soportados sobre DynamoDB.

En el esquema del inicio ahora los cilindros son tablas de DynamoDB y los hexágonos conjuntos de funciones Lambda. Para la comunicación entre los microservicios, en lugar de Kafka, usamos ahora DynamoDB Streams, que disparan funciones Lambda.

El esquema del Api Gateway definido está en apiGateway/Boletia-produccion-oas30-apigateway.yaml. Describamos los puntos de llamada del backend:

Dominio Admin.

Como en el repositorio anterior, este dominio gestiona los eventos y mediante llamadas a funciones Lambda actualiza la tabla evento, cuya estructura es:

  • PK (partition key): string, es el nombre del evento.
  • Estado: string ("A" activa o "C" cancelada)
  • Capacidad: int (capacidad del evento)
  • Categoría: string ("Rock", "Opera", "Fútbol", ...)

El código de las funciones Lambda en /eventos.

Endpoint Método Lambda Operación DynamoDB Resultado
/eventos GET findAll Scan Recupera todos los eventos
/eventos/{Evento} GET findOne getItem Recupera el evento "Evento"
/eventos POST insertEvento putItem Crea un evento, estado activo "A" (datos en el body)
/eventos/{Evento} DELETE cancelEvento updateItem Cancela el evento "Evento" (cambia el estado a "C")

Podemos hacer las llamadas correspondientes en Api Gateway (una vez todo implementado), por ejemplo, crear un evento:

curl -X POST https://oy7rgdejtk.execute-api.us-west-2.amazonaws.com/produccion/eventos
-d '{
        "evento":   "Opera Aida",
        "capacidad": 30,
        "categoria": "Opera"
    }'

HTTP/1.1 200 OK
Date: Mon, 06 Dec 2021 02:01:51 GMT
Content-Type: application/json
Content-Length: 88
Connection: close
x-amzn-RequestId: 4980b4df-ac94-46e4-89d7-979dd0306318
x-amz-apigw-id: J55KcH5jPHcFfhA=
X-Amzn-Trace-Id: Root=1-61ad6f0f-730fe2865d948c584057812b;Sampled=0
{
  "Evento": "Opera Aida",
  "Capacidad": 30,
  "Categoria": "Opera",
  "Estado": "A"
}

La creación de las tablas la hacemos con un programa independiente, en /initdb, que corremos desde nuestra computadora. Al crear la tabla eventos hemos definido, además de su estructura, que tenga habilitado DynamoDB Stream. Esto significa que al modificar un registro en eventos, se generará un registro en el stream, que podemos procesar con funciones Lambda.

Tenemos dos funciones Lambda asociadas al stream de eventos (se disparan al modificar eventos). Las hemos ubicado como parte del dominio Reservas porque propagan los cambios de la tabla eventos a la tabla inventario, que es el soporte de este dominio. El código en /reservas.

La función modificaInventarioEvento se encarga de reproducir los cambios de la tabla eventos (creación y cancelación) en la tabla inventario. La función modificaInventarioReserva la veremos más adelante.

Dominio Reservas:

Empecemos viendo la estructura de la tabla inventario. Esta sigue el diseño, muy común en DynamoDB, de "una sola tabla". Contiene dos tipos de registros:

  • Eventos: La copia, creada y actualizada automáticamente desde la tabla eventos, a través del stream-lambda antes mencionado.
  • Reservas: Los datos de las reservas que se creen mediante los puntos de acceso del Api Gateway.

La estructura es:

  • PK (partition key): string, "E", cuando el registro es un evento, "I" cuando es una reserva (id de la reserva)
  • SK (sort key): string, cuando PK es "E", el nombre de la reserva; cuando PK es I, la id de la reserva.
  • Cuando PK es "E", los campos que se registran son los de un evento (Estado, Capacidad y Categoría). Al igual que en el repositorio anterior aquí interpretaremos la Capacidad como Disponibilidad".
  • Cuando PK es "I" se registran los datos de una reserva, que son Evento, Email (del cliente), Estado (de la reserva) y Cantidad (de boletos comprados)

Un ejemplo de inventario sería:

PK SK Estado Capacidad Categoría Evento Cantidad Email
E Opera Aida A 30 Opera
I 21tBsOlzrdXa73UNotTK0nXIAm7 A Opera Aida 3 [email protected]
I 21tC9y2lJr3Aj1RQu6miT5usxf0 X Opera Aida 1 [email protected]
E Queen Live C 500 Rock
I 21tCLy6Y7EvxXEwMt4fzGJJccsc C Queen Live 30 [email protected]

Para ofrecer las posibilidades de consulta que requerirá el frontend, se define para la tabla inventario un índice secundario global, evento-email, cuya partition key es el campo Evento y la sort key Email.

Las llamadas para el dominio Reservas son:

Endpoint Método Lambda Operación DynamoDB Resultado
/reservas/eventos GET findAllEventos Query Recupera todos los eventos (en inventario)
/reservas/eventos/{Evento} GET findOneEvento getItem Recupera el evento "Evento" (en inventario)
/reservas/{Evento}/{Email} GET findReservasEventoEmail Query (índice secundario) Recupera las reservas del cliente "Email" para el evento "Evento"
/reservas/{Id} GET findReservaId getItem Recupera la reserva con id "Id"
/reservas POST insertReserva TransactWriteItems Crea una reserva, estado "A" (datos en el body)
/reservas/{Id} DELETE cancelReserva TransactWriteItems Cancela la reserva con id "Id", estado "X"

En este repositorio hemos aprovechado la operación de DynamoDB TransactWriteItems para actualizar, de forma consistente y simultánea, la disponibilidad del evento y el registro de la reserva, ambos en la tabla inventario, tanto al crear como al cancelar una reserva. Al crear se registra la reserva como activa y se disminuye la disponibilidad del evento por el número de boletos comprados. Al cancelar el estado de la reserva se cambia a "X" y se aumenta la disponibilidad del evento con los boletos liberados.

Hagamos algunas operaciones de ejemplo:

  • Listar los eventos disponibles en inventario (creados automáticamente desde eventos):
curl -X GET https://oy7rgdejtk.execute-api.us-west-2.amazonaws.com/produccion/reservas/eventos

HTTP/1.1 200 OK
Date: Mon, 06 Dec 2021 03:32:02 GMT
Content-Type: application/json
Content-Length: 206
Connection: close
x-amzn-RequestId: 7176fffb-99c9-4ce5-8752-aa8170d84e16
x-amz-apigw-id: J6GX6FuOPHcFc8g=
X-Amzn-Trace-Id: Root=1-61ad8432-1e92e4331ae4f8407cc485fa;Sampled=0
[
  {
    "Evento": "Opera Aida",
    "Capacidad": 30,
    "Categoria": "Opera",
    "Estado": "A"
  }
]
  • Hacer unas reservas:
curl -X POST  https://oy7rgdejtk.execute-api.us-west-2.amazonaws.com/produccion/reservas
-d '{
        "evento":   "Opera Aida",
        "cantidad": 1,
        "email": "[email protected]"
    }' 

HTTP/1.1 200 OK
Date: Mon, 06 Dec 2021 03:34:04 GMT
Content-Type: application/json
Content-Length: 139
Connection: close
x-amzn-RequestId: 24402507-93be-408c-a0a1-d8ab4a639165
x-amz-apigw-id: J6Gq2HSMPHcFneQ=
X-Amzn-Trace-Id: Root=1-61ad84ab-0d3b111b263d4e5250e3bdc8;Sampled=0
{
  "Id": "21tY2WfhKPDj0TDulNRlu2yszl4",
  "Evento": "Opera Aida",
  "Estado": "A",
  "Email": "[email protected]",
  "Cantidad": 1
}

curl -X POST  https://oy7rgdejtk.execute-api.us-west-2.amazonaws.com/produccion/reservas
-d '{
        "evento":   "Opera Aida",
        "cantidad": 10,
        "email": "[email protected]"
    }' 

HTTP/1.1 200 OK
Date: Mon, 06 Dec 2021 03:36:15 GMT
Content-Type: application/json
Content-Length: 139
Connection: close
x-amzn-RequestId: 99dbb1e2-3d35-49a8-8ace-56cc11db6d5a
x-amz-apigw-id: J6G_hHruvHcFeHg=
X-Amzn-Trace-Id: Root=1-61ad852f-3e095c387c3e2c752b3afb1c;Sampled=0
{
  "Id": "21tYJ9WFytF1KRq73EtsGjWrUE4",
  "Evento": "Opera Aida",
  "Estado": "A",
  "Email": "[email protected]",
  "Cantidad": 10
}
  • Cancelar una reserva (Id):
curl -X DELETE https://oy7rgdejtk.execute-api.us-west-2.amazonaws.com/produccion/reservas/21tYJ9WFytF1KRq73EtsGjWrUE4

HTTP/1.1 200 OK
Date: Mon, 06 Dec 2021 03:40:09 GMT
Content-Type: text/html
Content-Length: 48
Connection: close
x-amzn-RequestId: 8ec390f8-eb40-4761-b107-1da014e88732
x-amz-apigw-id: J6Hj8HNUPHcFlMg=
X-Amzn-Trace-Id: Root=1-61ad8619-337be8972d6ab9af31f39342;Sampled=0

Reserva Id 21tYJ9WFytF1KRq73EtsGjWrUE4 cancelada
  • Ver la reserva (cancelada):
curl -X GET https://oy7rgdejtk.execute-api.us-west-2.amazonaws.com/produccion/reservas/21tYJ9WFytF1KRq73EtsGjWrUE4

HTTP/1.1 200 OK
Date: Mon, 06 Dec 2021 03:40:33 GMT
Content-Type: application/json
Content-Length: 139
Connection: close
x-amzn-RequestId: ce2e6688-ef63-45fc-b0a9-7fb7b8f62437
x-amz-apigw-id: J6HnpHuNPHcFxAg=
X-Amzn-Trace-Id: Root=1-61ad8630-02ba5c2977ed98660ee681e4;Sampled=0
{
  "Id": "21tYJ9WFytF1KRq73EtsGjWrUE4",
  "Evento": "Opera Aida",
  "Estado": "X",
  "Email": "[email protected]",
  "Cantidad": 10
}
  • Verificar la disponibilidad del evento:
curl -X GET https://oy7rgdejtk.execute-api.us-west-2.amazonaws.com/produccion/reservas/eventos

HTTP/1.1 200 OK
Date: Mon, 06 Dec 2021 03:42:02 GMT
Content-Type: application/json
Content-Length: 206
Connection: close
x-amzn-RequestId: 7176fffb-99c9-4ce5-8752-aa8170d84e16
x-amz-apigw-id: J6GX6FuOPHcFc8g=
X-Amzn-Trace-Id: Root=1-61ad8432-1e92e4331ae4f8407cc485fa;Sampled=0
[
  {
    "Evento": "Opera Aida",
    "Capacidad": 29,
    "Categoria": "Opera",
    "Estado": "A"
  }
]

Mencionamos que el stream correspondiente a la tabla eventos dispara dos funciones Lambda, que hemos considerado parte del Dominio Reservas. Vimos que modificaInventarioEvento se encarga de duplicar el evento creado en la tabla eventos en la tabla inventario (lo crea con estado "A" y si se cancela lo pasa a estado "C".

La otra función, modificaInventarioReserva, también procesa el stream y se encarga de, cuando los administradores cancelan un evento, pasar a estado "C" todas las reservas de ese evento que estén activas (igual que en el repositorio anterior, una reserva puede tener estado "A" activa, "C" cancelado el evento por los organizadores ó "X" cancelada la reserva por el cliente).

Cómo DynamoDB no admite "update en masa" se realiza en partes:

  • Si el evento del stream es una cancelación de evento, se procesa.
  • Un Query por el índice secundario evento-email, de la tabla inventario, busca todas las reservas activas correspondientes a ese evento (solo especifica el evento y recupera todos los email).
  • Para cada una de estas reservas se recupera su Id y con la clave completa (PK "I" y SK Id), se hace un update de la reserva en inventario, poniendo el campo Estado en "C".

Probemos a cancelar un evento en la tabla eventos:

curl -X DELETE https://oy7rgdejtk.execute-api.us-west-2.amazonaws.com/produccion/eventos/Opera%20Aida

HTTP/1.1 200 OK
Date: Mon, 06 Dec 2021 03:43:14 GMT
Content-Type: text/html
Content-Length: 27
Connection: close
x-amzn-RequestId: 27321321-391b-4fb3-bc7f-2102047340a2
x-amz-apigw-id: J6IAyFPPPHcF44A=
X-Amzn-Trace-Id: Root=1-61ad86d1-41cb4cce0c01aa765cf0c8bf;Sampled=0

Evento Opera Aida cancelado

curl -X GET https://oy7rgdejtk.execute-api.us-west-2.amazonaws.com/produccion/reservas/21tY2WfhKPDj0TDulNRlu2yszl4

HTTP/1.1 200 OK
Date: Mon, 06 Dec 2021 03:47:30 GMT
Content-Type: application/json
Content-Length: 139
Connection: close
x-amzn-RequestId: a13236f5-1b26-4be5-a994-5e966a96c761
x-amz-apigw-id: J6Io4GiJvHcFS6g=
X-Amzn-Trace-Id: Root=1-61ad87d2-52221c1d7683a12b3c82c82f;Sampled=0
{
  "Id": "21tY2WfhKPDj0TDulNRlu2yszl4",
  "Evento": "Opera Aida",
  "Estado": "C",
  "Email": "[email protected]",
  "Cantidad": 1
}

Los eventos que estaban activos pasan a "C", cancelado el evento por los organizadores. Los que habían sido cancelados por el usuario siguen en estado "X".

El último dominio de la aplicación es Notificaciones. Igual que en el repositorio anterior envía automáticamente email al cliente sobre el estado de sus reservas. Para ello la tabla inventario también se crea con DynamoDB Stream habilitado.

Este stream se conecta a la función lambda notificaciones. Esta desecha los cambios en la tabla inventario que no se refieren a reservas (cuando se crea o cancela un evento). Con sólo los cambios que se refieren a reservas, envía el correspondiente mensaje usando el servicio AWS SES (los posibles tipos: creación de la reserva "A", cancelación por el usuario "X" ó cancelación del evento "C")

Los emails serían como:

Su reserva 21tY2WfhKPDj0TDulNRlu2yszl4 de 1 boletos para el evento Opera Aida está confirmada

Su reserva 21tC9y2lJr3Aj1RQu6miT5usxf0 de 3 boletos para el evento Opera Aida fue cancelada a petición suya

Su reserva 21tY2WfhKPDj0TDulNRlu2yszl4 de 1 boletos para el evento Opera Aida fue cancelada, el evento fue suspendido por los organizadores

Todo el deploy de Boletia en AWS pudiera hacerse automáticamente. Aquí lo hemos hecho mediante la consola AWS web. A lo mejor en un próximo proyecto lo presentamos, así como proteger nuestra API con Cognito, etc. Pero por el momento, habría muchos detalles por ver, pero uno resalta por su importancia: los permisos. A todas las funciones lambda que usamos hay que concederle los permisos que necesitan. Sin entrar en los detalles:

  • Todas necesitan el permiso básico de ejecución, mediante la política AWSLambdaBasicExecutionRole.
  • A las que son llamadas por API Gateway hay que agregarle los permisos para las operaciones de DynamoDB que ejecuten. Por ejemplo, para la función findReservaId una política adicional expresada por :
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "dynamodb:GetItem",
            "Resource": "arn:aws:dynamodb:us-west-2:440939667227:table/inventario"
        }
    ]
}
  • Hemos restringido estas políticas adicionales a las operaciones y recursos mínimos requeridos por cada función.
  • Las funciones lambda que se ejecutan conectadas a DynamoDB Stream necesitan la política AWSLambdaDynamoDBExecutionRole, en adición a las otras mencionadas.
  • A la función lambda notificaciones,que envía email mediante AWS SES, se le agrega:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "ses:SendEmail",
            "Resource": [
                "arn:aws:ses:*:440939667227:configuration-set/*",
                "arn:aws:ses:*:440939667227:identity/*"
            ]
        }
    ]
}

También es necesario, para las funciones lambda que procesan streams, definir un disparador que la conecte a la tabla dynamodb que corresponda.

Similar Resources

Lambda stack to turn off and destroy all resources from your personal AWS Account to avoid billing surprises

Lambda stack to turn off and destroy all resources from your personal AWS Account to avoid billing surprises

AWS, Turn off my Account, please Lambda stack to turn off and destroy all resources from your personal AWS Account to avoid billing surprises Resource

Oct 25, 2022

Golang AWS SAM Lambda example

Golang AWS SAM Lambda example This example project shows how to use AWS SAM with

Nov 18, 2022

Go-xrayprofile - Selective profiling of AWS Lambda functions

go-xrayprofile AWS X-Ray is handy for understanding the overall performance of y

May 18, 2022

A serverless sync server for Santa, built on AWS

Rudolph Rudolph is the control server counterpart of Santa, and is used to rapidly deploy configurations to Santa agents. Rudolph is built in Amazon W

Dec 5, 2022

Simple CRUD API written in Go, built using AWS SAM tool and using the AWS' infrastructure.

Simple CRUD API written in Go, built using AWS SAM tool and using the AWS' infrastructure.

tutor-pet API Simple CRUD API written in Go, built using AWS SAM tool and using the AWS' infrastructure. Macro architecture: Code architecture: Pre-Re

Aug 17, 2022

Aws-console-plugin - The current HashiCorp Vault AWS Secret Engine currently supports the creation of short lived API keys using the IAM User

aws-console-plugin Background The current HashiCorp Vault AWS Secret Engine curr

Feb 7, 2022

Api-waf-example-cdk - Demo using sam to drive a CDK serverless api

CDK SAM Demo Demo using sam to drive a CDK serverless api Stack Setup go build -

Feb 5, 2022

📚 API para o projeto final de LP2

📚 API - Sistema de Biblioteca Online API para o trabalho de LP2 Entitys check all entity in https://excalidraw.com/#json=FzJIjkvNyN_CnS_v9FcY0,aVEP5R

Jan 11, 2022

Simple no frills AWS S3 Golang Library using REST with V4 Signing (without AWS Go SDK)

simples3 : Simple no frills AWS S3 Library using REST with V4 Signing Overview SimpleS3 is a golang library for uploading and deleting objects on S3 b

Nov 4, 2022
lambda-go-api-proxy makes it easy to port APIs written with Go frameworks such as Gin to AWS Lambda and Amazon API Gateway.

aws-lambda-go-api-proxy makes it easy to run Golang APIs written with frameworks such as Gin with AWS Lambda and Amazon API Gateway.

Jan 6, 2023
Go-serverless-eth-event-listener - Go serverless, ethereum contract event listener with a sample contract

go-serverless-eth-event-listener This repository is for showing how to listen sm

May 19, 2022
This repository shows how can we use `AWS Lambda` to build serverless applications in golang.

Serverless Api in Go with AWS Lambda Here we are going to use AWS Lambda to build serverless applications in golang. Prerequisites You’ll need an AWS

Nov 3, 2021
Apis para la administracion de notifiaciones, utilizando servicios como AWS SNS y AWS SQS

notificacion_api Servicio para envío de notificaciónes por difusión en AWS SNS Especificaciones Técnicas Tecnologías Implementadas y Versiones Golang

Jan 7, 2022
A demonstration of the transactional outbox messaging pattern (+ Log Trailing) with Amazon DynamoDB (+ Streams) written in Go.
A demonstration of the transactional outbox messaging pattern (+ Log Trailing) with Amazon DynamoDB (+ Streams) written in Go.

Transactional Outbox Pattern in Amazon DynamoDB A demonstration of the transactional outbox messaging pattern (+ Log Trailing) with Amazon DynamoDB (+

Apr 12, 2022
Dynamodb-expire-non-latest - Dynamodb spike to find best solution to set expire on old records

Goal, expire non-latest records User (identified by IP address), adds record A,

Jan 5, 2022
Lambda microservice triggered by API Gateway to lookup ip address, domain or hash (md5, sha1, sha256)

lambdaGatewayAPI Lambda microservice triggered by API Gateway to lookup ip address, domain or hash (md5, sha1, sha256) How to deploy Build the lambdaG

Dec 21, 2021
Simple prueba de consumición de API por linea de comando

Go- catApi Motivo de la aplicación Es un simple programa creado en Go que emplea el API de cat facts para mostrar los datos según los parámetros en la

Feb 14, 2022
A Lambda function built with SAM (Serverless Application Module)

AWS SAM Lambda Function © Israel Pereira Tavares da Silva The AWS Serverless Application Model (SAM) is an open-source framework for building serverle

Dec 19, 2021
Mrrobot - A simple greetings bot for Slack that uses events api and hosted on AWS Lambda

Mr. Robot a greeter bot for your slack community build_docker

Aug 21, 2022