Serverless Backend
Precisa de backend e não quer perder tempo e dinheiro com infra-estrutura?
Agora seu Mini App já pode contar com nossa solução de Backend Serverless.
Na raiz do seu projeto de Mini App execute:
mkdir src/server
Pronto! Seu Mini App agora tem um backend.
Quer mais? Sigamos!
Pré-requisitos
Para ter acesso aos recursos de serverless, você precisará:
da versão 1.10.0 (ou superior) do
ame-app-tools
;da versão 2.17.0 (ou superior) do
ame-super-app-client
.
Overview
Nossa solução de Backend Serverless permitirá que você integre seu Backend Serverless diretamente ao seu Mini App da forma mais rápida e simples possivel.
Seu Mini App pode ser composto de várias funções serverless implementadas por meio de classes. Por exemplo, para executar a funcao checkout
da classe CheckoutBackend
, precisamos:
- Criar o módulo na pasta
src/server
// src/server/CheckoutBackend.js
export default class CheckoutBackend {
async checkout(req) {
const { param1, param2 } = req.body
// regra de negocio
}
}
- Chamar a função no codigo fonte do Mini App:
const result = Ame.server.exec('CheckoutBackend.checkout', {
param1: valueForParam1,
param2: valueForParam2
})
Essa funcionalidade agrega várias vantagens ao desenvolvimento de Mini Apps, dentre as quais destacamos:
- Economia com contratação de infraestrutura
- Fácil implementação
- Fácil integração com frontend
- Seu código-fonte de backend não é exibido como o código-fonte do frontend
- Utilização de variaveis de ambiente no backend para ocultar dados que estejam hardcoded no frontend
- Usuário autenticado no objeto
req.user
Configuração
Como mencionado anteriormente, para habilitar o Backend Serverless para seu Mini App, basta criar o diretório
src/server
em seu projeto de Mini App.
Nele você poderá adicionar seu módulo serverless. Esse módulo pode ter tantas funções quanto forem necessárias. Com exceção do módulo Notification.js, é permitido apenas um módulo severless.
Uma vez definido seu módulo no diretódio server
, ao executar o comando ame-app-tools start
, a plataforma se encarregará de criar sua função serverless na nuvem.
Comandos
env
Adiciona uma ou mais variáveis de ambiente ao seu backend.
Pré-requisitos
- Deve existir um módulo criado em
src/server
; ame-app-tools start
deve ter sido executado pelo menos uma vez antes de prosseguir com a criação de sua variável de ambiente. Ostart
é responsável por realizar o deploy de seu serverless. Só nesse estado seu serverless será capaz de armazenar suas variaveis de ambiente. Fique atento à versão de seu Mini App no arquivo ame.conf.js, pois quando alterada, será necessário re-executarame-app-tools start
a fim de refazer o deploy de seu serverless.
Parâmetros disponíveis
--prod
Cria/atualiza variaveis de ambiente no ambiente de produção.Exemplo:
ame-app-tools env EXTERNAL_PI_URL=https://external.api/base/path --prod
Variaveis criadas com
--prod
são acessíveis apenas pelo Mini App publicado.--read
Lista todas as variaveis de ambiente criadas.Exemplo:
ame-app-tools env --read
--verbose, -v
Exibe logs detalhados do comandoenv
.Exemplo:
ame-app-tools env EXTERNAL_API_URL=https://external.api/base/path --verbose
--help, -h
Exibe ajuda sobre o comandoenv
Exemplo:
ame-app-tools env -h
Para acessar essas variáveis no código-fonte de seu Backend Serverless, basta:
const externalApiUrl = process.env.EXTERNAL_API_URL;
Estrutura de Arquivos
Sua estrutura de arquivos deve ficar semelhante a essa:
.
├── ame.conf.js
└── src
├── components
├── server
│ ├── CheckoutService.js
└── views
Notificação de Confirmação de Pagamento
Após o seu cliente final realizar o pagamento, você pode precisar realizar alguma operação de backoffice. Seu Backend Serverless é capaz de processar a notificação de confirmação desse pagamento bastando implementar um simples módulo: Notification.js.
Não é necessário fornecer url de callback nem qualquer outra configuração adicional, apenas implementar o Notificaton.js
e sua função handler
.
Esse módulo também deve pertencer ao diretório src/server
, conforme ilustrado a seguir:
.
├── ame.conf.js
└── src
├── components
├── server
│ ├── CheckoutService.js
│ ├── Notification.js
└── views
Exemplos:
Implementação de notificação para tratar dados do pagamento devolvidos no
customPayload
const TAG = __filename.split('/').pop(); export default class Notification { async handler(req) { console.debug(TAG, 'Recebendo notificação'); const amePaymentObject = req.body; const customPayload = payment.attributes.customPayload; // Objeto fornecido na execução do Ame.startPayment(). const myData = customPayload.myData; console.debug("Meus dados devolvidos: ", JSON.stringify(myData)); // Return é opcional. O serverless loga o retorno para fins de debugging. } }
Implementação de notificação para repassar a notificação à outras APIs
const Axios = require('axios'); const TAG = __filename.split('/').pop(); export default class Notification { async handler(req) { console.debug(TAG, 'Recebendo notificação'); // Adicionamos EXTERNAL_API_URL através do comando: ame-app-tools env const externalApiUrl = process.env.EXTERNAL_API_URL; const response = await Axios.post(externalApiUrl, req.body); console.debug("Notificação enviada à API externa: ", response.status); // Return é opcional. O serverless loga o retorno para fins de debugging. } }
Para disparar notificações de captura automática de pagamento bem sucedidas, basta ao chamar a função Ame.startPayment(paymentOrder) incluir um atributo no paymentOrder
chamado serverNotification=true
.
Operações de pagamento via serverless
As operações de pagamento devem ser realizadas em um módulo serverless, não estando disponíveis para módulos não inclusos nessa categoria.
As operações de pagamento disponíveis são:
- Cancelamento
- Captura
- Estorno
Todas essas operações dependem da criação de uma ordem de pagamento, realizada através do método StartPayment.
Já no serverless, as operações de pagamento são invocadas pelo objeto Payment
, que tem escopo global e não precisa ser importado para seu módulo cliente.
Cancelamento
Requerimentos: Precisa haver uma ordem de pagamento criada e a mesma não pode ter sido capturada.
Dado que seu miniapp possui uma função serverless chamada ServerlessBack
, para invocar uma operação de cancelamento, crie nesse módulo uma função serverless doCancel
(Pode ser qual nome preferir) e dentro dela chame Payment.cancel()
, passando como argumento a requisição req
.
Como apresentado abaixo:
export default class ServerlessBack {
async doCancel(req) {
const response = await Payment.cancel(req);
return response
}
}
Onde req
possui um objeto body
contendo as seguintes propriedades:
- accessToken (adquirido através do askUserData)
- publicKey (do miniapp onde foi feito o pagamento)
- paymentId (id do pagamento a ser cancelado)
Os parâmetros mencionados acima, são fornecidos à função serverless através da chamada da função exec, como demonstrado a seguir:
await Ame.server.exec("ServerlessBack.doCancel", {
accessToken,
publicKey,
paymentId
});
Captura de pagamento
Requerimentos: Precisa haver uma ordem de pagamento criada.
Dado que seu miniapp possui uma função serverless chamada ServerlessBack, para invocar uma operação de captura, crie nesse módulo uma função serverless doCapture
(Pode ser qual nome preferir) e dentro dela chame Payment.capture()
, passando como argumento a requisição req
.
Como apresentado abaixo:
export default class ServerlessBack {
async doCapture(req) {
const response = await Payment.capture(req);
return response
}
}
Onde req
possui um objeto body
contendo as seguintes propriedades:
- accessToken (adquirido através do askUserData)
- publicKey (do miniapp onde foi feito o pagamento)
- paymentId (id do pagamento a ser cancelado)
Os parâmetros mencionados acima, são fornecidos à função serverless através da chamada da função exec, como demonstrado a seguir:
await Ame.server.exec("ServerlessBack.doCapture", {
accessToken,
publicKey,
paymentId
});
Estorno
Requerimentos: Precisa haver uma ordem de pagamento capturada.
Dado que seu miniapp possui uma função serverless chamada ServerlessBack, para invocar uma operação de estorno, crie nesse módulo uma função serverless doRefund
(Pode ser qual nome preferir) e dentro dela chame Payment.refund()
, passando como argumento a requisição req
.
Como apresentado abaixo:
export default class ServerlessBack {
async doRefund(req) {
const response = await Payment.refund(req);
return response
}
}
Onde req
possui um objeto body
contendo as seguintes propriedades:
- accessToken (adquirido através do askUserData)
- paymentId (id do pagamento a ser estornado)
- amount (valor a ser estornado)
- origin (Id que deve ser gerado pelo sistema parceiro para identificar a transação de estorno, deve ser criado seguindo a lógica de UUID. Esse ID deve ser único para cada requisição de estorno criada. Sugerimos que a criação do UUID siga o padrão: silgadoecommerce-uuid)
- walletToken (carteira que receberá o estorno)
Os parâmetros mencionados acima, são fornecidos à função serverless através da chamada da função exec, como demonstrado a seguir:
await Ame.server.exec("ServerlessBack.doRefund", {
accessToken,
walletToken,
paymentId,
amount,
origin,
});
Mãos à obra...
Exemplo de implementação de um Hello World com backend sem notificação
Através do terminal de seu sistema operacional execute:
$ cd ~/
$ ame-app-tools create hello-world-serverless && cd hello-world-serverless
? Qual o nome do seu mini app? hello-world-serverless
? Qual o título do seu mini app? Hello World Serverless
? Qual o slug do seu mini app? hello.world.serverless
? Qual o nome da sua empresa? Ame
Seu projeto deve ter agora a seguinte estrutura:
.
├── ame.conf.js
└── src
├── assets
│ └── images
│ └── icon_ame.svg
└── views
├── About.js
├── About.jsx
├── Home.js
├── Home.jsx
├── More.js
└── More.jsx
Inicie o ame-app-tools
ame-app-tools start
Se o ame-app-tools
alertar que as bibliotecas ame-miniapp-components
e ame-super-app-client
estão desatualizadas,
atualize suas respectivas versões no arquivo ./ame.conf.js
e reexecute o comando anterior.
Abra seu Super App Ame e escaneie o Qr Code que lhe foi exibido no navegador.
Abra um novo terminal e execute:
cd ~/hello-world-serverless
Crie o diretório para os módulos serverless:
mkdir src/server
Crie seu módulo serverless no diretório recém criado
touch src/server/HelloWorldBackend.js
Neste ponto, recomendamos importar o projeto hello-world-serverless
na IDE de sua preferencia.
Inclua o código-fonte a seguir em HelloWorldBackend.js
:
const TAG = __filename.split('/').pop();
export default class HelloWorldBackend {
async sayHello(req) {
console.debug(TAG, `About to say "Hello, ${req.body.message}!"`);
return { hello: `Hello, ${req.body.message}!` };
}
}
Neste momento, o terminal a rodar o comando start
deverá ter logado o seguinte:
Arquivo ~/hello-world-serverless/src/server/HelloWorldBackend.js alterado.
Salvo 7943ms
A partir deste momento, sua função serverless encontra-se pronta para execução.
Agora, vamos alterar alguns dos arquivos que foram auto-gerados.
Primeiro, abrimos o arquivo src/views/Home.js
e vamos substituir todo seu código-fonte por este aqui
import Ame from "ame-super-app-client";
const TAG = 'Home.js';
export default class Home {
state = { helloMessage: '' };
async componentDidMount() {
try {
const body = {
message: 'World'
}
const result = await Ame.server.exec('HelloWorldBackend.sayHello', body);
this.setState({helloMessage: result.hello});
} catch (e) {
console.error(TAG, e);
this.setState({helloMessage: `Ops! Algo saiu errado: ${e.message}`});
}
}
}
Por último, abrimos o arquivo src/views/Home.jsx
e vamos substituir todo seu código-fonte por este aqui
<Window>
<View>
<Illustration height={200} image={require('../assets/images/icon_ame.svg')}/>
<Header textAlign="center" >{this.state.helloMessage}</Header>
</View>
</Window>
Pronto, na tela de seu telefone você deverá estar vendo o seguinte:
Pode ficar melhor. Incluamos variáveis de ambiente no exemplo anterior
Inclua as seguintes variáveis de ambiente. Abra outro terminal para executar os comandos abaixo caso ame-app-tools start
ja esteja em execução.
$ ame-app-tools env ENV1='Variavel de ambiente 1'
As variaveis de ambiente foram criadas com sucesso.
$ ame-app-tools env ENV2='Variavel de ambiente 2'
As variaveis de ambiente foram criadas com sucesso.
Altere seu Home.jsx
, incluindo o componente Paragraph
<Window>
<View>
<Illustration height={200} image={require('../assets/images/icon_ame.svg')}/>
<Header textAlign="center" >{this.state.helloMessage}</Header>
<Paragraph textAlign="center" >Env 1: {this.state.envValue1}</Paragraph>
<Paragraph textAlign="center" >Env 2: {this.state.envValue2}</Paragraph>
</View>
</Window>
Altere seu HelloWorldBackend.js
const TAG = __filename.split('/').pop();
export default class HelloWorldBackend {
async sayHello(req) {
console.debug(TAG, `About to say "Hello, ${req.body.message}!"`);
return {
hello: `Hello, ${req.body.message}!`,
envValue1: process.env.ENV1,
envValue2: process.env.ENV2,
};
}
}
Altere seu Hello.js
import Ame from "ame-super-app-client";
const TAG = __filename.split('/').pop();
export default class Home {
state = {
helloMessage: '',
envValue1: '',
envValue2: ''
};
async componentDidMount() {
try {
const body = {
message: 'World'
}
const result = await Ame.server.exec('HelloWorldBackend.sayHello', body);
const { hello, envValue1, envValue2 } = result;
this.setState({helloMessage: hello, envValue1, envValue2 });
} catch (e) {
console.error(TAG, e);
this.setState({helloMessage: `Ops! Algo saiu errado: ${e.message}`});
}
}
}
Pronto, na tela de seu telefone você deverá estar vendo o seguinte:
Erros Frequentes
Unable to stringify response body
Dado o seguinte:
No serverless o codigo
return await Axios.default.get("https://meu.endpoint.com.br/list")
E na view, o codigo:
console.log(Ame.server.exec('MeuModulo.minhaFuncao'))
O erro abaixo costuma ocorrer quando se manda logar um AxiosResponse porém é preciso fazer um response.data
START RequestId: b3f9da2f-ce42-4930-a090-56bb363dcb9a ERROR Invoke Error {"errorType":"Error","errorMessage":"Unable to stringify response body","stack":["Error: Unable to stringify response body"," at _trySerializeResponse (/var/runtime/RAPIDClient.js:175:11)"," at RAPIDClient.postInvocationResponse (/var/runtime/RAPIDClient.js:45:22)"," at complete (/var/runtime/CallbackContext.js:33:12)"," at done (/var/runtime/CallbackContext.js:60:7)"," at succeed (/var/runtime/CallbackContext.js:64:5)"," at /var/runtime/CallbackContext.js:106:16"," at processTicksAndRejections (internal/process/task_queues.js:95:5)"]}END RequestId: b3f9da2f-ce42-4930-a090-56bb363dcb9aREPORT RequestId: b3f9da2f-ce42-4930-a090-56bb363dcb9a Duration: 420.44 ms Memory Size: 128 MB Max Memory Used: 62 MB