Definir o manipulador de função do Lambda em C# - AWS Lambda

Definir o manipulador de função do Lambda em C#

O manipulador da função do Lambda é o método no código da função que processa eventos. Quando sua função é invocada, o Lambda executa o método do manipulador. A função é executada até que o manipulador retorne uma resposta, seja encerrado ou atinja o tempo limite.

Quando sua função é invocada e o Lambda executa o método manipulador da função, ele passa dois argumentos para a função. O primeiro argumento é o objeto do event. Quando outro Serviço da AWS invoca a função, o objeto event contém dados sobre o evento que fez com que a função fosse invocada. Por exemplo, um objeto event do API Gateway contém informações sobre o caminho, o método HTTP e os cabeçalhos HTTP. A estrutura exata do evento varia de acordo com a invocação da função pelo Serviço da AWS. Consulte Invocando o Lambda com eventos de outros serviços da AWS para obter mais informações sobre formatos de eventos para serviços individuais.

O Lambda também passa um objeto context para a função. Esse objeto contém informações sobre a invocação, a função e o ambiente de execução. Para ter mais informações, consulte Objeto de contexto do AWS Lambda em C#.

O formato nativo para todos os eventos do Lambda são streams de bytes representando o evento formatado em JSON. A não ser que os parâmetros de entrada e saída de sua função sejam do tipo System.IO.Stream, você precisará serializá-los. Especifique o serializador que você deseja usar definindo o atributo LambdaSerializer do assembly. Para ter mais informações, consulte Serialização em funções do Lambda.

Modelos de execução do .NET para Lambda

Há dois modelos de execução diferentes para executar funções do Lambda no .NET: a abordagem de biblioteca de classes e a abordagem de assembly executável.

Na abordagem biblioteca de classes, você fornece ao Lambda uma string indicando o AssemblyName, ClassName e Method da função a ser invocada. Para obter mais informações sobre o formato de dessa string, consulte Manipuladores de bibliotecas de classes. Durante a fase de inicialização da função, a classe da função é inicializada e qualquer código no construtor é executado.

Na abordagem de assembly executável, você usa o recurso de instruções de nível superior do C# 9. Essa abordagem gera um assembly executável que o Lambda executa sempre que recebe um comando invocar para a função. Você fornece ao Lambda somente o nome do assembly executável a ser executado.

As seções a seguir fornecem exemplos de código de função para essas duas abordagens.

Manipuladores de bibliotecas de classes

O código da função do Lambda a seguir mostra um exemplo de um método manipulador (FunctionHandler) para uma função do Lambda que usa a abordagem de biblioteca de classes. Neste exemplo de função, o Lambda recebe um evento do API Gateway que invoca a função. A função lê um registro de um banco de dados e retorna o registro como parte da resposta do API Gateway.

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] namespace GetProductHandler; public class Function { private readonly IDatabaseRepository _repo; public Function() { this._repo = new DatabaseRepository(); } public async Task<APIGatewayProxyResponse> FunctionHandler(APIGatewayProxyRequest request) { var id = request.PathParameters["id"]; var databaseRecord = await this._repo.GetById(id); return new APIGatewayProxyResponse { StatusCode = (int)HttpStatusCode.OK, Body = JsonSerializer.Serialize(databaseRecord) }; } }

Ao criar uma função do Lambda, você precisa fornecer ao Lambda informações sobre o manipulador da função na forma de uma string do manipulador. Isso informa ao Lambda qual método do código deve ser executado quando sua função é invocada. Em C#, o formato da string do manipulador ao usar a abordagem da biblioteca de classes é o seguinte:

ASSEMBLY::TYPE::METHOD, em que:

  • ASSEMBLY é o nome do arquivo de assembly do .NET para a aplicação. Se você usar a CLI do Amazon.Lambda.Tools para criar a aplicação e não definir o nome do assembly usando a propriedade AssemblyName no arquivo .csproj, ASSEMBLY será simplesmente o nome do arquivo .csproj.

  • TYPE é o nome completo do tipo de manipulador, que consiste em Namespace e o ClassName.

  • METHOD é o nome do método do manipulador da função no código.

Para o código de exemplo mostrado, se o assembly for denominado GetProductHandler, a string do manipulador será GetProductHandler::GetProductHandler.Function::FunctionHandler.

Manipuladores de assembly executáveis

No exemplo a seguir, a função do Lambda é definida como um assembly executável. O método manipulador nesse código é denominado Handler. Ao usar assemblies executáveis, o runtime do Lambda deve ser inicializado. Para fazer isso, use o método LambdaBootstrapBuilder.Create. Esse método usa como entradas o método que a função usa como manipulador e o serializador Lambda a ser usado.

Para obter mais informações sobre o uso de instruções de nível superior, consulte Introdução ao runtime do .NET 6 para o AWS Lambda no blog de computação da AWS.

namespace GetProductHandler; IDatabaseRepository repo = new DatabaseRepository(); await LambdaBootstrapBuilder.Create<APIGatewayProxyRequest>(Handler, new DefaultLambdaJsonSerializer()) .Build() .RunAsync(); async Task<APIGatewayProxyResponse> Handler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) { var id = input.PathParameters["id"]; var databaseRecord = await this.repo.GetById(id); return new APIGatewayProxyResponse { StatusCode = (int)HttpStatusCode.OK, Body = JsonSerializer.Serialize(databaseRecord) }; };

Ao usar assemblies executáveis, a string do manipulador que diz ao Lambda como executar seu código é o nome do assembly. Neste exemplo, isso seria GetProductHandler.

Serialização em funções do Lambda

Se a função do Lambda usar tipos de entrada ou de saída diferentes de objeto Stream, você deverá adicionar uma biblioteca de serialização à aplicação. Você pode implementar a serialização usando a serialização padrão baseada em reflexão fornecida por System.Text.Json e Newtonsoft.Json ou usando a serialização gerada pela fonte.

Usar a serialização gerada pela fonte

A serialização gerada pela fonte é um recurso do .NET versão 6 e posteriores que permite que o código de serialização seja gerado durante a compilação. Ele elimina a necessidade de reflexão e pode melhorar a performance da função. Para usar a serialização gerada pela fonte em sua função, faça o seguinte:

  • Crie uma nova classe parcial herdada de JsonSerializerContext, adicionando atributos JsonSerializable para todos os tipos que exigem serialização ou desserialização.

  • Configure o LambdaSerializer para usar um SourceGeneratorLambdaJsonSerializer<T>.

  • Atualize as serializações ou desserializações manuais no código da aplicação para usar a classe recém-criada.

Um exemplo de função usando a serialização gerada pela fonte é mostrado no código a seguir.

[assembly: LambdaSerializer(typeof(SourceGeneratorLambdaJsonSerializer<CustomSerializer>))] public class Function { private readonly IDatabaseRepository _repo; public Function() { this._repo = new DatabaseRepository(); } public async Task<APIGatewayProxyResponse> FunctionHandler(APIGatewayProxyRequest request) { var id = request.PathParameters["id"]; var databaseRecord = await this._repo.GetById(id); return new APIGatewayProxyResponse { StatusCode = (int)HttpStatusCode.OK, Body = JsonSerializer.Serialize(databaseRecord, CustomSerializer.Default.Product) }; } } [JsonSerializable(typeof(APIGatewayProxyRequest))] [JsonSerializable(typeof(APIGatewayProxyResponse))] [JsonSerializable(typeof(Product))] public partial class CustomSerializer : JsonSerializerContext { }
nota

Se você quiser usar a compilação antecipada (AOT) nativa com o Lambda, você deve usar a serialização gerada pela fonte.

Usar serialização baseada em reflexão

A AWS fornece bibliotecas pré-criadas para permitir que você adicione rapidamente a serialização á aplicação. Você configura isso usando os pacotes NuGet Amazon.Lambda.Serialization.SystemTextJson ou Amazon.Lambda.Serialization.Json. Nos bastidores, o Amazon.Lambda.Serialization.SystemTextJson usa System.Text.Json para realizar tarefas de serialização e o Amazon.Lambda.Serialization.Json usa o pacote Newtonsoft.Json.

Você pode criar sua própria biblioteca de serialização implementando a interface ILambdaSerializer, que está disponível como parte da biblioteca Amazon.Lambda.Core. Essa interface define dois métodos:

  • T Deserialize<T>(Stream requestStream);

    Você implementa esse método para desserializar a carga útil da solicitação da API Invoke no objeto que é passado para o manipulador da função do Lambda.

  • T Serialize<T>(T response, Stream responseStream);

    Você implementa esse método para serializar o resultado retornado pelo manipulador da função do Lambda na carga útil da resposta retornada pela operação da API Invoke.

Simplificar o código da função com a estrutura Lambda Annotations

O Lambda Annotations é uma estrutura para o .NET 6 e o .NET 8 que simplifica a escrita de funções do Lambda usando C#. Com a estrutura Annotations, você pode substituir grande parte do código em uma função Lambda escrita usando o modelo de programação regular. O código escrito usando a estrutura usa expressões mais simples que permitem que você se concentre em sua lógica de negócios.

O código de exemplo a seguir mostra como o uso da estrutura de anotações pode simplificar a escrita de funções do Lambda. O primeiro exemplo mostra o código escrito usando o modelo regular de programa do Lambda e o segundo mostra o equivalente usando a estrutura Annotations.

public APIGatewayHttpApiV2ProxyResponse LambdaMathAdd(APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context) { if (!request.PathParameters.TryGetValue("x", out var xs)) { return new APIGatewayHttpApiV2ProxyResponse { StatusCode = (int)HttpStatusCode.BadRequest }; } if (!request.PathParameters.TryGetValue("y", out var ys)) { return new APIGatewayHttpApiV2ProxyResponse { StatusCode = (int)HttpStatusCode.BadRequest }; } var x = int.Parse(xs); var y = int.Parse(ys); return new APIGatewayHttpApiV2ProxyResponse { StatusCode = (int)HttpStatusCode.OK, Body = (x + y).ToString(), Headers = new Dictionary≪string, string> { { "Content-Type", "text/plain" } } }; }
[LambdaFunction] [HttpApi(LambdaHttpMethod.Get, "/add/{x}/{y}")] public int Add(int x, int y) { return x + y; }

Para ver outro exemplo de como o uso do Lambda Annotations pode simplificar seu código, consulte este exemplo de aplicação entre serviços no repositório do awsdocs/aws-doc-sdk-examples GitHub. A pasta PamApiAnnotations usa Lambda Annotations no arquivo function.cs principal. Para comparação, a pasta PamApi tem arquivos equivalentes escritos usando o modelo de programação do Lambda normal.

A estrutura Annotations usa geradores de código-fonte para gerar código que é traduzido do modelo de programação do Lambda para o código visto no segundo exemplo.

Para obter mais informações sobre como usar o Lambda Annotations para .NET, consulte os seguintes recursos:

Injeção de dependência com a estrutura Lambda Annotations

Você também pode usar a estrutura do Lambda Annotations para adicionar injeção de dependência nas funções do Lambda usando a sintaxe com a qual você está familiarizado. Quando você adiciona um atributo [LambdaStartup] a um arquivo Startup.cs, a estrutura do Lambda Annotations gera o código necessário durante a compilação.

[LambdaStartup] public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IDatabaseRepository, DatabaseRepository>(); } }

A função Lambda pode injetar serviços usando injeção de construtor ou injetando em métodos individuais usando o atributo [FromServices].

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] namespace GetProductHandler; public class Function { private readonly IDatabaseRepository _repo; public Function(IDatabaseRepository repo) { this._repo = repo; } [LambdaFunction] [HttpApi(LambdaHttpMethod.Get, "/product/{id}")] public async Task<Product> FunctionHandler([FromServices] IDatabaseRepository repository, string id) { return await this._repo.GetById(id); } }

Restrições do manipulador de função do Lambda

Observe que há algumas restrições na assinatura do manipulador.

  • Não pode ser unsafe e usar tipos de ponteiro na assinatura do manipulador, embora seja possível usar o contexto unsafe dentro do método do manipulador e de suas dependências. Para obter mais informações, consulte não seguro (Referência de C#) no site Microsoft Docs.

  • Não pode passar um número variável de parâmetros usando a palavra-chave params ou usar ArgIterator como um parâmetro de entrada ou retorno que é usado para dar suporte a um número variável de parâmetros.

  • O manipulador não pode ser um método genérico por exemplo, IList<T> Sort<T>(entrada IList<T>).

  • Manipuladores assíncronos com assinaturas async void não são compatíveis.