Como projetar uma API assíncrona sem servidor

Garrett Vargas Blocked Desbloquear Seguir Seguindo 7 de janeiro

Recentemente, executei um workshop para ensinar aos desenvolvedores como criar uma habilidade do Alexa. O material da oficina centrou-se em torno de um projeto para retornar os resultados da pesquisa de aluguel de carros. Como queria concentrar o aprendizado no desenvolvimento do fluxo de conversação e não na mecânica de fazer uma pesquisa de carro, decidi encapsular a lógica de pesquisa por trás de uma API. Além disso, como a solicitação de pesquisa de carro pode levar 10 segundos ou mais para ser concluída, eu queria que a chamada fosse assíncrona para que pudéssemos criar uma conversa como:

"Encontre um carro em Nova York na próxima semana"
"Que tamanho de carro você gostaria para a sua viagem em Nova York na próxima semana?"
“Um carro pequeno”
"Existe uma empresa específica que você gostaria de alugar?"
"Não"
"OK, eu encontrei um carro compacto da Enterprise por US $ 100 …"

A implementação da API assíncrona foi um projeto bastante interessante por si só, e neste post eu vou dizer como eu fiz isso de uma maneira sem servidor usando o API Gateway, as funções do Lambda e o S3.

Design para chamar uma pesquisa sem servidor assíncrona

Caçamba S3

O bucket do S3 nessa arquitetura serve como um cache que armazena os resultados da pesquisa para serem recuperados posteriormente. Os chamadores da API recebem um token quando iniciam uma pesquisa, e o design básico é usar esse token como parte do nome do objeto S3 para permitir que você recupere o conteúdo em uma chamada subsequente. Para impedir que o intervalo se preencha com os resultados da pesquisa, você pode definir uma política de expiração apropriada para o ciclo de vida de seus resultados da API (por exemplo, por quanto tempo deseja que os resultados assíncronos permaneçam ativos).

Observe que a política de expiração só pode ser definida em incrementos diurnos, portanto, mesmo que você tenha resultados que devam ser considerados obsoletos após 30 minutos, com essa abordagem, você ainda terá o objeto em armazenamento por pelo menos um dia.

Você pode associar um registro de data e hora com o objeto a verificar no seu código para garantir que o resultado seja ignorado se tiver mais de uma determinada idade, mas o próprio objeto persistirá por pelo menos um dia.

Para configurar seu intervalo, siga as etapas a seguir no Console de gerenciamento da AWS:

  • Clique em Criar Balde no S3
  • Digite o nome do intervalo e anote em qual região você o criou (você precisará verificar se as funções do Lambda e o gateway da API estão todos configurados nessa mesma região). Observe que os nomes de bucket do S3 são globalmente exclusivos, o que significa que um nome como "test" provavelmente será usado. Você precisará escolher algo que nenhum outro usuário tenha criado antes, então algo que incorpore seu nome ou a data atual seria um bom ponto de partida
  • Você pode manter o intervalo com as permissões padrão e sem controle de versão – você concederá explicitamente a sua permissão de função do Lambda a esse intervalo, por isso não o exponha publicamente ao mundo (na verdade, isso seria uma má ideia!)
  • Depois que o bucket for criado, clique nele e vá para a guia Management
  • Insira um ciclo de vida clicando em Add Lifecycle Rule
  • Digite um nome e passe pela tela Transições para terminar na tela Expiração
  • Como não adicionamos controle de versão ao nosso bucket do S3, você só precisa configurar uma regra de expiração para a versão atual, conforme ilustrado abaixo

Inserindo uma regra de ciclo de vida para expirar objetos após um dia do seu intervalo do S3

Funções Lambda

Eu tive a idéia básica de usar uma função do Lambda para realizar uma pesquisa, retornar um token para o chamador e gravar os resultados em um bucket do S3. Os resultados poderiam então ser recuperados por uma chamada subsequente, passando o token e qualquer informação adicional de filtragem (por exemplo, “carro pequeno” no exemplo acima).

No entanto, percebi rapidamente que minha função Lambda retornaria depois que eu validasse os parâmetros de entrada e retornasse o retorno com um token, o que significava que não era capaz de mantê-lo ativo para gravar os resultados da pesquisa no S3.

O que eu precisava era uma maneira de continuar a execução do código depois que eu tivesse o token para que eu pudesse retornar ao chamador da API. Para fazer isso, criei duas funções do Lambda, uma para validar os parâmetros e outra para executar a pesquisa e pesquisar os resultados armazenados em cache.

A primeira função valida os parâmetros, e uma vez que tenha feito isso, invoca a segunda função Lambda de forma assíncrona para iniciar a busca, então retorna com um token gerado de volta ao chamador enquanto a segunda função Lambda se move. O token que minha função generateToken usou no meu workshop era apenas um timestamp, pois eu não tinha considerações de escalabilidade, mas também poderia ser um UUID ou outro ID gerado.

Função Lambda para processar a validação de parâmetros

Como essa função do Lambda invoca outra função do Lambda, você precisará conceder as permissões apropriadas para fazer essa chamada. Você faz isso criando a função apropriada do IAM seguindo estas etapas:

  • Na sua função do Lambda, em Função de execução, selecione Criar uma função personalizada no menu suspenso
  • Isso iniciará o IAM em uma nova guia
  • Selecione Criar uma nova função do IAM no menu suspenso da função do IAM e forneça um nome
  • Na lista completa de funções do IAM, selecione essa nova função e clique em Anexar políticas.
  • Filtre pela política AWSLambdaRole e adicione-a a essa função. Opcionalmente, você pode modificar o JSON para dar acesso somente à segunda função do Lambda depois de criá-lo na etapa a seguir, consultando seu ARN no campo Recurso.

Configurando a função apropriada do IAM para chamar uma segunda função Lambda

A segunda função Lambda tem duas responsabilidades – realizar a pesquisa e salvar os resultados em um bucket S3 e recuperar os resultados de um bucket S3 quando chamado com um token válido. Eu poderia ter separado essa lógica e criado três funções do Lambda, mas senti que era um projeto melhor ter a lógica que acessava o cache e sabia como codificar / decodificar o objeto em um só lugar.

Como o API Gateway permite mapear parâmetros de consulta, é fácil diferenciar os casos em que essa função está sendo chamada para realizar uma nova pesquisa e quando é solicitada a recuperação de um resultado de pesquisa (vou demonstrar como faça isso depois). Note que esta função chama uma longa função interna doSearch que grava os resultados no S3 com base no token fornecido pela função anterior.

Execução da função Lambda para manipular a pesquisa e a recuperação do S3

Como essa função do Lambda faz uma chamada para ler e gravar a partir do S3, você terá que definir as permissões apropriadas para essa função do Lambda – que será diferente da primeira. Siga o mesmo conjunto de etapas para criar uma função do IAM, somente desta vez em vez da política AWSLambdaRole, você desejará associar a política AmazonS3FullAccess – novamente fornecendo opcionalmente o ARN do bucket S3 específico ao qual deseja fornecer acesso total.

Gateway de API

Com as funções do Lambda fora do caminho, a próxima etapa é criar um gateway de API em torno dessas funções para que um usuário tenha uma API REST para chamar. Lembre-se, o fluxo que eu queria construir a partir de uma perspectiva do cliente era:

  • Chamada de POST para a API com os parâmetros de pesquisa desejados
  • Receba um token de volta como resposta
  • Faça ao usuário algumas perguntas adicionais para ajudar a restringir os resultados
  • Receber chamada para a API com o token e critérios de filtro adicionais para obter o conjunto de resultados real

O API Gateway torna fácil e conveniente acessar suas funções do Lambda conforme desejado.

O primeiro passo é criar sua nova API. No AWS Management Console, você pode navegar até o API Gateway e clicar em Create API para criar uma nova API. Depois de atribuir um nome, você precisa criar os métodos que deseja usar para acessar a API. No meu caso, isso significava selecionar POST e GET como novos métodos no menu suspenso Actions .

Criando métodos no gateway de API

Vamos começar com o POST. Depois de selecionar o método POST, será solicitado o tipo de integração que você deseja usar. Selecione Função Lambda e preencha os detalhes com a primeira função Lambda criada para validar parâmetros e iniciar a pesquisa. Você não precisa apontar o API Gateway para a segunda função do Lambda que faz a pesquisa – isso é feito pela função de validação para você.

Depois de definir esses parâmetros, você verá o fluxo completo da API, junto com uma barra lateral TESTE que permitirá a você passar uma carga útil de teste para sua API para ver se ela é executada corretamente.

Para a chamada GET, faça o mesmo, embora neste caso você esteja chamando a segunda função Lambda passando em um token para que ele saiba recuperar os resultados do seu cache do S3. Além disso, nesse caso, você não terá uma carga útil JSON para transmitir – em vez disso, a expectativa é que o cliente forneça parâmetros de consulta na URL. O API Gateway permite que você faça essa transformação facilmente por meio de um modelo de mapeamento.

Os passos básicos são os seguintes:

  • Adicione um método GET no menu suspenso Ações
  • Definir o tipo de integração para a função Lambda
  • Digite os detalhes da segunda função Lambda
  • Uma vez criado, clique na etapa Solicitação de Integração da execução do seu método GET
  • Expanda a seção Modelos de mapeamento e clique em Adicionar modelo de mapeamento
  • Digite application / json na caixa de edição e clique na seleção para confirmar
  • Digite no mapeamento de parâmetros de consulta para solicitação JSON – você fará isso com as chaves do formulário "field": “$input.params('queryparam')" Isso queryparam o parâmetro de consulta chamado queryparam para um campo denominado field

O bom aqui é que você não precisa ter o mesmo nome para os parâmetros de consulta expostos ao seu cliente quanto ao uso interno na sua função do Lambda. Por exemplo, no meu caso, eu espero parâmetros de User, CarSize, SupplierRating e UpgradeClass, mas os mapeio para id, size, rating e upgrade, respectivamente, para uso interno.

Mapeando parâmetros de URL para uma carga útil JSON para o GET

Construa e implemente

Agora que você integrou sua função do Lambda no API Gateway, você está pronto para criar e implantar. No menu Ações, selecione Implementar API . O gateway de API solicitará um estágio de implantação; escolha [Novo Estágio] para criar um novo estágio e forneça um nome como Beta. Depois de implantar a API, o API Gateway informará o URL a ser usado para invocar sua API. Você usa o mesmo URL para as funções POST e GET. Muito fácil, não é?

Preparando-se para implantar sua API

Embrulhar

O que descrevi aqui é a base para uma API sem servidor assíncrona. Há muitos casos de borda e tratamento de erros que eu transcrevi, bem como técnicas no API Gateway para proteger a API que não abordei, como a validação de que todos os parâmetros necessários são definidos antes de chamar a função do Lambda ou exigir um acesso token em vez de criar uma API totalmente aberta.

Além disso, esse caso de uso era para um ambiente de pequena oficina. Você teria que analisar seu próprio caso de uso para entender a escala de que precisa e se essa abordagem funcionaria para você. Você deve prestar atenção especial às configurações de execução simultâneas para sua função do Lambda. Se a chamada subjacente que você está tentando fazer demorar um minuto para ser executada, por exemplo, mesmo com um limite de 1.000 execuções simultâneas, você só poderá fazer 16 chamadas por segundo, o que pode não ser suficiente para uma carga de trabalho de produção completa.

Advertências à parte, para os casos de uso corretos, essa abordagem pode ser uma maneira simples e eficiente de criar uma API assíncrona sem precisar ficar com servidores dedicados ou uma solução de armazenamento em cache.