Want to Contribute to us or want to have 15k+ Audience read your Article ? Or Just want to make a strong Backlink?

Crud Dynamodb usando funciones lambdas y apigateway

Amazon Net Providers

La plataforma en la nube Amazon Net Providers (AWS) ofrece multiples servicios integrales para que los desarrolladores de software program puedan implementar sus soluciones de manera rápida y relativamente fácil.

Hoy en día es indispensable crear servicios internet para entregar y capturar información para que las aplicaciones puedan conectarse entre ellas, los servicios relaxation son de los más usados actualmente.



Ejercicio práctico

Para este ejemplo se crea una api relaxation que realice operaciones CRUD (Crear, Leer, Actualizar y Eliminar) de productos dentro de una base de datos NoSQL proporcionada por AWS, se implementa funciones lambdas para realizar el código y el servicio de Apigateway para consumir desde un cliente.

Requisitos previos:

  • Cuenta activa de AWS
  • Tener configurada las credenciales AWS (AWS CLI y SAM CLI) en el ordenador.
  • Conocimientos esenciales de serverless.
  • Conocimientos esenciales de Cloudformation.
  • Tener instalado nodejs (para este ejercicio se usa v16)

Tecnologías a usar en AWS

  • AWS CLI y SAM CLI
  • Cloudformation
  • Lambdas (Nodejs)
  • Dynamodb
  • Apigateway

A continuación se detalla brevemente y se pone en contexto de las herramientas de AWS a usar.

Serverless

Serverless quiere decir sin servidor, permite al desarrollador crear y ejecutar aplicaciones con raṕidez y menor costo, no se preocupa por gestionar infraestructura de servidores donde se aloja el código, simplemente se enfoca en codear. Puedes revisar más del tema en este artículo: Serverless

AWS CLI y SAM CLI

AWS CLI es una herramienta de línea de comandos de AWS, se usa para administrar sus servicios en AWS. Se debe configurar las credenciales con aws configure, ver el siguiente enlace:AWS CLI

SAM

Significa modelo de aplicación sin sevidor de AWS, es un framework que se utiliza para crear apliaciones sin servidor en AWS. También tiene una interfaz de línea de comandos para poder desplegar servicios, revisar el siguiente hyperlink para su instalación dependiendo del sistema operativo: SAM CLI

Cloudformation

AWS CloudFormation es un servicio que ofrece a desarrolladores una manera sencilla de crear una colección de recursos de AWS y organizarlos como si fuese archivos de código.

Lambdas

AWS Lambda permite correr código sin administrar servidores, el desarrollador solo escribe trozos de código (funciones) y se las sube a este servicios, se puede usar con varios lenguajes entre ellos: python, javascript y java. Para más información revisar: AWS Lambda

Dynamodb

Es un servicio de base de datos NoSQL de AWS, permite almacenar datos en documentos y valores clave.

Api Gateway

Amazon Api Gateway es un servicio de AWS para administrar y proteger servicios de API REST, HTTP y WebSockets. Permite conectarse a otros servicios y consumirlas mediante una url.
Para más información revisar el siguiente enlace: AWS Api Gateway

Infraestructura a crear

Se indica un pequeño diagrama de como va funcionar la aplicación CRUD a crear.



Inicio de proyecto

Dentro de un directorio en la consola colocar el comando sam init y completar las instrucciones de la siguiente manera:

Console 1

Console 2

Se debe generar un proyecto en la siguiente estructura:

Estructura de proyecto

Esta estructura solo nos sirve como ejemplo de partida, para un mejor management del proyecto se configura de la siguiente manera, quitar la carpeta de assessments y el archivo principal app.ts, la carpeta hello-world se cambia por app:

New infra

Instalar dependencias
Dentro de la carpeta app en donde se encuentra el bundle.json ejecutar lo siguiente:

npm set up
npm set up aws-sdk
npm set up short-uuid
Enter fullscreen mode

Exit fullscreen mode

DynamoDB, servicios y modelo

En las siguientes carpteas crear los siguientes archivos:

Services

DynamoClient.ts

Nos genera una instancia cliente para poder realizar operaciones en una tabla de Dynamo.

import DynamoDB from 'aws-sdk/purchasers/dynamodb';
export const dynamoClient = new DynamoDB.DocumentClient();
Enter fullscreen mode

Exit fullscreen mode

Product.ts

Es un molde de los campos que va a contener el producto.

export interface Product{
    id: string;
    identify: string;
    value: quantity;
    description: string;
}
Enter fullscreen mode

Exit fullscreen mode

ProductService.ts

Es una clase que nos va permitir realizar la lógica de cada una de las funciones que estamos usando, en este caso operaciones CRUD:

import { dynamoClient } from "../dynamodb/DynamoClient";
import { Product } from '../fashions/Product';
import { generate } from 'short-uuid';


export class ProductService {

    static async getProducts(): Promise<Product[]> {
        strive {
            const tableName = course of.env.TABLE_NAME ?? ''
            const response = await dynamoClient.scan({
                TableName: tableName
            }).promise();

            return response.Objects as Product[];
        } catch (error) {
            throw new Error("Error getting merchandise: " + error);
        }
    }

    static async getProductById(id: string): Promise<Product> {
        strive {
            const tableName = course of.env.TABLE_NAME ?? ''
            const response = await dynamoClient.get({
                TableName: tableName,
                Key: {
                    id
                }
            }).promise();
            return response.Merchandise as Product;
        } catch (error) {
            throw new Error("Error getting product by id: " + error);
        }
    }

    static async createProduct(product: Product): Promise<Product> {
        strive {
            const tableName = course of.env.TABLE_NAME ?? ''
            product.id = generate();
            await dynamoClient.put({
                TableName: tableName,
                Merchandise: product
            }).promise();
            return product;
        } catch (error) {
            throw new Error("Error creating product: " + error);
        }
    }

    static async updateProductById(product: Product): Promise<Product> {
        strive {
            const tableName = course of.env.TABLE_NAME ?? ''
            await dynamoClient.replace({
                TableName: tableName,
                Key: {
                    id: product.id
                },
                UpdateExpression: "set #identify = :identify, #value = :value, #description = :description",
                ExpressionAttributeNames: {
                    "#identify": "identify",
                    "#value": "value",
                    "#description": "description"
                },
                ExpressionAttributeValues: {
                    ":identify": product.identify,
                    ":value": product.value,
                    ":description": product.description
                }
            }).promise();
            return product;
        } catch (error) {
            throw new Error("Error updating product: " + error);
        }
    }

    static async deleteProductById(id: string): Promise<void> {
        strive {
            const tableName = course of.env.TABLE_NAME ?? ''
            await dynamoClient.delete({
                TableName: tableName,
                Key: {
                    id
                }
            }).promise();
        } catch (error) {
            throw new Error("Error deleting product: " + error);
        }
    }

}
Enter fullscreen mode

Exit fullscreen mode

Funciones

Dentro de la carpeta funciones se crea un archivo por cada función.

Funciones

En las funciones solo hace falta llamar al servicio creado anteriormente y retornar datos:

create.ts

import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { ProductService } from '../companies/ProductService';
import { Product } from '../fashions/Product';

/**
 * Operate to create a brand new product
 * @param occasion 
 * @returns 
 */
export const handler = async (occasion: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
    const newProduct: Product = JSON.parse(occasion.physique as string);
    const product = await ProductService.createProduct(newProduct);
    return {
        statusCode: 201,
        physique: JSON.stringify({
            merchandise: product
        })
    }
}
Enter fullscreen mode

Exit fullscreen mode

deleteById.ts

import { ProductService } from '../companies/ProductService';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

/**
 * Operate to delete a product by id
 * @param occasion 
 * @returns 
 */
export const handler = async (occasion: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
    const id = occasion.pathParameters?.id;
    if (!id) {
        return {
            statusCode: 400,
            physique: JSON.stringify({
                message: "id is required"
            })
        }
    }
    await ProductService.deleteProductById(id);
    return {
        statusCode: 204,
        physique: 'Product deleted'
    }
}
Enter fullscreen mode

Exit fullscreen mode

getAll.ts

import { ProductService } from '../companies/ProductService';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

/**
 * Operate to get all merchandise
 * @param occasion 
 * @returns 
 */
export const handler = async (occasion: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
    const merchandise = await ProductService.getProducts();
    return {
        statusCode: 200,
        physique: JSON.stringify({
            gadgets: merchandise
        })
    }
}
Enter fullscreen mode

Exit fullscreen mode

getById.ts

import { ProductService } from '../companies/ProductService';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

/**
 * 
 * @param occasion Operate to get product by id
 * @returns 
 */
export const handler = async (occasion: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
    const id = occasion.pathParameters?.id;
    if (!id) {
        return {
            statusCode: 400,
            physique: JSON.stringify({
                message: "id is required"
            })
        }
    }
    const product = await ProductService.getProductById(id);
    return {
        statusCode: 200,
        physique: JSON.stringify({
            merchandise: product
        })
    }
}
Enter fullscreen mode

Exit fullscreen mode

updateById.ts

import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { ProductService } from '../companies/ProductService';
import { Product } from '../fashions/Product';

/**
 * Operate to replace a product by id
 * @param occasion 
 * @returns 
 */
export const handler = async (occasion: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
    const updatedProduct: Product = JSON.parse(occasion.physique as string);
    const product = await ProductService.updateProductById(updatedProduct);
    return {
        statusCode: 200,
        physique: JSON.stringify({
            merchandise: product
        })
    }
}
Enter fullscreen mode

Exit fullscreen mode

Template.yaml

En este archivo se configura toda la infraestructura que se va a desplegar en AWS: lambdas, apigateway, roles, tablas, and so on. La configuración es cloudformation con características avanzadas proporcionadas por SAM para desplegar aplicaciones serverless.

Se configura las propiedades de algunos recursos de manera world para evitar repetir cosas:

Globals

En los recursos para cada una de las funciones debe quedar de la siguiente manera:

Function resource

La parte de Insurance policies es muy importante, se le da permisos a la función lambda para ejecutar operaciones en la tabla de dynamo que estamos creando.

A continuación se muestra el template completo:

AWSTemplateFormatVersion: '2010-09-09'
Remodel: AWS::Serverless-2016-10-31
Description: >
  crud-products-serverless

  Pattern SAM Template for crud-products-serverless

# Extra data about Globals: https://github.com/awslabs/serverless-application-model/blob/grasp/docs/globals.rst
Globals:
  Operate:
    CodeUri: app
    Timeout: 10
    Tracing: Lively
    Runtime: nodejs16.x
    Architectures:
      - x86_64
    Surroundings:
      Variables:
        TABLE_NAME: !Ref ProductTable

  Api:
    TracingEnabled: True

Assets:
  CreateProductFunction:
    Sort: AWS::Serverless::Operate
    Properties:
      Handler: src/features/create.handler
      Occasions:
        CreateProduct:
          Sort: Api
          Properties:
            Path: /merchandise
            Methodology: submit
      Insurance policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref ProductTable
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Goal: "es2020"
        EntryPoints: 
        - src/features/create.ts

  UpdateProductFunction:
    Sort: AWS::Serverless::Operate
    Properties:
      Handler: src/features/updateById.handler
      Occasions:
        CreateProduct:
          Sort: Api
          Properties:
            Path: /merchandise
            Methodology: put
      Insurance policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref ProductTable
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Goal: "es2020"
        EntryPoints: 
        - src/features/updateById.ts

  DeleteByIdProductFunction:
    Sort: AWS::Serverless::Operate
    Properties:
      Handler: src/features/deleteById.handler
      Occasions:
        CreateProduct:
          Sort: Api
          Properties:
            Path: /merchandise/{id}
            Methodology: delete
      Insurance policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref ProductTable
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Goal: "es2020"
        EntryPoints: 
        - src/features/deleteById.ts

  GetAllProductsFunction:
    Sort: AWS::Serverless::Operate
    Properties:
      Handler: src/features/getAll.handler
      Occasions:
        CreateProduct:
          Sort: Api
          Properties:
            Path: /merchandise
            Methodology: get
      Insurance policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref ProductTable
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Goal: "es2020"
        EntryPoints: 
        - src/features/getAll.ts

  GetProductByIdFunction:
    Sort: AWS::Serverless::Operate
    Properties:
      Handler: src/features/getById.handler
      Occasions:
        CreateProduct:
          Sort: Api
          Properties:
            Path: /merchandise/{id}
            Methodology: get
      Insurance policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref ProductTable
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Goal: "es2020"
        EntryPoints: 
        - src/features/getById.ts

  ProductTable:
    Sort: AWS::DynamoDB::Desk
    Properties:
      TableName: !Sub ${AWS::StackName}-products
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      BillingMode: PAY_PER_REQUEST

Enter fullscreen mode

Exit fullscreen mode

Despliegue

Eso sería todo en cuanto a las plantillas y el código para realizar operaciones CRUD. Dentro de la carpeta donde esta la plantilla template.yaml ejecutar:

sam construct
Enter fullscreen mode

Exit fullscreen mode

Seguidamente ejecutar

sam deploy --guided
Enter fullscreen mode

Exit fullscreen mode

A continuación realiza algunas preguntas, se recomienda configurar de la siguiente manera:

sam deploy

Luego confirmar el despliegue y esperar:

Despliegue

Es possible que en home windows la consola se queda congelada, revisar en la consola aws directamente.

Se revisa en la consola de cloudformation de AWS:

Stack desplegado

Los recursos han sido creados:

Recursos creados

En el recurso de apigateway se puede ver la implementación para poder consumirlas.

Api gateway

Apigateway

Probando apis
Consultando productos (se creo uno previamente)

Prueba 1

Se crea un producto

Prueba 2

Producto por ID.

Prueba 3

Despedida

Eso sería todo, puedes ir probando todas las demas funciones, de esta manera es sencillo desplegar apis con AWS. Si alguna duda no dudes en comentarlo.



Referencias

Add a Comment

Your email address will not be published. Required fields are marked *

Want to Contribute to us or want to have 15k+ Audience read your Article ? Or Just want to make a strong Backlink?