Controlador de funciones de Lambda en C# - AWS Lambda

Controlador de funciones de Lambda en C#

El controlador de la función de Lambda es el método del código de la función que procesa eventos. Cuando se invoca una función, Lambda ejecuta el método del controlador. Si el controlador existe o devuelve una respuesta, pasará a estar disponible para gestionar otro evento.

Un controlador de función de Lambda se define como una instancia o un método estático en una clase. Para acceder al objeto de contexto Lambda, puede definir un parámetro de método de tipo ILambdaContext. Puede utilizar esta opción para acceder a información sobre la invocación actual, como el nombre de la función, el límite de memoria, el tiempo de ejecución restante y el registro.

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

En la sintaxis, tenga en cuenta lo siguiente:

  • InputType: el primer parámetro del controlador es la entrada del controlador. Esta entrada puede consistir en datos de eventos (publicados por un origen de eventos) o por una entrada personalizada que proporcione, como una cadena o cualquier objeto de datos personalizado.

  • returnType: si piensa invocar la función de Lambda de forma síncrona (utilizando el tipo de invocación RequestResponse), puede devolver la salida de la función utilizando cualquiera de los tipos de datos admitidos. Por ejemplo, si utiliza una función de Lambda como backend de aplicaciones móviles, la invocación se realiza de forma síncrona. El tipo de datos de salida se serializa en JSON.

    Si piensa invocar la función de Lambda de forma asíncrona (utilizando el tipo de invocación Event), returnType debería ser void. Por ejemplo, si utiliza Lambda con orígenes de eventos como Amazon Simple Storage Service (Amazon S3) o Amazon Simple Notification Service (Amazon SNS), estos orígenes de eventos invocan la función de Lambda mediante el tipo de invocación Event.

  • ILambdaContext context: el segundo argumento de la firma del controlador es opcional. Proporciona acceso al objeto context que tiene información sobre la función y la solicitud.

Control de flujos

De manera predeterminada, Lambda solo es compatible con el tipo System.IO.Stream como parámetro de entrada.

Por ejemplo, fíjese en el siguiente código de ejemplo de C#.

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

En el código C# de ejemplo, el primer parámetro del controlador es la entrada del controlador (MyHandler). Esta entrada puede estar formada por datos de eventos (publicados por un origen de eventos, como Simple Storage Service (Amazon S3)) o por una entrada personalizada que proporcione, como Stream o cualquier objeto de datos personalizado. La salida es de tipo Stream.

Control de tipos de datos estándar

Para todos los demás tipos, tendrá que especificar un serializador:

  • Tipos primitivos de .NET (como string o int)

  • Colecciones y mapas: IList, IEnumerable, IList<T>, Array, IDictionary, IDictionary<TKey, TValue>

  • Tipos POCO (objetos CLR estándar simples, o Plain old CLR objects)

  • Tipos de eventos predefinidos de AWS

  • Para las invocaciones asíncronas, Lambda ignora el tipo de retorno. El tipo de retorno se puede establecer en void en estos casos.

  • Si está utilizando la programación asíncrona de .NET, el tipo de retorno puede ser Task y Task<T>, y utilizar las palabras clave async y await. Para obtener más información, consulte Uso de async en funciones C# con Lambda.

A menos que los parámetros de entrada y salida de la función sean del tipo System.IO.Stream, debe serializarlos. Lambda proporciona un serializador predeterminado que se puede aplicar en el nivel de ensamblado o de método de la aplicación, o puede definir uno implementando la interfaz ILambdaSerializer proporcionada por la biblioteca Amazon.Lambda.Core. Para obtener más información, consulte Implementar funciones de Lambda C# con archivos de archivo .zip.

Para añadir el atributo serializador predeterminado a un método, primero añada una dependencia de Amazon.Lambda.Serialization.SystemTextJson en el archivo .csproj.

<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles> <AWSProjectType>Lambda</AWSProjectType> <!-- Makes the build directory similar to a publish directory and helps the AWS .NET Lambda Mock Test Tool find project dependencies. --> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <!-- Generate ready to run images during publishing to improve cold start time. --> <PublishReadyToRun>true</PublishReadyToRun> </PropertyGroup> <ItemGroup> <PackageReference Include="Amazon.Lambda.Core" Version="2.1.0 " /> <PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.2.0" /> </ItemGroup> </Project>

El ejemplo siguiente ilustra la flexibilidad que puede obtenerse especificando el serializador predeterminado de System.Text.Json en un método y otro elegido por el usuario en otro método:

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

Generación de fuentes para serialización JSON

C# 9 proporciona generadores de origen que permiten la generación de código durante la compilación. A partir de .NET 6, la biblioteca JSON nativa System.Text.Json puede utilizar generadores de orígenes, de modo que permite el análisis JSON sin necesidad de API de reflejo. Esto puede ayudar a mejorar el rendimiento del arranque en frío.

Para utilizar el generador de orígenes
  1. En el proyecto, defina una clase vacía y parcial que se derive de System.Text.Json.Serialization.JsonSerializerContext.

  2. Agregue el atributo de JsonSerializable para cada tipo .NET para el que el generador de origen debe generar código de serialización.

ejemplo Integración de API Gateway que aprovecha la generación de orígenes
using System.Collections.Generic; using System.Net; using System.Text.Json.Serialization; using Amazon.Lambda.Core; using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.Serialization.SystemTextJson; [assembly: LambdaSerializer(typeof(SourceGeneratorLambdaJsonSerializer<SourceGeneratorExa mple.HttpApiJsonSerializerContext>))] namespace SourceGeneratorExample; [JsonSerializable(typeof(APIGatewayHttpApiV2ProxyRequest))] [JsonSerializable(typeof(APIGatewayHttpApiV2ProxyResponse))] public partial class HttpApiJsonSerializerContext : JsonSerializerContext { } public class Functions { public APIGatewayProxyResponse Get(APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context) { context.Logger.LogInformation("Get Request"); var response = new APIGatewayHttpApiV2ProxyResponse { StatusCode = (int)HttpStatusCode.OK, Body = "Hello AWS Serverless", Headers = new Dictionary<string, string> { { "Content-Type", "text/plain" } } }; return response; } }

Cuando invoca la función, Lambda utiliza el código de serialización JSON generado por el origen para gestionar la serialización de los eventos y respuestas de Lambda.

Signaturas del controlador

Cuando cree funciones de Lambda, debe proporcionar una cadena de controlador que indique a Lambda dónde debe buscar el código que va a invocar. En C#, tiene este formato:

ENSAMBLADO::TIPO::MÉTODO, donde:

  • ENSAMBLADO es el nombre del archivo de ensamblado de .NET para la aplicación. Cuando utilice la CLI de .NET Core para compilar la aplicación, si no ha establecido el nombre del ensamblado con la propiedad AssemblyName en el archivo .csproj, el nombre de ENSAMBLADO será el nombre de la carpeta que contiene el archivo .csproj. Para obtener más información, consulte CLI de .NET Core. En este caso, supongamos que el archivo .csproj es HelloWorldApp.csproj.

  • TIPO es el nombre completo del tipo de controlador, que consta del EspacioDeNombres y del NombreDeLaClase. En este caso Example.Hello.

  • MÉTODO es el nombre del controlador de la función, en este caso, MyHandler.

En definitiva, la signatura tendrá este formato: Ensamblado::EspacioDeNombres.NombreDeLaClase::NombreDelMétodo

Considere el siguiente ejemplo:

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

La cadena del controlador sería: HelloWorldApp::Example.Hello::MyHandler.

importante

Si el método especificado en la cadena de controlador está sobrecargado, debe proporcionar la signatura exacta del método que Lambda debe invocar. Si la resolución requeriría seleccionar entre varias signaturas (sobrecargadas), Lambda rechazará una signatura que de otra forma sería válida.

Uso de sentencias de nivel superior

A partir de .NET 6, puede escribir funciones mediante declaraciones de nivel superior. Las instrucciones de nivel superior eliminan parte del código reutilizable requerido para los proyectos .NET, lo cual reduce el número de líneas de código que escribe. Por ejemplo, puede volver a escribir el ejemplo anterior utilizando instrucciones de nivel superior:

using Amazon.Lambda.RuntimeSupport; var handler = (Stream stream) => { //function logic }; await LambdaBootstrapBuilder.Create(handler).Build().RunAsync();

Al utilizar sentencias de nivel superior, solo incluye el nombre de ASSEMBLY al proporcionar la firma del controlador. Continuando con el ejemplo anterior, la cadena del controlador sería HelloWorldApp.

Al configurar el controlador en el nombre del ensamblaje, Lambda tratará el ensamblaje como un ejecutable y lo ejecutará al inicio. Debe agregar el paquete de NuGet Amazon.Lambda.RuntimeSupport al proyecto para que el ejecutable que se ejecuta al inicio pueda iniciar el cliente en el tiempo de ejecución de Lambda.

Serialización de las funciones de Lambda

Para las funciones de Lambda que utilizan tipos de entrada o salida distintos de un objeto Stream, debe agregar una biblioteca de serialización a la aplicación. Puede hacerlo de las siguientes maneras:

  • Utilice el paquete Amazon.Lambda.Serialization.SystemTextJson de NuGet. Esta biblioteca utiliza el serializador JSON de .NET Core nativo para gestionar la serialización. Este paquete proporciona una mejora de rendimiento sobre Amazon.Lambda.Serialization.Json, pero tenga en cuenta las limitaciones descritas en la documentación de Microsoft. Esta biblioteca está disponible para .NET Core 3.1 y en tiempos de ejecución posteriores.

  • Utilice el paquete Amazon.Lambda.Serialization.Json de NuGet. Esta biblioteca utiliza JSON.NET para gestionar la serialización.

  • Creando su propia biblioteca de serialización mediante la implementación de la interfaz ILambdaSerializer, que está disponible formando parte de la biblioteca Amazon.Lambda.Core. La interfaz define dos métodos:

    • T Deserialize<T>(Stream requestStream);

      Puede implementar este método para deserializar la carga de solicitud desde la API Invoke en el objeto que se pasa al controlador de la función de Lambda.

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

      Puede implementar este método para serializar el resultado que devuelve el controlador de la función de Lambda en la carga de respuesta que devuelve la API de operación Invoke.

Para utilizar el serializador, debe agregar una dependencia a su archivo MyProject.csproj.

... <ItemGroup> <PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.1.0" /> <!-- or --> <PackageReference Include="Amazon.Lambda.Serialization.Json" Version="2.0.0" /> </ItemGroup>

A continuación, deberá definir el serializador. El siguiente ejemplo define el serializador Amazon.Lambda.Serialization.SystemTextJson en el archivo AssemblyInfo.cs.

[assembly: LambdaSerializer(typeof(DefaultLambdaJsonSerializer))]

El siguiente ejemplo define el serializador Amazon.Lambda.Serialization.Json en el archivo AssemblyInfo.cs.

[assembly: LambdaSerializer(typeof(JsonSerializer))]

Puede definir un atributo de serialización personalizado en el nivel de método, que sustituye al serializador predeterminado especificado en el nivel de ensamblado.

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

Restricciones del controlador de funciones de Lambda

Tenga en cuenta que existen algunas restricciones que afectan a la firma del controlador.

  • No puede ser unsafe ni utilizar tipos de puntero en la signatura del controlador, aunque puede utilizar el contexto unsafe dentro del método del controlador y sus dependencias. Para obtener más información, consulte inseguro (Referencia de C#) en el sitio web de Microsoft Docs.

  • No puede pasar un número variable de parámetros utilizando la palabra clave params, ni utilizar ArgIterator como parámetro de entrada o de retorno que se utiliza para admitir un número variable de parámetros.

  • El controlador no puede ser un método genérico, por ejemplo, IList<T> Sort<T(IList<T> input).

  • No se admiten los controladores asíncronos con la signatura async void.

Uso de async en funciones C# con Lambda

Si sabe que la función de Lambda requerirá un proceso de larga duración, como cargar archivos grandes en Amazon Simple Storage Service (Amazon S3) o leer una gran cantidad de registros desde Amazon DynamoDB, puede utilizar el patrón async/await. Cuando se utiliza esta firma, Lambda invoca la función de forma síncrona y espera a que devuelva una respuesta o a que se agote el tiempo de espera de ejecución.

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

Si utiliza este patrón, tenga en cuenta lo siguiente:

  • Lambda no admite métodos async void.

  • Si crea una función de Lambda asíncrona sin implementar el operador await, .NET emitirá una advertencia de compilación y se producirá un comportamiento inesperado. Por ejemplo, algunas acciones asíncronas se ejecutarán, mientras que otras no. O algunas acciones asíncronas no se completarán antes de que finalice la invocación de la función.

    public async Task ProcessS3ImageResizeAsync(SimpleS3Event event) // Compiler warning { client.DoAsyncWork(input); }
  • La función de Lambda puede incluir varias llamadas asíncronas, que pueden invocarse en paralelo. Puede utilizar los métodos Task.WhenAll y Task.WhenAny para trabajar con varias tareas. Para utilizar el método Task.WhenAll, pase una lista de las operaciones en una matriz al método. En el ejemplo siguiente, tenga en cuenta que si olvida incluir cualquier operación en la matriz, la llamada puede volver antes de que finalice su operación.

    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 utilizar el método Task.WhenAny, también se pasa una lista de las operaciones en una matriz al método. La llamada vuelve tan pronto como finaliza la primera operación, incluso si las demás siguen ejecutándose.

    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 waiting for only one to finish. await Task.WhenAny(task1, task2, task3); }