@seniorsistemas/fsw-aws-lambda

Biblioteca de utilitários para construção de lambdas no ambiente SKD G7 para interceptação de primitivas.

Usage no npm install needed!

<script type="module">
  import seniorsistemasFswAwsLambda from 'https://cdn.skypack.dev/@seniorsistemas/fsw-aws-lambda';
</script>

README

Fábrica de Software Senior - Aws Lambda Lib

Biblioteca da Fábrica de Software Senior para padronização e auxílio na construção de constomizações utilizando lambdas no ambiente SDK G7 para interceptação de primitivas.


Instalação

A partir do root de um projeto NodeJs, instalar a dependência:

npm install --save @seniorsistemas/fsw-aws-lambda

Feito isso, os módulos já estarão disponíveis para utilização:

require('@seniorsistemas/fsw-aws-lambda');

Dependências opcionais

São dependências requeridas por alguns módulos da lib (o módulo core não requer dependências).

npm install --save axios moment

Principais módulos

A biblioteca é distribuída como módulos no formato CommonJS por compatibilidade nativa com Node v10.16.3 (versão atual suportada pela IDE AWS Cloud9). Os principais módulos são:

  • @seniorsistemas/fsw-aws-lambda: Módulo principal, provê funções para manipular o evento nativo da Lambda AWS, configuração da resposta da lambda e gerencia a execução de regras;
  • @seniorsistemas/fsw-aws-lambda/utils: Utilitários;
  • @seniorsistemas/fsw-aws-lambda/services: Services com lógicas em comum entre projetos.

Utilização

A utilização da biblioteca visa padronizar o tratamento de eventos recebidos e resposta retornada pela função Lambda e a execução de regras de execução. A documentação também pode ser consultada na respectiva documentação de código em cada função e classe.

Com a integração da biblioteca, o index.js da função lambda deve-se assimilar com o modelo abaixo:

index.js

// Importação de funções e classe para execução de validações.
const {
    lambdaEvent,
    lambdaResponse,
    AsyncRuleValidator
} = require('@seniorsistemas/fsw-aws-lambda');

// Função principal da lambda, sempre deve ser 'async' ou retornar 
// uma Promise com o payload de resposta.
exports.handler = async event => {

    // Retorna o payload da requisição original do evento.
    // Sempre será um objeto JSON.
    const body = lambdaEvent.parseBody(event);

    // Lê e extrai informações do evento original da função lambda e
    // define as configurações de ambiente, como o ambiente de execução
    // (development, homologx, production) e variáveis de ambiente, como
    // a URL da plataforma e o token para autenticação.
    const eventInfo = lambdaEvent.createEventInfo(event);

    // Criação do executor das regras de validação, recebe como parâmetro
    // as informações extraídas do evento.
    return new AsyncRuleValidator(body, eventInfo)
        .validate([ // Adiciona as regras de validação a serem executadas.
            'rf01',
            'rf02',
            /* outras regras */
        ])

        // Após todas as regras serem executadas, avalia se há erros de
        // validação e retorna a resposta correspondente, sendo elas:
        // Erro de cliente (código 400) caso houveram erros de validação,
        // sucesso (código 200) caso contrário.
        .then(validationResult => {
            if (validationResult.hasErrors()) {
                return lambdaResponse
                .validationError(validationResult.getErrorsAsString());
            }

            return lambdaResponse.success(body);
        })

        // Em caso de alguma exceção ou erro inesperado, retorna erro
        // interno (código 417).
        .catch(lambdaResponse.internalError);
};

Customizando o ambiente de execução do evento

Ao executar o código:

const eventInfo = lambdaEvent.createEventInfo(event);

Já temos em mãos as variáveis de ambiente necessária para a execução das regras de validação. Entretanto, pode ser necessária a adição de mais variáveis ou a definição de valores padrão para as mesmas.

Os valores padrão podem ser informados como segundo parâmetro da função createEventInfo, tendo como definição:

interface EnvironmentData {
    basePlatformUrl: string;
    bearerToken: string;
}

Por exemplo:

const eventInfo = lambdaEvent.createEventInfo(event, {
    basePlatformUrl: 'https://platform.senior.com.br/t/senior.com.br/bridge/1.0/rest',
    bearerToken: 'Bearer 15c2a9eca2e0f8faac36de609c7d05ca'
});

Caso não forem informados os valores padrão de variáveis de ambiente, serão utilizados os declarados nas constantes environments.js conforme o ambiente identificado a partir dos dados do evento, no atributo event.environment.

ARQPDT-1923: O usuário liberado pelo SDK (ainda) não possui acesso às configurações de variáveis de ambiente, conforme documentação oficial da AWS. Portanto, faz-se necessário outro meio para definir as variáveis de ambiente.

Criando evento de execução com autenticação da lambda (Autenticação de aplicação externa)

Em alguns momentos é necessário realizar chamadas HTTP para a plataforma com um token que tenha permissões de administrador e não utilizar o token do usuário recebido no evento original. Através desse recurso é possível criar o evento de execução para extrair as informações do evento original e autenticar a lambda na plataforma através da chaves de aplicações geradas na plataforma.

Ao optar por utilizar essa forma de criação do evento, é necessário substituir a implementação utilizada no item "Customizando o ambiente de execução do evento" dessa documentação.

O metodo recebe como parâmetro o evento original, a chave de acesso e a senha de acesso (Essa informações serão geradas através da plataforma).

const eventInfo = await lambdaEvent.createEventInfoWithAuthentication(event, 'PwBbpdyzKSEGrZmfeEiCkrb3awEa', 'mXVJMIs2o7pTsVjMUpaWyVVKlFUa');

Ao executar esse trecho de código será criada uma nova variável no eventInfo chamada "lambdaToken". Através dessa variável é possível obter um token válido para chamadas para a plataforma. Essa informação pode ser acessada da seguinte forma:

const eventInfo = await lambdaEvent.createEventInfoWithAuthentication(event, 'PwBbpdyzKSEGrZmfeEiCkrb3awEa', 'mXVJMIs2o7pTsVjMUpaWyVVKlFUa');
let lambdaToken = eventInfo.lambdaToken;

AsyncRuleValidator

Como o próprio nome sugere, é um validador de regras assíncrono (tudo é ou vira uma Promise).

As regras de validação são apenas funções, não precisam, necessariamente, serem módulos externos, apenas precisam obedecer à seguinte interface:

function (body, event) {
    return 'Mensagem de erro';
}
// Ou
function (body, event) {
    return ['Uma mensagem de erro', 'Outra mensagem de erro'];
}
// Ou
async function (body, event) {
    return 'Mensagem de erro';
}

Onde o parâmetro body é o payload da requisição convertido no formato objeto JSON e event é o objeto contendo os dados de evento original da função lambda. Cada regra de validação pode ser síncrona ou assíncrona, entretanto, a execução sempre será assíncrona e podem retornar um, nenhum, ou uma lista de erros de validação.

Possui dois métodos principais para validação:

Ambos os métodos aceitam dois formatos de parâmetros:

  1. O nome da regra. Nesse caso, a regra será carregada de um arquivo no diretório fixo src/rules a partir do root do projeto. Por exeplo: 'rf01' será requerido do arquivo '/src/rules/rf01.js'. O arquivo da regra deve obedecer arquitetura de módulos, exportando a função para validação: module.exports = async (body, event) => {}.

  2. A função para execução das regras, podendo conter nenhum, um ou dois parâmetros e retornar uma mensagem, uma lista de mensagens ou uma Promise.

AsyncRuleValidator.validate()

Executa uma série de regras de validação de forma assíncrona, onde todas as regras serão executadas ao mesmo tempo e o resultado será apresentado apenas quando todas as regras terminarem sua execução.

Exemplo:

asyncRuleValidator
    .validate([
        'rf01',
        function (body) {  return 'Mensagem de erro'  },
        async (body, event) => ['Mensagem de erro'] ,
        () => 'Mensagem de erro'
    ]).then(/* ... */);

AsyncRuleValidator.validateOneByOne()

Executa uma série de regras de validação, executando uma regra por vez. Cada regra de validação pode ser síncrona ou assíncrona, entretanto, a execução sempre terá comportamento síncrono (a execução de uma regra trava a execução das seguintes). A primeira regra que retornar uma mensagem de validação excerrará a execução da cadeia de regras.

Exemplo:

asyncRuleValidator
    .validateOneByOne([
        'rf01',
        function (body) {  return 'Mensagem de erro'  },
        async (body, event) => ['Mensagem de erro'] ,
        () => 'Mensagem de erro'
    ]).then(/* ... */);

Tratando o retorno das regras de validação

Após a execução bem sucedida das regras de validação, será invocado o método .then() da Promise retornada pelos métodos de validação. Este método receberá como argumento uma instância da classe ValidationResult a qual tem a responsabilidade de armazenar todas as mensagens retornadas por regras de validação e possui métodos auxiliares para posterior tratamento das mensagens.

Principais métodos da classe ValidationResult:

  • .hasErros(): avalia se houveram erros de validação;
  • .getErrors(): retorna um objeto contendo todas as mensagens de erro de validação, se houver.
  • .getErrorsAsString() retorna uma string concatenando todas as mensagens de erro de validação separadas pelo caractere quebra de linha \n.

Caso alguma regra falhar na execução e lençar uma exceção, será invocado o método .catch() da Promise contendo informações sobre qual regra falhou e o detalhe da exceção.

Padrão de retorno da função lambda

Os retornos da função lambda customizada são criados pelas funções do módulo lambda-response, tanto para sucesso, falha em regras de validação e erros inesperados.

O padrão de retorno sempre será:

{
    statusCode: 200 | 400 | 417
    headers: {
        'Content-Type': 'application/json' | 'text/plain',
        'X-Senior-FSW': 'Customizacao'
    },
    body: {} | ''
}

Importante: Em todos os cenários, o header X-Senior-FSW é retornado na resposta com o valor Customizacao, identificando que esse retorno foi interceptado e, possivelmente, modificado pela equipe de Customização da Fábrica de Software Senior.

Os códigos padrão de status HTTP retornados são:

  • 200: Sucesso;
  • 400: Erro de validação retornado por regras;
  • 417: Erro interno, durante a execução de regras ou outro erro inesperado.

O Content-Type pode variar dependendo do parâmetro informado para o body, caso o body seja um objeto JSON, o Content-Type será application/json, caso o body seja um texto, o Content-Type será text/plain. Quando uma resposta for gerada por exceções ou erros inesperados, o corpo da resposta sempre terá o prefixo [FSW-ERROR].

Funções para construção da resposta

As funções disponíveis para criação do payload de resposta da função lambda são os seguintes:

const { lambdaResponse } = require('@seniorsistemas/fsw-aws-lambda');

const body = {}; // Também pode ser string.

lambdaResponse.success(body);

lambdaResponse.validationError(body);

lambdaResponse.internalError(body);

const httpStatus = 404;
lambdaResponse.response(httpStatus, body);

Validando existência de propriedades em objetos

Por muitas vezes é necessário validar se determinada propriedade/atritbuo de um objeto existe e se o valor dele é diferente de vazio.

Existem duas formas de realizar esse tratamento:


const { lambdaUtils } = require('@seniorsistemas/fsw-aws-lambda');

//Objeto de exemplo
const obj = {
        name: 'Fulano de tal',
        nickname: '',
        city: 'Blumenau',
        infos: {
            cel: '47999999999'
        },
        emptyObj: {
            name: ''
        }
};

//forma nativa - Valida se a propriedade existe e tem valor. nesse caso a propriedade nickname existe mais não tem valor, então a comapração retoraná false

if(!!obj.nickname) {
    //implementation
}

// Via utiliário - Realiza a mesma validação. Se a propriedade existe e tem valor
if(lambdaUtils.isPresent(obj.nickname)){
    // implementation
}


Requisições para a plataforma

A biblioteca possui um utilitário para facilitar a realização de requisições para a plataforma SeniorX, funciona como um wrapper para as funções do axios. Tem por objetivo:

  • Autenticação com a plataforma, adiciona o token no cabeçalho da requisição;
  • Tratamento da resposta com sucesso;
  • Tratamento da resposta com erro. Realiza logs e adiciona informações sobre a URL e o erro ocorrido (não suprime a exceção, a mesma será propagada).

OBS.: Requer dependência com axios@0.19.0 (ou posterior).

Exemplo:

const { axiosW } = require('@seniorsistemas/fsw-aws-lambda/utils');

const eventInfo = {}; // O mesmo evento recebido pelas regras de validação.

axiosW.platformGet(eventInfo, '/uri');
//    .platformHead(eventInfo, uri, config)
//    .platformDelete(eventInfo, uri, config)
//    .platformPut(eventInfo, uri, data, config)
//    .platformPost(eventInfo, uri, data, config)
//    .platformPatch(eventInfo, uri, data, config)
//    .get(url, config)
//    .head(url, config)
//    .delete(url, config)
//    .put(url, data, config)
//    .post(url, data, config)
//    .patch(url, data, config)

Requisições para a plataforma via utilitário (PlatformApi)

Esse novo utilitário PlatformApi facilita as chamadas a outras primtivas da Plataforma de uma maneira mais simples.

Lembrando que o método do tópico acima ainda continua funcional.

Esse novo método é apenas uma absstração que utiliza por baixo as chamadas utilizando o utiliátios axiosW.

    const {
        lambdaEvent,
        PlatformApi
    } = require('@seniorsistemas/fsw-aws-lambda');

   /*Evento vindo da requisição*/ 
    const eventInfo = lambdaEvent.createEventInfo(event);

    const userData = await PlatformApi.Get(eventInfo,'/usuarios/userManager/queries/obterMeusDados');

    /*O utilitário possui os seguintes métodos:
    
    PlatformApi.Get(event,primitive,params)
    PlatformApi.Delete (event,primitive,params) 
    PlatformApi.Head  (event,primitive,params)
    PlatformApi.Post (event, primitive, data, params)
    PlatformApi.Put (event, primitive, data, params)
    PlatformApi.Patch = (event, primitive, data, params) 
        
    */

Além das chamadas para a plataforma utilizando o token do evento original (usuário logado na plataforma), é possível realizar chamadas utilizando o token obtido na lambda através de uma autenticação de aplicação externa. Para mais informações, verificar o item "Criando evento de execução com autenticação da lambda (Autenticação de aplicação externa)" dessa documentação.

    const {
        lambdaEvent,
        PlatformApi
    } = require('@seniorsistemas/fsw-aws-lambda');

   /*Evento vindo da requisição*/ 
    const eventInfo = await lambdaEvent.createEventInfoWithAuthentication(event, 'PwBbpdyzKSEGrZmfeEiCkrb3awEa', 'mXVJMIs2o7pTsVjMUpaWyVVKlFUa');

    const userData = await PlatformApi.GetWithLambdaToken(eventInfo,'/usuarios/userManager/queries/obterMeusDados');
    
    /*O utilitário possui os seguintes métodos:
    PlatformApi.GetWithLambdaToken(event,primitive,params)
    PlatformApi.DeleteWithLambdaToken (event,primitive,params) 
    PlatformApi.HeadWithLambdaToken  (event,primitive,params)
    PlatformApi.PostWithLambdaToken (event, primitive, data, params)
    PlatformApi.PutWithLambdaToken (event, primitive, data, params)
    PlatformApi.PatchWithLambdaToken = (event, primitive, data, params) 
    */

Consumindo serviços da G5

A biblioteca possui um utilitário para auxiliar nas chamadas aos Webservices SOAP da G5. Esse utilitário realiza a chamada no SXIAPI que é uma biblioteca que possibilita a chamada dos WS SOAP G5, enviando um corpo em JSON e recebendo o corpo também em JSON. Visto que os webservices SOAP da G5 trabalham com dados no formato XML.

Para maiores informações sobre o SXIAPI verificar o link a seguir presente na página dev.senior.com.br: SXIAPI

OBS.: Chamadas a G5 devem ser evitadas a todo custo quando estamos em um contexto de execução dentro de customizações na G7, visto que é uma chamada externa que aumenta consideravelmente o tempo de execução da função lambda, aumentando assim o tempo de respota ao usuário. Esse recurso deve ser utilizado apenas se não existir outra solução.

Exemplo:

const {
    lambdaEvent,
    G5Api
} = require('@seniorsistemas/fsw-aws-lambda');


 /*Evento vindo da requisição*/ 
    const eventInfo = lambdaEvent.createEventInfo(event);

    /*Instância classe de utiliários da chamada as APIS da G5
    Parâmetros:
        - sxiServer: Endereço onde se encontra deployado o utilitário SXI para comunicação com a G5
        - sxiContext:  Contexto que o utilitário SXI responde dentro do servidor Glassfish da G5
        - g5Server: Endereço onde os Webservices SOAP da G5 estão implantados
        - user: Nome do Usuário G5 para execução dos webservices
        - token: Token da plataforma referente ao usuário logado, ou Senha do usuário da G5 informado
    */     
    const g5 = new g5Api('http://desktop-lb7k1kk:8000','API','http://desktop-lb7k1kk:8000','senior', eventInfo.platformToken);

    /* Realizada a chamada do WS da G5 através do método callService passando como parâmetro:
       - g5Module: Módulo da G5 referente ao serviço a ser executado
       - service: Nome completo do webservice da G5
       - port: Nome da Porta do webservice da G5
       - body: Objeto contendo o corpo da requisição (parâmetros de entrada no foramto json)
       - dataSourceName: Parâmetro opcional, quando passado altera o nome do objeto principal retornado
    */ 

   const bodyCol = {
        numEmp: 1,
        abrTipCol: "1",
        iniPer: "01/01/2000",
        fimPer: "31/01/2001"
   };

    const response = await g5.callService("rubi","com.senior.g5.rh.fp.colaboradoresAdmitidos","ColaboradoresAdmitidos",bodyCol);

Utilitário para envio de E-mail

Esse utilitário é um facilitador para realizar envio de e-mail através da primitiva de notifação da Pltaforma Senior X: /platform/notifications/actions/notifyUser

O utiliário E-mail Service possui dos métodos para realizar o envio:

sendEmail Realiza o envio de um email simples, conteúdo em texto Plano, pramâmetros:

  • Evento: Enviar o eventIndo que contém os metadados da requisição
  • Destinatário: Lista de destinatários, pode ser passado mais de um e-mail separando com " , - virgula"
  • Assunto: Assunto do E-mail
  • Contéudo: Corpo do E-mail em texto Plano

sendEmailHTML Realiza o envio de um email com conteúdo formatado em HTML, pramâmetros:

  • Evento: Enviar o eventIndo que contém os metadados da requisição
  • Destinatário: Lista de destinatários, pode ser passado mais de um e-mail separando com " , - virgula"
  • Assunto: Assunto do E-mail
  • Contéudo: Corpo do E-mail em HTML

Ambos metódos retornam um objeto de responto se sucesso o retorno será: { ok: true }


const { lambdaEvent } = require('@seniorsistemas/fsw-aws-lambda');
const { emailService } = require('@seniorsistemas/fsw-aws-lambda/services/email-service');


/*Evento vindo da requisição*/ 
const eventInfo = lambdaEvent.createEventInfo(event);

const responseEmail = await emailService.sendEmail(eventInfo,'diego.cassandri@senior.com.br','Teste Via Customização','Conteúdo');

console.log(responseEmail);

const responseEmailHTML = await emailService.sendEmailHTML(eventInfo,'diego.cassandri@senior.com.br,diego.cassandri@gmail.com','Teste Via Customização HTML','<h1>Conteúdo</h1>');

console.log(responseEmailHTML);

Testando as regras dentro do Cloud9

Após criada, a função lambda pode ser executada e depurada diretamente da IDE AWS Cloud9. Para isso, é necessário abrir o menu de execução da lambda:

  1. No menu lateral direito, clicar na aba "AWS Resources" para listar as lambdas criadas. Escolha a lambda desejada para execução e clique com o botão direito, selecione a opção "Run" e, então, "Run Local". Menu de execução AWS Resources, indicando onde acessar o submenu "Run Local"

  2. A aba que será aberta, é a aba de configuração de execução da função lambda. Para executar a função basta clicar no botão "Run" e para executar em modo debug, basta ativar o botão "Run in debug mode" e, então, clicar no botão "Run". Note que no payload há um atributo "environment": "development", este atributo é customizado e não existirá em eventos reais quando a lambda for invocada, ele é utilizado para indicar à biblioteca configurar o ambiente de execução com as variáveis de desenvolvimento. Todo o conteúdo do campo payload será o valor da variável event na execução da lambda. Executar função lambda com payload customizado

  3. Durante a execução em modo de depuração, no topo da janela de execução é possível controlar os passos de avanço da depuração, acompanhar valor de variáveis e a stack de execução. Clicando na aba "Immediate" na parte inferior da janela, será aberto um console para depuração onde é possível executar um código dentro do contexto do breakpoint. Controle de breakpoint e console de depuração

Contribuindo com o projeto

Para ver como pode contribuir com o projeto, leia as instruções de contribuição.

Licença

Senior Sistemas SA © Ver licença em LICENSE.

Autores: Luiz Nazari <luiz.nazari@senior.com.br> Diego Cassandri <diego.cassandri@senior.com.br> Felipe Gonçalves <felipe.goncalves@senior.com.br>