AWS Lambda
Guia do desenvolvedor

Manipulador de função do AWS Lambda em C#

Quando você cria uma função do Lambda, você especifica um manipulador que o AWS Lambda pode invocar quando o serviço executa a função em seu nome.

Você define um manipulador de função do Lambda como uma instância ou método estático em uma classe. Se você deseja obter acesso ao objeto de contexto do Lambda, ele é disponibilizado através da definição de um método parâmetro do tipo ILambdaContext, uma interface que você pode usar para acessar informações sobre a execução atual, como o nome da função atual, limite de memória, tempo de execução restante e registros.

returnType handler-name(inputType input, ILambdaContext context) { ... }

Observe o seguinte em relação à sintaxe:

  • inputType – O primeiro parâmetro do manipulador é a entrada do manipulador, que pode ser os dados do evento (publicados por uma origem de evento) ou uma entrada personalizada fornecida por você, tal como uma string ou qualquer objeto de dados personalizado.

  • returnType – Se você planeja invocar a função do Lambda de forma síncrona (usando o tipo de invocação RequestResponse), pode fazer com que a saída de sua função retorne um dos tipos de dados compatíveis. Por exemplo, se você usar uma função do Lambda como um back-end de aplicativo móvel, estará invocando-a de forma síncrona. O tipo de dados de saída será serializado em JSON.

    Se você planeja invocar a função do Lambda de forma assíncrona (usando o tipo de invocação Event), o returnType deve ser void. Por exemplo, se você usar o AWS Lambda com origens de eventos como Amazon S3 ou Amazon SNS, essas origens de eventos invocarão a função do Lambda usando o tipo de invocação Event.

Tratamento de streams

Somente o tipo System.IO.Stream é suportado como um parâmetro de entrada por padrão.

Por exemplo, considere o seguinte exemplo de código em C#.

using System.IO; namespace Example { public class Hello { public Stream MyHandler(Stream stream) { //function logic } }

No exemplo de código em C#, o primeiro parâmetro do manipulador é a entrada do manipulador (MyHandler), que pode ser os dados do evento (publicados por uma origem de evento, como o Amazon S3) ou uma entrada personalizada fornecida por você, como um Stream (como neste exemplo) ou um objeto de dados personalizado qualquer. A saída é do tipo Stream.

Tratamento de tipos de dados padrão

Todos os outros tipos, como os listados abaixo, exigem que você especifique um serializador.

  • Tipos .NET primitivos (como string ou int).

  • Coleções e mapas - IList, IEnumerable, IList<T>, Array, IDictionary, IDictionary<TKey, TValue>

  • Tipos de POCO (objetos CLR básicos)

  • Tipos de eventos predefinidos da AWS

  • Para invocações assíncronas, o tipo de retorno será ignorado pelo Lambda. O tipo de retorno pode ser definido como nulo em tais casos.

  • Se você estiver usando programação assíncrona .NET, o retorno pode ser do tipo Task e Task<T> e usar as palavras-chave async e await. Para obter mais informações, consulte Uso de async em funções em C# com o AWS Lambda.

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. O AWS Lambda fornece um serializador padrão que pode ser aplicado no nível da montagem ou método de seu aplicativo ou você pode definir o seu próprio, implementando a interface ILambdaSerializer fornecida pela biblioteca Amazon.Lambda.Core. Para obter mais informações, consulte Pacote de implantação do AWS Lambda em C#.

Para adicionar o atributo de serializador padrão à um método, adicione primeiro uma dependência de Amazon.Lambda.Serialization.Json em seu arquivo project.json.

{ "version": "1.0.0-*", "dependencies":{ "Microsoft.NETCore.App": { "type": "platform", "version": "1.0.1" }, "Amazon.Lambda.Serialization.Json": "1.3.0" }, "frameworks": { "netcoreapp1.0": { "imports": "dnxcore50" } } }

O exemplo abaixo ilustra a flexibilidade que você pode aproveitar especificando o serializador padrão Json.NET em um método e outro de sua escolha em um método diferente:

public class ProductService{ [LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] public Product DescribeProduct(DescribeProductRequest request) { return catalogService.DescribeProduct(request.Id); } [LambdaSerializer(typeof(MyJsonSerializer))] public Customer DescribeCustomer(DescribeCustomerRequest request) { return customerService.DescribeCustomer(request.Id); } }

Assinaturas do manipulador

Ao criar funções do Lambda, você precisa fornecer uma string de manipulador que diz ao AWS Lambda onde procurar o código a invocar. Em C#, o formato é:

ASSEMBLY::TYPE::METHOD onde:

  • ASSEMBLY é o nome do arquivo de montagem .NET para o seu aplicativo. Ao usar a CLI do .NET Core para criar seu aplicativo, se você não definiu o nome da montagem usando a configuração buildOptions.outputName em project.json, o nome ASSEMBLY será o nome da pasta que contém seu arquivo project.json. Para obter mais informações, consulte CLI do .NET Core. Neste caso, suponhamos que o nome da pasta seja HelloWorldApp.

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

  • METHOD é o nome do manipulador da função, neste caso MyHandler.

Por fim, a assinatura será neste formato: Assembly::Namespace.ClassName::MethodName

Mais uma vez, considere o seguinte exemplo:

using System.IO; namespace Example { public class Hello { public Stream MyHandler(Stream stream) { //function logic } }

A string do manipulador seria: HelloWorldApp::Example.Hello::MyHandler

Importante

Se o método especificado em sua string do manipulador estiver sobrecarregado, você deve fornecer a assinatura exata do método que o Lambda deve chamar. O AWS Lambda rejeitará uma assinatura considerada válida se a resolução exigir a seleção entre várias assinaturas (sobrecarregadas).

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

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

  • Ela não pode ser unsafe e usar tipos de ponteiro na assinatura do manipulador, embora o contexto unsafe possa ser usado dentro do método do manipulador e suas dependências. Para obter mais informações, consulte não seguro (Referência de C#).

  • Ela 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 ao 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.

Uso de async em funções em C# com o AWS Lambda

Se você souber que sua função do Lambda exigirá um processo de longa duração, tal como o upload de grandes arquivos no Amazon S3 ou a leitura de um grande fluxo de registros do DynamoDB, você pode aproveitar a vantagem do padrão async/await. Quando você usa essa assinatura, o Lambda executa a função de forma síncrona e aguarda a função retornar uma resposta ou aguarda que a execução expire.

public async Task<Response> ProcessS3ImageResizeAsync(SimpleS3Event input) { var response = await client.DoAsyncWork(input); return response; }

Se você usar esse padrão, há algumas considerações que você deve levar em conta:

  • O AWS Lambda não oferece suporte a métodos async void.

  • Se você criar uma função do Lambda assíncrona sem implementar o operador await, o .NET emitirá um aviso do compilador e você vai observar um comportamento inesperado. Por exemplo, algumas ações assíncronas serão executadas enquanto outras não. Ou algumas ações assíncronas não serão concluídas antes que execução da função esteja completa.

    public async Task ProcessS3ImageResizeAsync(SimpleS3Event event) // Compiler warning { client.DoAsyncWork(input); }
  • Sua função do Lambda pode incluir várias chamadas assíncronas que podem ser invocadas em paralelo. Você pode usar os métodos Task.WhenAll e Task.WhenAny para trabalhar com múltiplas tarefas. Para usar o método Task.WhenAll, a lista de operações é passada ao método como uma matriz. Observe que, no exemplo abaixo, se você não incluir qualquer operação na matriz, essa chamada pode retornar antes da conclusão de sua operação.

    public async Task DoesNotWaitForAllTasks1() { // In Lambda, Console.WriteLine goes to CloudWatch Logs. var task1 = Task.Run(() => Console.WriteLine("Test1")); var task2 = Task.Run(() => Console.WriteLine("Test2")); var task3 = Task.Run(() => Console.WriteLine("Test3")); // Lambda may return before printing "Test2" since we never wait on task2. await Task.WhenAll(task1, task3); }

    Para usar o método Task.WhenAny, você passa novamente uma lista de operações ao método como uma matriz. A chamada retorna assim que a primeira operação é concluída, mesmo se a outras ainda estiverem em execução.

    public async Task DoesNotWaitForAllTasks2() { // In Lambda, Console.WriteLine goes to CloudWatch Logs. var task1 = Task.Run(() => Console.WriteLine("Test1")); var task2 = Task.Run(() => Console.WriteLine("Test2")); var task3 = Task.Run(() => Console.WriteLine("Test3")); // Lambda may return before printing all tests since we're only waiting for one to finish. await Task.WhenAny(task1, task2, task3); }