AWS NodeJS App 06 – Carrinho de compras com Time To Live do DynamoDB – Parte 1

O objetivo deste tutorial é implementar a funcionalidade de carrinho de compras, para isso vamos armazenar temporariamente objetos em nosso DynamoDB. E para isso vamos utilizar no ID da sessão HTTP como chave para identificar nossos usuários. Para isso precisamos instalar duas bibliotecas em nosso projeto.

npm install express-session --save
npm install cookie-parser --save

Para que estas bibliotecas funcionem precisamos modificar nosso arquivo app.js para importar e coloca-las no pipeline de processamento do express.

//muito código antes disso

var cookieParser = require('cookie-parser');
var session = require('express-session');

//mais código ainda

app.use(cookieParser());
app.use(session({
    secret: 'wV95QY67vRxWtDOQJO0194NUJdl5zYam', // just a long random string
    resave: false,
    saveUninitialized: true
}));

Em seguida na pasta models vamos criar uma nova classe chamada ShoppingCar para representar o carrinho de compras.

var {
  DataMapper, DynamoDbSchema, DynamoDbTable
} = require('@aws/dynamodb-data-mapper');
class ShoppingCar{
    
}
Object.defineProperties(ShoppingCar.prototype, {
    [DynamoDbTable]: {
        value: 'shoppingcar'
        
    },
    [DynamoDbSchema]: {
        value: {
            id: {
                type: 'String',
                keyType: 'HASH'
            },
            expiration: {type: 'Number'},
            itens: {
                type: 'Collection'
            }
        },
    },
});
module.exports = ShoppingCar;

Agora vamos ao DynamoDB criar nossa nova tabela chamada shoppingcar, e vamos definir a propriedade expiration como nos Time to live attribute. Assim os documentos desta tabela serão automaticamente excluídos após um período de tempo.

Para saber mais sobre o TTL veja este site: https://docs.aws.amazon.com/pt_br/amazondynamodb/latest/developerguide/howitworks-ttl.html

Em seguida vamos retornar ao nosso projeto e criar uma nova classe de serviço chamada ShoppingCarService, nesta classe vamos implementar dois métodos um para salvar um novo carrinho de compras e outro para recuperar o carrinho de compras pela propriedade id.

var {
  DataMapper, DynamoDbSchema, DynamoDbTable
} = require('@aws/dynamodb-data-mapper');
var ConditionExpression = require('@aws/dynamodb-expressions');
var {contains} = require('@aws/dynamodb-expressions');
var DynamoDB = require('aws-sdk/clients/dynamodb');

const client = new DynamoDB({region: 'us-east-2'});
const mapper = new DataMapper({client});
const Product = require("../models/product");
const ShoppingCar = require("../models/shoppingcar");

class ShoppingCarService{
    async save(shoppingCar){
        if(shoppingCar.id == null || shoppingCar.id === ""){
            throw Error("Todo carrinho de compras deve ter um ID");
        }
        mapper.put({item: shoppingCar}).then(() => {
            return shoppingCar;
        });
    }
    
    async getById(idshoppingcar){
        var resultado = mapper.get(Object.assign(new ShoppingCar(), {id: idshoppingcar}))
        .catch(err => {
            return null;
        });
        return resultado;
    }
}
module.exports = ShoppingCarService;

Podemos agora modificar nossa view index2.hbs do controlador index para que o botão Buy now chame o novo controlador shoppingcar na action add product passando o ID do produto selecionado.

<a href="/shoppingcar/addproduct?productid={{this.id}}" 
					class="btn btn-primary"> Buy now </a>

O próximo passo será criar o ShoppingCarController, nesta classe vamos criar o código da action addProduct que irá recuperar o id do produto selecionado, o identificador único da sessão. Em seguida nosso código chama a classe de serviço para verificar se encontra no DynamoDB algum objeto com o id igual ao id da sessão do usuário, caso não encontre ele cria um novo objeto contendo o id da sessão, o valor de expiração do documento e uma array de itens. Em seguida ele insere um novo objeto na lista de itens contendo o ID do produto selecionado e a quantidade inicial de 1 item.

var ProductService = require("../services/productservice");
var productService = new ProductService();
var Product = require("../models/product");

var ShoppingCarService = require("../services/shoppingcarservice");
var shoppingCarService = new ShoppingCarService();
var ShoppingCar = require("../models/shoppingcar");


class ShoppingCarController{
    async addProduct(req,res,next){
        var idproduct = req.query.productid; 
        var sessionid = req.sessionID; 
        
        await shoppingCarService.getById(sessionid).then((shoppingcar)=>{
            if(shoppingcar == null){
                shoppingcar = new ShoppingCar();
                shoppingcar.id = sessionid;
                shoppingcar.expiration = Math.floor( Date.now() / 1000 ) + 60;
                shoppingcar.itens = new Array();
            }
            //console.log(JSON.stringify(shoppingcar, null, 10));
            shoppingcar.itens.push({id:idproduct,quantity:1});
            shoppingCarService.save(shoppingcar);
        });

        
        res.redirect("/");
    }
}
module.exports = ShoppingCarController;    

Agora nos devemos registrar nosso controlador, para isso vamos criar um novo arquivo na pasta de routes chamado shoppingcar.js criando a rota /addproduct.

var express = require('express');
var router = express.Router();

var ShoppingCarController = require("../controller/shoppingcarcontroller");
var shoppingCarController = new ShoppingCarController();

router.get('/addproduct', function(req, res, next) {
    shoppingCarController.addProduct(req,res,next);
});

module.exports = router;

E agora vamos novamente alterar nosso arquivo app.js para incluir nosso novo arquivo de rotas para o carrinho de compras.

//muito código antes disso

var shoppingcarRouter = require('./routes/shoppingcar');

//várias e várias linhas de código

app.use('/shoppingcar', shoppingcarRouter);

Agora execute a aplicação e clique no botão Buy now, em qualquer um dos produtos. Ao acessar o DynamoDB observe que um novo documento será inserido, contendo como chave o id da sessão do usuário e o id do produto.

Por fim podemos alterar nossa tela inicial para apresentar o número de itens que estão no carrinho de compras. Para isso vamos modificar o código do nosso IndexController para incluir o código necessário para carregar o serviço do carrinho de compras, e na action index recuperar o carrinho de compras do usuário e calcular o número de itens que ele já adicionou e então enviar essa informação para a view.

var ShoppingCarService = require("../services/shoppingcarservice");
var shoppingCarService = new ShoppingCarService();
var ShoppingCar = require("../models/shoppingcar");

class IndexController{
    async index(req,res,next){
        var listProduct  = await productService.getAll();
        var numitens = 0;
        await shoppingCarService.getById(req.sessionID).then((shoppingcar)=>{
            if(shoppingcar != null)
                numitens = shoppingcar.itens.length;
        });
        //console.log(JSON.stringify(listProduct, null, 4));
        res.render('index/index2', { listProduct: listProduct, numitensshoppingcar:numitens });
    }

Para que o número de itens seja apresentado em tela, vamos substituir a informação do telefone de contato no header da tela pelo número de itens que está no carrinho de compras.

<div class="text-wrap">
    Carrinho de compras {{numitensshoppingcar}} itens
</div>

Agora ao acessar a aplicação o número de itens no carrinho de compras será apresentado no topo da tela. Aguarde alguns minutos e ao atualizar a tela, o carrinho de compras aparecerá zerado pois o documento foi removido automaticamente pelo DynamoDB.