As ofertas da AWS Lambda e Amazon API Gateway forneceram um novo mecanismo poderoso para o desenvolvimento rápido de APIs REST sem a sobrecarga de criação de infraestrutura e código boilerplate para ativar servidores web. Quando você percebe que o SAM está uma bagunça e passa para o Serverless Framework, as coisas realmente começam a voar. Inevitavelmente, você chegará ao ponto em que essa prova de conceito precisa começar a se tornar mais real. Isso significa adicionar funcionalidades como autenticação e tratamento/relatório de erros. Depois de vasculhar a web e achar os resultados insatisfatórios, decidi publicar minha experiência aqui na esperança de que outros possam se beneficiar.
Objetivo: fornecer códigos de status e mensagens apropriadas para os consumidores da API REST, como a interface do usuário em Angular, ao mesmo tempo em que fornecemos uma intrusão mínima dos códigos de status HTTP no próprio código Lambda.
Etapa 1: Integração Lambda Padrão
O Serverless Framework faz um ótimo trabalho na criação de recursos com padrões razoáveis, removendo a necessidade de código boilerplate, para que o desenvolvedor (ou engenheiro DevOps) possa se concentrar em "coisas mais importantes". Para fazer sentido, a primeira coisa que precisamos fazer é tentar entender o que o Serverless Framework oferece por padrão.
Usando a Integração Lambda, criamos um endpoint simples em nosso serverless.yml
:
createDeal:
handler: create-deal/index.handler
events:
- http:
path: create-deal
integration: lambda
method: post
cors: true
Quando fazemos o deploy na AWS, vemos o seguinte no Amazon API Gateway:
Serverless Framework, por padrão, criou várias expressões regulares que são mapeadas para códigos de erro. Com isso, precisamos garantir que os erros lançados em nosso código incluam o código de status apropriado com a string de erro esperada. Vejamos um exemplo:
exports.handler = async (event) => {
if (!event.dealName) {
throw new Error('[400] Missing required property');
}
return createDeal(event);
};
Fácil né?
Talvez não. O que acontece se recebermos um erro em createDeal()
que não esteja em conformidade com este padrão? Não podemos confiar em todos os métodos e bibliotecas chamados para saber se as strings de erro precisam conter um código de status HTTP. Certamente, podemos executar um try..catch
, mas isso ainda coloca a lógica de determinar o código de status HTTP em nosso código dentro do endpoint. Além disso, qualquer consumidor da resposta precisará usar ou analisar o código de status da mensagem de erro. Por fim, se um erro for gerado sem um dos códigos de status mapeados, o API Gateway retornará uma resposta 504 Bad Gateway
, que é de pouco valor para o consumidor. Nós podemos fazer melhor.
Etapa 2: Integração Proxy Lambda
Uma recomendação comum ao criar Lambdas é usar a "integração de proxy". O serverless-stack
tem uma excelente descrição de como fazer isso; portanto, tentarei não ser repetitivo. O desenvolvedor é responsável por definir o código de status HTTP ao criar uma resposta e é responsável por capturar quaisquer erros no código, para que uma resposta apropriada possa ser criada. Vamos dar uma olhada nos exemplos abaixos:
createDeal:
handler: create-deal/index.handler
events:
- http:
path: create-deal
method: post
cors: true
Observe que, em vez de usar a integração lambda, estamos usando a integração padrão (proxy).
exports.handler = async (event) => {
const dealInfo = JSON.parse(event.body);
if (!dealInfo.dealName) {
return {
statusCode: 400,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: {
message: 'Missing required property'
}
}
}
const newDeal = await createDeal(dealInfo);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(newDeal)
}
};
Essa abordagem tem seus prós e contras. Como a solução anterior, o desenvolvedor é responsável por capturar todos os erros e retornar o código e a mensagem de status apropriados. Também como a solução anterior, obteremos um 504 Bad Gateway
se a resposta não estiver no formato correto, como quando um erro não detectado é gerado.
A solução no serverless-stack
inclui uma função utilitária para simplificar a resposta return buildResponse(statusCode, body)
. Isso ajuda parte do código que é comum em torno da resposta, como informações do cabeçalho, mas não aborda as preocupações de erros não capturados e mapeamentos de código de status na função lambda.
Etapa 3: Middleware com Middy
Uma abordagem popular para abstrair muito do clichê de códigos Lambda é usar uma solução de middleware como o middy. Middy envolve a chamada lambda e permite ao desenvolvedor anexar qualquer número de código em pré e pós-execução, que possa ser usado para modificar a solicitação ou resposta. Isso não soluciona o problema de declarar códigos de status HTTP na função lambda, mas fornece alguns recursos poderosos para integração com outras bibliotecas comuns, como o http-errors. Infelizmente, o ganho líquido para este caso de uso não é melhor do que as soluções mais leves acima. Acabamos com um resultado semelhante com mais dependências e código adicional:
createDeal:
handler: create-deal/index.handler
events:
- http:
path: create-deal
method: post
cors: true
Novamente estamos usando a integração padrão (proxy).
const middy = require('middy');
const {httpErrorHandler, cors} = require('middy/middlewares');
const createError = require('http-errors');
const {autoProxyResponse} = require('middy-autoproxyresponse');
// envolve o manipulador da lambda no middleware
const handler = middy(async (event) => {
const dealInfo = JSON.parse(event.body);
if (!dealInfo.dealName) {
throw new createError.BadRequest({message: 'Missing required property'});
}
return createDeal(dealInfo);
});
// declaramos quais middlewares usar
handler
.use(autoProxyResponse())
.use(httpErrorHandler())
.use(cors());
module.exports = {handler};
O middleware CORS é uma adição bem-vinda, pois permite ignorar a entrada manual do cabeçalho 'Access-Control-Allow-Origin'
, mas precisamos adicionar o middleware de terceiros auto-proxy-response para agrupar o valor retornado em um formato de resposta lambda válido (com código de status 200) ou criar manualmente o objeto de resposta.
Etapa 4 - Final: Mapeando códigos de erro no API Gateway
Após muita discussão sobre usar a resposta do proxy (como recomendado pelo Serverless Framework e pela AWS), cheguei a uma conclusão que já vi repetidamente na web: tornar o desenvolvimento de curto prazo mais rápido, mas não fornece a separação que eu esperaria da resposta HTTP do código lambda. Essa separação pode ser usada para simplificar a vida dos desenvolvedores, dificultando o tiro no pé por não conseguir detectar um erro e ao mesmo tempo reduzir boilerplate para tornar o código mais legível como uma unidade de trabalho. O que isso adiciona é uma configuração extra no Serverless Framework na forma de modelos de resposta e regex de códigos de erros.
Código lambda:
exports.handler = async (event) => {
if (!event.dealName) {
throw new Error('Missing required property');
}
return createDeal(event);
};
Retornamos a um código lambda simples em um mundo onde os códigos de status HTTP são alegremente desconhecidos. Se createDeal()
retornar um erro que não seja capturado e reconvertido, não interromperemos o tratamento pelo API Gateway.
No serverless.yml
createDeal:
handler: create-deal/index.handler
events:
- http:
path: create-deal
method: post
cors: true
response:
headers:
Content-Type: "'application/json'"
statusCodes:
200:
pattern: ''
400:
pattern: '^Missing.*'
template: ${file(resources/error-response-template.yml)}
500:
pattern: '^(?!Missing).*'
template: ${file(resources/500-response-template.yml)}
Nosso error-response-template.yml
:
'{
"message": $input.json("$.errorMessage")
}'
E em 500-response-template.yml
:
'{
"message": "Internal Server Error"
}'
Toda a manipulação do código de status HTTP foi abstraída na configuração do API Gateway. O API Gateway aplicará o regex pattern no campo errorMessage
da resposta para determinar qual código de status e modelo de resposta usar (consulte a documentação para mais detalhes). Nesse caso, configurei 3 códigos de status possíveis:
- 200 - se não houver mensagem de erro
- 400 - se a mensagem de erro começar com a sequência
Missing
- 500 - se a mensagem de erro iniciar com qualquer outra sequência (catch-all)
Eles funcionarão além dos códigos de status 401 e 403 existentes que o API Gateway retornará quando a autenticação ou autorização falhar usando um autorizador personalizado (tópico para outro artigo).
Além disso, criei modelos de resposta padrão que podem ser reutilizados, reduzindo o copiar e colar, padronizando o formato de retorno.
No mais, acredito que isso fornece a experiência mais simples para o desenvolvedor, maximizando o comportamento consistente e o formato de resposta. O desenvolvedor não precisa se preocupar com códigos de status HTTP ao escrever funções lambda, e os possíveis códigos de status que podem ser retornados são documentados em um único local, e não em todo o código. Há algum custo e complexidade em executar a regex na mensagem de erro, mas para esse caso de uso é uma troca aceitável.
Eu espero que você tenha achado útil essa explicação!
Créditos
- Error Handling in AWS Lambda and API Gateway, escrito originalmente por Ben Arena.
Top comments (2)
What does
integration: lambda
does on serverless.yml?olá Caíque, obrigado pela leitura,
a diferença principal é o payload passado pelo API Gateway para sua Lambda
usando
integration: lambda
temos o seguinte cenário:ao usar
integration: lambda-proxy
(que é o default ao usar Serverless Framework), temos o seguinte cenário:eu explico mais sobre isso nesse outro artigo:
dev.to/oieduardorabelo/aws-serverl...
e você pode verificar a documentação do framework também:
serverless.com/framework/docs/prov...