O blog da AWS
Desenvolvendo funções AWS Lambda portáteis
Por Uri Segev, Principal Serverless Specialist Solutions Architect.
Ao desenvolver novas aplicações ou modernizar as existentes, você pode enfrentar um dilema: qual tecnologia de computação usar? Um serviço de computação Serverless como o AWS Lambda ou talvez contêineres? Frequentemente, Serverless pode ser a melhor abordagem graças ao dimensionamento automático, alta disponibilidade integrada e um modelo de cobrança pay-for-use. No entanto, você pode hesitar em escolher Serverless por razões como:
- Custo percebido mais alto ou dificuldade em estimar custos
- É uma mudança de paradigma, que requer aprendizado para preencher a lacuna de conhecimento
- Conceitos equivocados sobre capacidades e casos de uso do Lambda
- Preocupação de que usar Lambda resultará em lock-in
- Investimentos existentes em plataformas e ferramentas não Serverless
Esta publicação do blog sugere melhores práticas para desenvolver funções Lambda portáteis que permitem que você migre facilmente seu código para contêineres se optar por isso posteriormente. Ao fazer isso, você pode evitar lock-in e experimentar a abordagem Serverless de forma livre de riscos.
Cada seção desta publicação do blog descreve o que você precisa considerar ao escrever código portátil e as etapas necessárias para migrar este código do Lambda para contêineres, se você optar por isso posteriormente.
Melhores práticas para funções Lambda portáteis
Separar lógica de negócios e handler do Lambda
As funções Lambda são orientadas a eventos por natureza. Quando um evento específico acontece, ele invoca a função Lambda chamando seu método handler. O método handler recebe um objeto event que contém informações sobre o motivo da invocação da função. Uma vez que a execução da função é concluída, ela retorna do método handler. O que é retornado do handler é o valor de retorno da função.
Para escrever código portátil, recomendamos usar o método handler apenas como uma interface entre o runtime do Lambda (objeto event) e a lógica de negócios. Usando a terminologia da arquitetura hexagonal, o handler deve ser um adaptador de condução fazendo chamadas para a porta, que é a interface exposta pela lógica de negócios. O handler deve extrair todas as informações necessárias do objeto event e então chamar um método separado que implementa a lógica de negócios.
Quando esse método retorna, o handler constrói o resultado no formato esperado pelo invocador da função e o retorna. Também recomendamos dividir o código do handler e o código da lógica de negócios em arquivos separados. Caso você opte por migrar para contêineres posteriormente, você simplesmente migra seus arquivos de código de lógica de negócios sem alterações adicionais.
O seguinte pseudocódigo mostra um handler do Lambda que extrai informações do objeto event e chama a lógica de negócios. Uma vez que a lógica de negócios é concluída, o handler coloca a resposta no valor de retorno da função:
O seguinte pseudocódigo mostra a lógica de negócios. Ela está localizada em um arquivo separado e não tem conhecimento de que está sendo invocada a partir de uma função Lambda. É lógica pura.
Esta abordagem também facilita a execução de testes unitários na lógica de negócios sem a necessidade de construir objetos event e invocar o handler do Lambda.
Se você migrar para contêineres posteriormente, você inclui os arquivos de lógica de negócios em seu contêiner com novo código de interface conforme descrito na seção seguinte.
Integração de fonte de eventos
Um benefício das funções Lambda é a integração de fonte de eventos. Por exemplo, se você integrar o Lambda com o Amazon Simple Queue Service (Amazon SQS), o serviço Lambda cuidará de fazer polling da fila, invocar a função Lambda e excluir as mensagens da fila quando concluído. Ao usar esta integração, você precisa escrever menos código boilerplate. Você pode se concentrar apenas em implementar a lógica de negócios e não a integração com a fonte de eventos.
O seguinte pseudocódigo mostra como o handler do Lambda se parece para uma fonte de eventos SQS:
Como você pode ver no código anterior, a função Lambda quase não tem conhecimento de que está sendo invocada a partir do SQS. Não há chamadas de API do SQS. Ela apenas conhece a estrutura do objeto event, que é específica do SQS.
Ao migrar para um contêiner, a responsabilidade de integração passa do serviço Lambda para você, o desenvolvedor. Existem diferentes fontes de eventos na AWS, e cada uma delas exigirá uma abordagem diferente para consumir eventos e invocar a lógica de negócios. Por exemplo, se a fonte de eventos for o Amazon API Gateway, sua aplicação precisará criar um servidor HTTP que escuta em uma porta HTTP e aguarda solicitações recebidas para invocar a lógica de negócios.
Se a fonte de eventos for o Amazon Kinesis Data Streams, sua aplicação precisará executar um poller que lê registros dos shards, acompanhar registros processados, lidar com o caso de uma mudança no número de shards no stream, tentar novamente em caso de erros e muito mais. Independentemente da fonte de eventos, se você seguir as recomendações anteriores, não precisará alterar nada no código da lógica de negócios.
O seguinte pseudocódigo mostra como a integração com o SQS se parecerá em um contêiner. Observe que você perderá alguns recursos como batching, filtragem e, é claro, dimensionamento automático.
Outro ponto a considerar aqui são os destinos do Lambda. Se sua função for invocada de forma assíncrona e você configurou um destino para sua função, você precisará incluir isso no código de interface. Ele precisará capturar qualquer erro de lógica de negócios e, com base nisso, invocar o destino correto.
Empacotar funções como contêineres
O Lambda suporta empacotamento de funções como arquivos .zip e imagens de contêiner. Para desenvolver código portátil, recomendamos usar imagens de contêiner como seu método de empacotamento padrão. Embora você empacote a função como uma imagem de contêiner, você não pode executá-la em outras plataformas de contêiner como o Amazon Elastic Container Service (Amazon ECS) ou Amazon Elastic Kubernetes Service (EKS). No entanto, ao empacotá-la desta forma, a migração para contêineres posteriormente será mais fácil, pois você já está usando as mesmas ferramentas e já criou um Dockerfile que exigirá alterações mínimas.
Um exemplo de Dockerfile para Lambda se parece com isto:
Se você migrar para contêineres posteriormente, você precisará alterar o Dockerfile para usar uma imagem base diferente e adaptar a linha CMD que define como iniciar a aplicação. Isso é além das alterações de código descritas na seção anterior.
O Dockerfile correspondente para o contêiner se parecerá com isto:
O pipeline de implantação também precisa mudar, pois implantamos em um destino diferente. No entanto, a construção dos artefatos permanece a mesma.
Invocação única por instância
As funções Lambda são executadas em seu próprio ambiente de runtime isolado. Cada ambiente lida com uma única solicitação por vez, o que funciona muito bem para o Lambda. No entanto, se você migrar sua aplicação para contêineres, provavelmente invocará a lógica de negócios de múltiplas threads em um único processo ao mesmo tempo.
Esta seção discute aspectos da mudança de uma invocação única para múltiplas invocações concorrentes dentro do mesmo processo.
Variáveis estáticas
Variáveis estáticas são aquelas que são instanciadas uma vez e depois reutilizadas em múltiplas invocações. Exemplos de tais variáveis são conexões de banco de dados ou informações de configuração.
Para otimização de função, e especificamente para reduzir cold starts e a duração de invocações de função warm, recomendamos inicializar todas as variáveis estáticas fora do handler da função e armazená-las em variáveis globais para que invocações futuras as reutilizem.
Recomendamos usar uma função de inicialização que você escreve como parte do módulo de lógica de negócios e que você invoca de fora do handler. Esta função salva informações em variáveis globais que o código de lógica de negócios reutiliza em invocações.
O seguinte pseudocódigo mostra a função Lambda:
E o código da lógica de negócios se parecerá com isto:
O mesmo também se aplica a contêineres. Você geralmente inicializará variáveis estáticas quando o processo iniciar e não para cada solicitação individual. Ao migrar para contêineres, tudo o que você precisa fazer é chamar a função de inicialização antes de iniciar o loop principal da aplicação.
Como você pode ver, não há alterações no código da lógica de negócios.
Conexões de banco de dados
Como as funções Lambda não compartilham nada entre os ambientes de runtime, diferentemente dos contêineres, elas não podem depender de pools de conexão ao se conectar a um banco de dados relacional. Por esse motivo, criamos o Amazon RDS Proxy, que atua como um pool de conexão centralizado usado por muitas funções.
Para escrever funções Lambda portáteis, recomendamos usar um objeto de pool de conexão com uma única conexão. Seu código de lógica de negócios sempre solicitará uma conexão do pool ao fazer uma solicitação de banco de dados. Você ainda precisará usar o RDS Proxy.
Se você migrar posteriormente para contêineres, você pode aumentar o número de conexões no pool para um número maior sem alterações adicionais e a aplicação escalará sem sobrecarregar o banco de dados.
Sistema de arquivos
As funções Lambda vêm com uma pasta /tmp gravável no tamanho de 512 MB a 10 GB. Como cada instância de função é executada em um ambiente de runtime isolado, os desenvolvedores geralmente usam nomes de arquivo fixos para arquivos armazenados nessa pasta. Se você executar o mesmo código de lógica de negócios em um contêiner em múltiplas threads, as diferentes threads sobrescreverão os arquivos criados por outras.
Recomendamos usar nomes de arquivo únicos em cada invocação. Anexe um UUID ou outro número aleatório ao nome do arquivo. Exclua os arquivos assim que terminar com eles para evitar ficar sem espaço.
Se você migrar seu código para contêineres posteriormente, não há nada a fazer.
Aplicações web portáteis
Se você desenvolver uma aplicação web, há outra maneira de alcançar portabilidade. Você pode usar o projeto AWS Lambda Web Adapter para hospedar uma aplicação web dentro de uma função Lambda. Desta forma, você pode desenvolver uma aplicação web com frameworks familiares (por exemplo, Express.js, Next.js, Flask, Spring Boot, Laravel, ou qualquer coisa que use HTTP 1.1/1.0), e executá-la no Lambda. Se você empacotar sua aplicação web como um contêiner, a mesma imagem Docker pode ser executada no Lambda (usando o web adapter) e em contêineres.
Portando de contêineres para Lambda
Esta publicação do blog demonstra como desenvolver funções Lambda portáteis que você pode facilmente portar para contêineres. Levar essas recomendações em consideração também pode ajudar a desenvolver código portátil em geral, o que permite que você porte contêineres para funções Lambda.
Algumas coisas a considerar:
- Separe a lógica de negócios do código de interface no contêiner. O código de interface deve interagir com as fontes de eventos e invocar a lógica de negócios.
- Como as funções Lambda têm apenas uma pasta /tmp gravável, replique isso em seus contêineres (mesmo que você possa escrever em locais diferentes).
Conclusão
Esta publicação do blog sugere melhores práticas para desenvolver funções Lambda que permitem que você obtenha os benefícios de uma abordagem Serverless sem arriscar lock-in.
Ao seguir essas melhores práticas para separar a lógica de negócios dos handlers do Lambda, empacotar funções como contêineres, lidar com a invocação única por instância do Lambda e muito mais, você pode desenvolver funções Lambda portáteis. Como consequência, você será capaz de portar seu código do Lambda para contêineres com esforço mínimo se optar por migrar para contêineres posteriormente.
Consulte essas melhores práticas e exemplos de código para facilitar a adoção de uma abordagem Serverless ao desenvolver sua próxima aplicação.
Para mais recursos de aprendizado Serverless, visite o Serverless Land.
Este conteúdo foi traduzido do post original do blog, que pode ser encontrado aqui.
Tradutores
![]() |
Nicolas Tarzia é Senior Technical Account Manager na AWS, com mais de 13 anos de experiência, com ampla experiência em arquitetura cloud, engenharia e design de software. Sua área de interesse são tecnologias serverless.
https://www.linkedin.com/in/nicolastarzia |
![]() |
Daniel Abib é Arquiteto de Soluções Sênior e Especialista em Amazon Bedrock na AWS, com mais de 25 anos trabalhando com gerenciamento de projetos, arquiteturas de soluções escaláveis, desenvolvimento de sistemas e CI/CD, microsserviços, arquitetura Serverless & Containers e especialização em Machine Learning. Ele trabalha apoiando Startups, ajudando-os em sua jornada para a nuvem.
https://www.linkedin.com/in/danielabib/ |

