Definieren des Lambda-Funktions-Handlers in C# - AWS Lambda

Definieren des Lambda-Funktions-Handlers in C#

Der Lambda-Funktionshandler ist die Methode in Ihrem Funktionscode, die Ereignisse verarbeitet. Wenn Ihre Funktion aufgerufen wird, führt Lambda die Handler-Methode aus. Ihre Funktion wird so lange ausgeführt, bis der Handler eine Antwort zurückgibt, beendet wird oder ein Timeout auftritt.

Auf dieser Seite wird beschrieben, wie Sie Lambda-Funktionshandler in C# verwenden, um mit der verwalteten .NET-Laufzeit zu arbeiten, einschließlich der Optionen für Projekteinrichtung, Benennungskonventionen und Best Practices. Diese Seite enthält auch ein Beispiel für eine C#-Lambda-Funktion, die Informationen über eine Bestellung aufnimmt, eine Textdatei als Beleg erstellt und diese Datei in einen Bucket in Amazon Simple Storage Service (S3) stellt. Informationen darüber, wie Sie Ihre Funktion nach dem Schreiben einsetzen können, finden Sie unter Erstellen und Bereitstellen von C#-Lambda-Funktionen mit ZIP-Dateiarchiven oder Bereitstellen von .NET-Lambda-Funktionen mit Container-Images.

Einrichten Ihres C#-Handler-Projekts

Wenn Sie mit Lambda-Funktionen in C# arbeiten, umfasst der Prozess das Schreiben Ihres Codes und das anschließende Bereitstellen Ihres Codes in Lambda. Es gibt zwei verschiedene Ausführungsmodelle für die Bereitstellung von Lambda-Funktionen in .NET: den Klassenbibliotheksansatz und den Ansatz für ausführbare Assemblys.

Beim Klassenbibliotheksansatz packen Sie Ihren Funktionscode als .NET-Assembly (.dll) und stellen ihn mit der verwalteten .NET-Laufzeit (dotnet8) in Lambda bereit. Für den Namen des Handlers erwartet Lambda eine Zeichenfolge im Format AssemblyName::Namespace.Classname::Methodname. Während der Initialisierungsphase der Funktion wird die Klasse Ihrer Funktion initialisiert und der gesamte Code im Konstruktor wird ausgeführt.

Beim Ansatz für ausführbare Assemblys verwenden Sie das Feature für Anweisungen der obersten Ebene, die erstmals in C# 9 eingeführt wurde. Dieser Ansatz generiert eine ausführbare Assembly, die Lambda immer dann ausführt, wenn sie einen Aufrufbefehl für Ihre Funktion empfängt. Bei diesem Ansatz verwenden Sie außerdem die verwaltete .NET-Laufzeit (dotnet8). Für den Namen des Handlers geben Sie in Lambda den Namen der ausführbaren Assembly an, die ausgeführt werden soll.

Das Hauptbeispiel auf dieser Seite veranschaulicht den Klassenbibliotheksansatz. Sie können Ihr C#-Lambda-Projekt auf verschiedene Arten initialisieren. Am einfachsten ist es jedoch, die .NET-CLI mit der Amazon.Lambda.Tools-CLI zu verwenden. Richten Sie die Amazon.Lambda.Tools-CLI ein, indem Sie die Schritte unter Einrichten der .NET-Entwicklungsumgebung befolgen. Initialisieren Sie das Projekt dann mit dem folgenden Befehl:

dotnet new lambda.EmptyFunction --name ExampleCS

Dieser Befehl generiert die folgende Dateistruktur:

/project-root └ src └ ExampleCS └ Function.cs (contains main handler) └ Readme.md └ aws-lambda-tools-defaults.json └ ExampleCS.csproj └ test └ ExampleCS.Tests └ FunctionTest.cs (contains main handler) └ ExampleCS.Tests.csproj

In dieser Dateistruktur befindet sich die Hauptlogik des Handlers für Ihre Funktion in der Datei Function.cs.

Beispiel für den C#-Lambda-Funktionscode

Das folgende Beispiel für einen C#-Lambda-Funktionscode nimmt Informationen über eine Bestellung auf, erstellt eine Textdatei als Beleg und platziert diese Datei in einem Amazon-S3-Bucket.

Beispiel Function.cs-Lambda-Funktion
using System; using System.Text; using System.Threading.Tasks; using Amazon.Lambda.Core; using Amazon.S3; using Amazon.S3.Model; // Assembly attribute to enable Lambda function logging [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] namespace ExampleLambda; public class Order { public string OrderId { get; set; } = string.Empty; public double Amount { get; set; } public string Item { get; set; } = string.Empty; } public class OrderHandler { private static readonly AmazonS3Client s3Client = new(); public async Task<string> HandleRequest(Order order, ILambdaContext context) { try { string? bucketName = Environment.GetEnvironmentVariable("RECEIPT_BUCKET"); if (string.IsNullOrWhiteSpace(bucketName)) { throw new ArgumentException("RECEIPT_BUCKET environment variable is not set"); } string receiptContent = $"OrderID: {order.OrderId}\nAmount: ${order.Amount:F2}\nItem: {order.Item}"; string key = $"receipts/{order.OrderId}.txt"; await UploadReceiptToS3(bucketName, key, receiptContent); context.Logger.LogInformation($"Successfully processed order {order.OrderId} and stored receipt in S3 bucket {bucketName}"); return "Success"; } catch (Exception ex) { context.Logger.LogError($"Failed to process order: {ex.Message}"); throw; } } private async Task UploadReceiptToS3(string bucketName, string key, string receiptContent) { try { var putRequest = new PutObjectRequest { BucketName = bucketName, Key = key, ContentBody = receiptContent, ContentType = "text/plain" }; await s3Client.PutObjectAsync(putRequest); } catch (AmazonS3Exception ex) { throw new Exception($"Failed to upload receipt to S3: {ex.Message}", ex); } } }

Diese Function.cs-Datei enthält die folgenden Abschnitte des Codes:

  • using-Anweisungen: Damit können Sie C#-Klassen importieren, die für Ihre Lambda-Funktion erforderlich sind.

  • [assembly: LambdaSerializer(...)]: LambdaSerializer ist ein Assembly-Attribut, das Lambda anweist, JSON-Ereignisnutzdaten automatisch in C#-Objekte zu konvertieren, bevor sie an Ihre Funktion übergeben werden.

  • namespace ExampleLambda: Dies definiert den Namespace. In C# muss der Namespace-Name nicht mit dem Dateinamen übereinstimmen.

  • public class Order {...}: Hiermit wird die Form des erwarteten Eingabeereignisses definiert.

  • public class OrderHandler {...}: Hiermit wird die C#-Klasse definiert. Darin definieren Sie die Haupt-Handler-Methode und alle anderen Hilfsmethoden.

  • private static readonly AmazonS3Client s3Client = new();: Dadurch wird ein Amazon-S3-Client mit der standardmäßigen Anbieterkette von Anmeldeinformationen außerhalb der Haupt-Handler-Methode initialisiert. Dies veranlasst Lambda, diesen Code während der Initialisierungsphase auszuführen.

  • public async ... HandleRequest (Order order, ILambdaContext context): Dies ist die Haupthandler-Methode, die Ihre Hauptanwendungslogik enthält.

  • private async Task UploadReceiptToS3(...) {}: Dies ist eine Hilfsmethode, auf die von der handleRequest-Haupthandler-Methode verwiesen wird.

Da für diese Funktion ein SDK-Client für Amazon S3 erforderlich ist, müssen Sie sie zu den Abhängigkeiten Ihres Projekts hinzufügen. Dazu können Sie zu src/ExampleCS navigieren und den folgenden Befehl ausführen:

dotnet add package AWSSDK.S3

Standardmäßig enthält die generierte Datei aws-lambda-tools-defaults.json keine profile- oder region-Informationen für Ihre Funktion. Aktualisieren Sie außerdem die function-handler-Zeichenfolge auf den richtigen Wert (ExampleCS::ExampleLambda.OrderHandler::HandleRequest). Sie können diese Aktualisierung manuell vornehmen und die erforderlichen Metadaten hinzufügen, um ein bestimmtes Anmeldeinformationsprofil und eine bestimmte Region für Ihre Funktion zu verwenden. Die aws-lambda-tools-defaults.json-Datei sollte etwa wie folgt aussehen:

{ "Information": [ "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", "dotnet lambda help", "All the command line options for the Lambda command can be specified in this file." ], "profile": "default", "region": "us-east-1", "configuration": "Release", "function-architecture": "x86_64", "function-runtime": "dotnet8", "function-memory-size": 512, "function-timeout": 30, "function-handler": "ExampleCS::ExampleLambda.OrderHandler::HandleRequest" }

Damit diese Funktion ordnungsgemäß funktioniert, muss ihre Ausführungsrolle die s3:PutObject-Aktion zulassen. Stellen Sie außerdem sicher, dass Sie die RECEIPT_BUCKET-Umgebungsvariable definieren. Nach einem erfolgreichen Aufruf sollte der Amazon-S3-Bucket eine Empfangsdatei enthalten.

Handler für Klassenbibliotheken

Der Hauptbeispielcode auf dieser Seite veranschaulicht den Klassenbibliotheks-Handler. Klassenbibliotheks-Handler haben die folgende Struktur:

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] namespace NAMESPACE; ... public class CLASSNAME { public async Task<string> METHODNAME (...) { ... } }

Wenn Sie eine Lambda-Funktion erstellen, müssen Sie Lambda Informationen über den Handler Ihrer Funktion in Form einer Zeichenfolge im Feld Handler zur Verfügung stellen. Dadurch wird Lambda mitgeteilt, welche Methode in Ihrem Code ausgeführt werden soll, wenn Ihre Funktion aufgerufen wird. In C# lautet das Format der Handler-Zeichenfolge für Klassenbibliotheks-Handler ASSEMBLY::TYPE::METHOD. Dabei gilt Folgendes:

  • ASSEMBLY ist der Name der .NET-Assembly-Datei für Ihre Anwendung. Wenn Sie die Amazon.Lambda.Tools-CLI zur Erstellung Ihrer Anwendung verwenden und den Assembly-Namen nicht über die Eigenschaft AssemblyName in der .csproj-Datei festlegen, ist ASSEMBLY einfach der Name Ihrer .csproj-Datei.

  • TYPE ist der vollständige Name des Handler-Typs, der NAMESPACE.CLASSNAME lautet.

  • METHOD ist der Name der Haupt-Handler-Methode in Ihrem Code. Er lautet METHODNAME.

Wenn die Assembly im Hauptbeispielcode auf dieser Seite den Namen ExampleCS trägt, lautet die vollständige Zeichenfolge für den Handler ExampleCS::ExampleLambda.OrderHandler::HandleRequest.

Ausführbare Assembly-Handler

Sie können Lambda-Funktionen in C# auch als ausführbare Assembly definieren. Handler für ausführbare Assemblys nutzen das C#-Feature für Anweisungen der obersten Ebene, bei der der Compiler die Main()-Methode generiert und Ihren Funktionscode darin platziert. Bei der Verwendung ausführbarer Assemblys muss die Lambda-Laufzeit gebootet werden. Verwenden Sie dazu die LambdaBootstrapBuilder.Create-Methode in Ihrem Code. Die Eingaben für diese Methode stellen die Haupt-Handler-Funktion sowie den Lambda-Serialisierer dar, die verwendet werden sollen. Im Anschluss sehen Sie ein Beispiel für einen Handler für ausführbare Assemblys in C#:

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 = apigProxyEvent.PathParameters["id"]; var databaseRecord = await this.repo.GetById(id); return new APIGatewayProxyResponse { StatusCode = (int)HttpStatusCode.OK, Body = JsonSerializer.Serialize(databaseRecord) }; };

Im Feld Handler für Handler für ausführbare Assemblys ist die Handler-Zeichenfolge, die Lambda mitteilt, wie Ihr Code ausgeführt werden soll, der Name der Assembly. In diesem Beispiel ist dies GetProductHandler.

Gültige Handler-Signaturen für C#-Funktionen

In C# können gültige Lambda-Handler-Signaturen zwischen 0 und 2 Argumente enthalten. Normalerweise hat Ihre Handler-Signatur zwei Argumente, wie im Hauptbeispiel gezeigt:

public async Task<string> HandleRequest(Order order, ILambdaContext context)

Wenn Sie zwei Argumente angeben, muss das erste Argument die Ereigniseingabe und das zweite Argument das Lambda-Kontextobjekt sein. Beide Argumente sind optional. Die folgenden sind beispielsweise ebenfalls gültige Lambda-Handler-Signaturen in C#:

  • public async Task<string> HandleRequest()

  • public async Task<string> HandleRequest(Order order)

  • public async Task<string> HandleRequest(ILambdaContext context)

Neben der Basissyntax der Handler-Signatur gibt es einige zusätzliche Einschränkungen:

  • Sie können das Schlüsselwort unsafe nicht in der Handler-Signatur verwenden. Sie können jedoch den unsafe-Kontext innerhalb der Handler-Methode und ihrer Abhängigkeiten verwenden. Weitere Informationen finden Sie unter unsafe (C#-Referenz) auf der Microsoft-Dokumentationswebsite.

  • Der Handler darf das Schlüsselwort params nicht verwenden oder ArgIterator als Eingabe- oder Rückgabeparameter verwenden. Diese Schlüsselwörter unterstützen eine variable Anzahl von Parametern. Ihr Handler kann maximal zwei Argumente akzeptieren.

  • Der Handler ist möglicherweise keine generische Methode. Mit anderen Worten: Er kann keine generischen Typparameter wie <T> verwenden.

  • Lambda unterstützt keine asynchronen Handler mit async void in der Signatur.

Namenskonventionen für Handler

Lambda-Handler in C# haben keine strengen Benennungsbeschränkungen. Sie müssen jedoch sicherstellen, dass Sie Lambda bei der Bereitstellung Ihrer Funktion die richtige Handler-Zeichenfolge zur Verfügung stellen. Die richtige Handler-Zeichenfolge hängt davon ab, ob Sie einen Klassenbibliotheks-Handler oder einen Handler für ausführbare Assemblys bereitstellen.

Sie können zwar einen beliebigen Namen für Ihren Handler verwenden, Funktionsnamen in C# werden aber im Allgemeinen in PascalCase angegeben. Auch wenn der Dateiname nicht mit dem Klassennamen oder dem Handler-Namen übereinstimmen muss, ist es im Allgemeinen eine bewährte Methode, einen Dateinamen wie OrderHandler.cs zu verwenden, wenn der Klassenname OrderHandler lautet. Sie können den Dateinamen in diesem Beispiel beispielsweise von Function.cs in OrderHandler.cs ändern.

Serialisierung in C#-Lambda-Funktionen

JSON ist das gebräuchlichste und standardmäßigste Eingabeformat für Lambda-Funktionen. In diesem Beispiel erwartet die Funktion eine Eingabe ähnlich der folgenden:

{ "orderId": "12345", "amount": 199.99, "item": "Wireless Headphones" }

In C# können Sie die Form des erwarteten Eingabeereignisses in einer Klasse definieren. In diesem Beispiel definieren wir die Order-Klasse, um diese Eingabe zu modellieren:

public class Order { public string OrderId { get; set; } = string.Empty; public double Amount { get; set; } public string Item { get; set; } = string.Empty; }

Wenn Ihre Lambda-Funktion andere Eingabe- oder Ausgabetypen als ein Stream-Objekt verwendet, müssen Sie eine Serialisierungsbibliothek zu Ihrer Anwendung hinzufügen. Auf diese Weise können Sie die JSON-Eingabe in eine Instanz der von Ihnen definierten Klasse konvertieren. Es gibt zwei Methoden der Serialisierung für C#-Funktionen in Lambda: reflexionsbasierte Serialisierung und quellgenerierte Serialisierung.

Reflexionsbasierte Serialisierung

AWS bietet vorgefertigte Bibliotheken, die Sie schnell Ihrer Anwendung hinzufügen können. Diese Bibliotheken implementieren Serialisierung mithilfe von Reflexion. Verwenden Sie eins der folgenden Pakete, um die reflexionsbasierte Serialisierung zu implementieren:

  • Amazon.Lambda.Serialization.SystemTextJson: Im Backend nutzt dieses Paket System.Text.Json zur Ausführung von Serialisierungsaufgaben.

  • Amazon.Lambda.Serialization.Json: Im Backend nutzt dieses Paket Newtonsoft.Json zur Ausführung von Serialisierungsaufgaben.

Sie können auch Ihre eigene Serialisierungsbibliothek erstellen, indem Sie die ILambdaSerializer-Schnittstelle implementieren; diese ist als Teil der Amazon.Lambda.Core-Bibliothek verfügbar. Diese Schnittstelle definiert zwei Methoden:

  • T Deserialize<T>(Stream requestStream);

    Sie implementieren diese Methode für die Deserialisierung der Anfragenutzlast von der Invoke-API in das Objekt, das Ihrem Lambda-Funktions-Handler übergeben wird.

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

    Sie implementieren diese Methode, um das von Ihrem Lambda-Funktions-Handler zurückgegebene Ergebnis in die Antwort-Nutzlast zu serialisieren, die der Invoke-API-Vorgang zurückgibt.

Das Hauptbeispiel auf dieser Seite verwendet reflexionsbasierte Serialisierung. Die reflexionsbasierte Serialisierung ist bei AWS Lambda sofort einsatzbereit und erfordert keine zusätzliche Einrichtung, weshalb sie aus Gründen der Einfachheit eine gute Wahl ist. Es ist jedoch mehr Funktionsspeicher erforderlich. Aufgrund von Laufzeitreflexionen kann es auch zu höheren Funktionslatenzen kommen.

Quellgenerierte Serialisierung

Bei der quellgenerierten Serialisierung wird der Serialisierungscode zur Kompilierzeit generiert. Dies macht Reflexion überflüssig und kann die Leistung Ihrer Funktion verbessern. Gehen Sie wie folgt vor, um die quellgenerierte Serialisierung in Ihrer Funktion zu verwenden:

  • Erstellen Sie eine neue Teilklasse, die von JsonSerializerContext erbt, und fügen Sie JsonSerializable-Attribute für alle Typen hinzu, die serialisiert oder deserialisiert werden müssen.

  • Konfigurieren Sie das LambdaSerializer so, dass es ein SourceGeneratorLambdaJsonSerializer<T> verwendet.

  • Aktualisieren Sie alle manuellen Serialisierungen und Deserialisierungen in Ihrem Anwendungscode, um die neu erstellte Klasse zu verwenden.

Das folgende Beispiel zeigt, wie Sie das Hauptbeispiel auf dieser Seite, das reflexionsbasierte Serialisierung verwendet, so ändern können, dass stattdessen die quellgenerierte Serialisierung verwendet wird.

using System.Text.Json; using System.Text.Json.Serialization; ... public class Order { public string OrderId { get; set; } = string.Empty; public double Amount { get; set; } public string Item { get; set; } = string.Empty; } [JsonSerializable(typeof(Order))] public partial class OrderJsonContext : JsonSerializerContext {} public class OrderHandler { ... public async Task<string> HandleRequest(string input, ILambdaContext context) { var order = JsonSerializer.Deserialize(input, OrderJsonContext.Default.Order); ... } }

Für die quellgenerierte Serialisierung sind mehr Einrichtungsschritte erforderlich als für die reflexionsbasierte Serialisierung. Funktionen, die quellgenerierte Serialisierung verwenden, verbrauchen jedoch tendenziell weniger Speicher und weisen aufgrund der Codegenerierung während der Kompilierung eine bessere Leistung auf. Um Kaltstarts von Funktionen zu vermeiden, sollten Sie in Erwägung ziehen, zur quellgenerierten Serialisierung zu wechseln.

Anmerkung

Wenn Sie die native Ahead-of-Time-Kompilierung (AOT) mit Lambda verwenden möchten, müssen Sie die quellgenerierte Serialisierung verwenden.

Zugreifen auf und Verwenden des Lambda-Kontextobjekts

Das Lambda-Kontextobjekt enthält Informationen über Aufruf, Funktion und Ausführungsumgebung. In diesem Beispiel ist das Kontextobjekt vom Typ Amazon.Lambda.Core.ILambdaContext und das zweite Argument der Haupthandlerfunktion.

public async Task<string> HandleRequest(Order order, ILambdaContext context) { ... }

Das Kontextobjekt ist eine optionale Eingabe. Weitere Informationen zu gültigen akzeptierten Handlersignaturen finden Sie unter Gültige Handler-Signaturen für C#-Funktionen.

Das Kontextobjekt ist nützlich für die Erstellung von Funktionsprotokollen für Amazon CloudWatch. Sie können die context.getLogger()-Methode verwenden, um ein LambdaLogger-Objekt für die Protokollierung abzurufen. In diesem Beispiel können wir den Logger verwenden, um eine Fehlermeldung zu protokollieren, falls bei der Verarbeitung aus irgendeinem Grund ein Fehler auftritt:

context.Logger.LogError($"Failed to process order: {ex.Message}");

Außerhalb der Protokollierung können Sie das Kontextobjekt auch für die Funktionsüberwachung verwenden. Weitere Informationen über das Kontextobjekt finden Sie unter Verwenden des Lambda-Kontextobjekts zum Abrufen von C#-Funktionsinformationen.

Verwenden Sie die SDK für .NET v3 in Ihrem Handler

Oft verwenden Sie Lambda-Funktionen, um mit anderen AWS-Ressourcen zu interagieren oder diese zu aktualisieren. Die einfachste Art, eine Schnittstelle zu diesen Ressourcen herzustellen, ist die Verwendung von SDK für .NET v3.

Anmerkung

Das SDK für .NET (v2) ist veraltet. Wir empfehlen, dass Sie nur noch das SDK für .NET v3 verwenden.

Mit dem folgenden Amazon.Lambda.Tools-Befehl können Sie Ihrem Projekt SDK-Abhängigkeiten hinzufügen:

dotnet add package <package_name>

Im Hauptbeispiel auf dieser Seite müssen wir beispielsweise die Amazon-S3-API verwenden, um einen Beleg in S3 hochzuladen. Wir können den SDK-Client für Amazon S3 mit dem folgenden Befehl importieren:

dotnet add package AWSSDK.S3

Dieser Befehl fügt die Abhängigkeit zu Ihrem Projekt hinzu. In der .csproj-Datei Ihres Projekts sollte außerdem eine Zeile ähnlich der folgenden angezeigt werden:

<PackageReference Include="AWSSDK.S3" Version="3.7.2.18" />

Importieren Sie dann die Abhängigkeiten direkt in Ihren C#-Code:

using Amazon.S3; using Amazon.S3.Model;

Der Beispielcode initialisiert dann wie folgt einen Amazon-S3-Client (unter Verwendung der standardmäßigen Anbieterkette für Anmeldeinformationen):

private static readonly AmazonS3Client s3Client = new();

In diesem Beispiel haben wir unseren Amazon-S3-Client außerhalb der Haupt-Handler-Funktion initialisiert, um zu vermeiden, dass wir ihn bei jedem Aufruf unserer Funktion initialisieren müssen. Nachdem Sie Ihren SDK-Client initialisiert haben, können Sie ihn für die Interaktion mit anderen AWS-Services verwenden. Der Beispielcode ruft die Amazon S3 PutObject-API wie folgt auf:

var putRequest = new PutObjectRequest { BucketName = bucketName, Key = key, ContentBody = receiptContent, ContentType = "text/plain" }; await s3Client.PutObjectAsync(putRequest);

Zugriff auf Umgebungsvariablen

In Ihrem Handler-Code können Sie mithilfe der System.Environment.GetEnvironmentVariable-Methode auf beliebige Umgebungsvariablen verweisen. In diesem Beispiel verweisen wir mit den folgenden Codezeilen auf die definierte RECEIPT_BUCKET-Umgebungsvariable:

string? bucketName = Environment.GetEnvironmentVariable("RECEIPT_BUCKET"); if (string.IsNullOrWhiteSpace(bucketName)) { throw new ArgumentException("RECEIPT_BUCKET environment variable is not set"); }

Verwenden des globalen Zustands

Lambda führt Ihren statischen Code und den Klassenkonstruktor während der Initialisierungsphase aus, bevor Ihre Funktion zum ersten Mal aufgerufen wird. Ressourcen, die während der Initialisierung erstellt werden, bleiben zwischen Aufrufen im Speicher, sodass Sie sie nicht bei jedem Aufruf Ihrer Funktion neu erstellen müssen.

Im Beispielcode befindet sich der Initialisierungscode des S3-Clients außerhalb der Haupt-Handler-Methode. Die Laufzeit initialisiert den Client, bevor die Funktion ihr erstes Ereignis verarbeitet, was zu längeren Verarbeitungszeiten führen kann. Nachfolgende Ereignisse sind viel schneller, da Lambda den Client nicht erneut initialisieren muss.

Vereinfachen Sie den Funktionscode mit dem Lambda Annotations Framework

Lambda Annotations ist ein Framework für .NET 8, das das Schreiben von Lambda-Funktionen mit C# vereinfacht. Das Annotations-Framework verwendet Quellgeneratoren, um Code zu generieren, der vom Lambda-Programmiermodell in den vereinfachten Code übersetzt wird. Mit dem Annotations-Framework können Sie einen Großteil des Codes in einer Lambda-Funktion ersetzen, die mit dem regulären Programmiermodell geschrieben wurde. Code, der mit dem Framework geschrieben wurde, verwendet einfachere Ausdrücke, sodass Sie sich auf Ihre Geschäftslogik konzentrieren können. Beispiele finden Sie unter Amazon.Lambda.Annotations in der NuGet-Dokumentation.

Ein Beispiel für eine vollständige Anwendung, die Lambda Annotations verwendet, finden Sie im PhotoAssetManager-Beispiel im GitHub-Repository awsdocs/aws-doc-sdk-examples. Die Hauptdatei Function.cs im Verzeichnis PamApiAnnotations verwendet Lambda Annotations. Zum Vergleich: Das Verzeichnis PamApi enthält äquivalente Dateien, die mit dem regulären Lambda-Programmiermodell geschrieben wurden.

Abhängigkeitsinjektion mit dem Lambda Annotations-Framework

Sie können auch das Lambda Annotations-Framework verwenden, um Ihren Lambda-Funktionen eine Dependency Injection hinzuzufügen, indem Sie die Syntax verwenden, mit der Sie vertraut sind. Wenn Sie einer [LambdaStartup]-Datei ein Startup.cs-Attribut hinzufügen, generiert das Lambda Annotations-Framework den erforderlichen Code zur Kompilierzeit.

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

Ihre Lambda-Funktion kann Dienste entweder durch Konstruktorinjektion oder durch Injektion in einzelne Methoden unter Verwendung des [FromServices]-Attributs injizieren.

[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); } }

Bewährte Codemethoden für C#-Lambda-Funktionen

Halten Sie sich an die Richtlinien in der folgenden Liste, um beim Erstellen Ihrer Lambda-Funktionen die besten Codierungspraktiken anzuwenden:

  • Trennen Sie den Lambda-Handler von Ihrer Core-Logik. Auf diese Weise können Sie eine Funktion zur besseren Prüfbarkeit von Einheiten schaffen.

  • Kontrollieren Sie die Abhängigkeiten im Bereitstellungspaket Ihrer Funktion. Die AWS Lambda-Ausführungsumgebung enthält eine Reihe von Bibliotheken. Um die neuesten Funktionen und Sicherheitsupdates zu aktivieren, wird Lambda diese Bibliotheken regelmäßig aktualisieren. Diese Updates können das Verhalten Ihrer Lambda-Funktion geringfügig verändern. Um die Abhängigkeiten, die Ihre Funktion verwendet, vollständig zu kontrollieren, empfehlen wir, alle Abhängigkeiten mit Ihrem Bereitstellungspaket zu bündeln.

  • Minimieren Sie die Komplexität Ihrer Abhängigkeiten. Ziehen Sie einfachere Frameworks vor, die sich schnell beim Start der Ausführungsumgebung laden lassen.

  • Minimieren Sie die Größe Ihres Bereitstellungspakets auf die für die Laufzeit erforderliche Größe. Dadurch verkürzt sich die Zeit, die für das Herunterladen und Entpacken Ihres Bereitstellungspakets vor dem Aufruf benötigt wird. Für Funktionen, die in .NET erstellt wurden, sollten Sie das Hochladen der gesamten AWS-SDK-Bibliothek als Teil Ihres Bereitstellungspakets vermeiden. Stattdessen sollten Sie selektiv von den Modulen ausgehen, die Komponenten des SDK aufnehmen, die Sie benötigen (z. B. DynamoDB, Amazon-S3-SDK-Module und Lambda-Kernbibliotheken).

Nutzen Sie die Wiederverwendung der Ausführungsumgebung zur Verbesserung Ihrer Funktion. Initialisieren Sie SDK-Clients und Datenbankverbindungen außerhalb des Funktions-Handlers und speichern Sie statische Komponenten lokal im /tmp-Verzeichnis. Nachfolgende Aufrufe, die von derselben Instance Ihrer Funktion verarbeitet werden, können diese Ressourcen wiederverwenden. Dies spart Kosten durch Reduzierung der Funktionslaufzeit.

Um potenzielle Datenlecks über Aufrufe hinweg zu vermeiden, verwenden Sie die Ausführungsumgebung nicht, um Benutzerdaten, Ereignisse oder andere Informationen mit Sicherheitsauswirkungen zu speichern. Wenn Ihre Funktion auf einem veränderbaren Zustand beruht, der nicht im Speicher innerhalb des Handlers gespeichert werden kann, sollten Sie für jeden Benutzer eine separate Funktion oder separate Versionen einer Funktion erstellen.

Verwenden Sie eine Keep-Alive-Direktive, um dauerhafte Verbindungen zu pflegen. Lambda bereinigt Leerlaufverbindungen im Laufe der Zeit. Der Versuch, eine Leerlaufverbindung beim Aufruf einer Funktion wiederzuverwenden, führt zu einem Verbindungsfehler. Um Ihre persistente Verbindung aufrechtzuerhalten, verwenden Sie die Keep-Alive-Direktive, die Ihrer Laufzeit zugeordnet ist. Ein Beispiel finden Sie unter Wiederverwenden von Verbindungen mit Keep-Alive in Node.js.

Verwenden Sie Umgebungsvariablen um Betriebsparameter an Ihre Funktion zu übergeben. Wenn Sie z. B. Daten in einen Amazon-S3-Bucket schreiben, anstatt den Bucket-Namen, in den Sie schreiben, hartzucodieren, konfigurieren Sie den Bucket-Namen als Umgebungsvariable.

Vermeiden Sie rekursive Aufrufe in Ihrer Lambda-Funktion, bei denen die Funktion sich selbst aufruft oder einen Prozess initiiert, der die Funktion erneut aufrufen kann. Dies kann zu unvorhergesehenen Mengen an Funktionsaufrufen führen und höhere Kosten zur Folge haben. Wenn Sie eine unbeabsichtigte Menge von Aufrufen feststellen, legen Sie die reservierte gleichzeitige Ausführung der Funktion auf 0 fest, um sofort alle Aufrufe der Funktion zu drosseln, während Sie den Code aktualisieren.

Verwenden Sie keine nicht dokumentierten, nicht öffentlichen APIs in Ihrem Lambda-Funktionscode. Für AWS Lambda-verwaltete Laufzeiten wendet Lambda regelmäßig Sicherheits- und Funktionsupdates auf Lambdas interne APIs an. Diese internen API-Updates können abwärtskompatibel sein, was zu unbeabsichtigten Konsequenzen wie Aufruffehlern führt, wenn Ihre Funktion von diesen nicht öffentlichen APIs abhängig ist. Eine Liste öffentlich zugänglicher APIs finden Sie in der API-Referenz.

Schreiben Sie idempotenten Code. Das Schreiben idempotenter Code für Ihre Funktionen stellt sicher, dass doppelte Ereignisse auf die gleiche Weise behandelt werden. Ihr Code sollte Ereignisse ordnungsgemäß validieren und doppelte Ereignisse ordnungsgemäß behandeln. Weitere Informationen finden Sie unter Wie mache ich meine Lambda-Funktion idempotent?.