AWS Lambda wspiera kilka środowisk uruchomieniowych. W obecnej chwili (maj 2020) jest ich aż 6. Ponadto istnieje możliwość implementacji swojego własnego środowiska uruchomieniowego. Jednym ze wspieranych środowisk jest .NET Core w wersjach 2.1 oraz 3.1 – czyli tych, które obecnie są jedynymi wersjami LTS. Najpopularniejszym językiem, którego można używać korzystając z tego frameworka, jest C#.
Z tego artykułu dowiesz się:
- jakich narzędzi używać w efektywnej pracy z AWS Lambda w połączeniu z C# oraz .NET Core,
- w jaki sposób tworzyć oraz wdrażać funkcje Lambda, które używają środowiska uruchomieniowego .NET Core,
- jak weryfikować działanie funkcji.
Podczas pracy z narzędziami zintegrowanymi z powłoką systemową oraz pisania skryptów używam PowerShella. Nic nie stoi jednak na przeszkodzie, żeby analogiczne komendy z niewielki wysiłkiem przenieść do innego środowiska.
Narzędzia przydatne w pracy z AWS Lambda
AWS CLI
Podstawowym narzędziem w pracy z AWS jest AWS CLI. Instrukcję instalacji można znaleźć w oficjalnej dokumentacji. Po instalacji należy je skonfigurować, żeby umożliwić pracę z naszym kontem AWS.
.NET Core CLI
Pomocnym narzędziem przy tworzeniu funkcji lambdy przy użyciu platformy .NET Core jest .NET Core CLI. Zintegrowany z powłoką systemową interfejs pozwala na efektywną pracę z projektami, które są oparte o ten framework. Jedną z funkcjonalności wiersza poleceń jest tworzenie startowych projektów zgodnych z naszymi potrzebami. W tym celu wystarczy wywołać komendę:
dotnet new -all
Po jej wykonaniu otrzymasz podobny rezultat:

Pracując z AWS Lambda, możesz skorzystać z dodatkowego pakietu oferowanego przez Amazon:
dotnet new -i Amazon.Lambda.Templates
Po instalacji dostępne są nowe typy projektów, bardzo przydatne podczas rozpoczynania pracy z interesującą nas usługą:

Kolejnym ciekawym rozszerzeniem jest Amazon Lambda Tools. Przyda się przede wszystkim w procesie wdrażania funkcji na środowisku. W celu instalacji również wystarczy wywołać odpowiednią komendę w wierszu poleceń:
dotnet tool install -g Amazon.Lambda.Tools
AWS SAM CLI
Kolejnym przydatnym narzędziem jest AWS SAM CLI. Służy przede wszystkim do lokalnego uruchamiania naszych funkcji. Kompletną instrukcję, w jaki sposób je zainstalować, znajdziesz tutaj.
Struktura projektu
Wspomniane wcześniej szablony stanowią bardzo dobry punkt początkowy w tworzeniu funkcji, które wymagają na starcie pewnych integracji z innymi usługami AWS. W tym rozdziale opiszę, w jaki sposób własnoręcznie stworzyć prostą funkcję. Po stworzeniu nowej solucji oraz projektu typu Class library
w .NET Core musisz zainstalować pakiet NuGet Amazon.Lambda.Core. Za jego pomocą będziesz w stanie stworzyć poprawną funkcję Lambda. Po tej akcji warto stworzyć nową klasę, która będzie reprezentacją funkcji. Ja wybrałem trywialny przykład, w którym moja funkcja będzie obliczała końcową ocenę studenta na podstawie przekazanych do niej ocen cząstkowych. Nie wchodząc w szczegóły implementacyjne samej logiki obliczania, funkcja wygląda w następujący sposób:
public class CalculateStudentFinalGradeLambda
{
public CalculateStudentFinalGradeResponse Invoke(CalculateStudentFinalGradeRequest request)
{
var lectureGrade = new StudentGrade(request.LectureGrade);
var exerciseGrade = new StudentGrade(request.ExerciseGrade);
var workshopGrade = new StudentGrade(request.WorkshopGrade);
var finalGrade = new StudentFinalGrade(lectureGrade, exerciseGrade, workshopGrade);
return new CalculateStudentFinalGradeResponse
{
FinalGrade = finalGrade.Value,
HasPassed = finalGrade.HasPassed
};
}
}
Drugim obowiązkowym elementem w projekcie (oprócz samej funkcji) jest ustawienie Assembly attribute
. Ten atrybut umożliwi poprawną deserializację oraz serializację modeli użytych odpowiednio na wejściu i wyjściu funkcji. Możesz to zrobić bezpośrednio w klasie funkcji. Warto jednak wydzielić ten element do osobnego pliku, ponieważ wystarczy przeprowadzić to raz w obrębie biblioteki (niezależnie od liczby funkcji). Do definicji potrzebna jest implementacja interfejsu dostarczonego przez bazowy pakiet użyty do tworzenia funkcji Lambda: ILambdaSerializer
. Możesz dostarczyć własną implementację, ale warto rozważyć użycie jednej z dwóch gotowych. Pierwsza z nich znajduje się w pakiecie NuGet Amazon.Lambda.Serialization.SystemTextJson. Opiera się ona na przestrzeni nazw System.Text.Json
, wprowadzonej w .NET Core 3.0. Należy zainstalować wspomniany pakiet, a następnie zdefiniować atrybut w taki sposób:
using Amazon.Lambda.Serialization.SystemTextJson;
[assembly: LambdaSerializer(typeof(DefaultLambdaJsonSerializer))]
Istnieje również alternatywa opierająca się na znanej bibliotece Newtonsoft.Json. Podobnie jak w poprzednim przypadku, gotowa implementacja dostarczona jest w odpowiednim pakiecie NuGet – tym razem w Amazon.Lambda.Serialization.Json. Po instalacji pakietu możesz użyć go w atrybucie w taki sposób:
using Amazon.Lambda.Serialization.Json;
[assembly: LambdaSerializer(typeof(JsonSerializer))]
Wdrażanie funkcji
Funkcję Lambda najłatwiej wdrożyć za pomocą AWS SAM oraz CloudFormation. W tym celu musisz zdefiniować plik opisujący funkcję pod kątem infrastrukturalnym. Żeby to osiągnąć, należy stworzyć plik YAML w katalogu projektu. Wewnątrz pliku trzeba zdefiniować wszystkie potrzebne właściwości funkcji:
Transform: AWS::Serverless-2016-10-31
Description: Example of Lambda Functions in .NET Core
Resources:
CalculateStudentGradeLambda:
Type: AWS::Serverless::Function
Properties:
FunctionName: calculate-student-grade-lambda
Handler: AwsLambdaDotnet.Functions::AwsLambdaDotnet.Functions.CalculateStudentFinalGrade.CalculateStudentFinalGradeLambda::Invoke
Runtime: dotnetcore3.1
CodeUri: bin/Release/netcoreapp3.1/publish
MemorySize: 1024
Timeout: 10
Zawartość jest dość czytelna i myślę, że większość właściwości nie wymaga komentarza. Pełną dokumentację tego zasobu znajdziesz tutaj. Warto wytłumaczyć przede wszystkim postać, w jakiej należy podać Handler
funkcji. Składa się on z 3 członów:
- nazwy projektu (czyli wynikowego pliku dll),
- pełnej nazwy typu (wraz z przestrzenią nazw), w którym zdefiniowana jest metoda, którą chcemy wywołać,
- metody, którą chcemy wywołać.
Posiadając plik opisujący funkcję Lambda, przystąp do wdrożenia. Bardzo pomocne będzie wcześniej wspomniane narzędzie Amazon Lambda Tools. Zakładając, że wcześniej zostało skonfigurowane konto, na które chcesz wdrożyć funkcję oraz że zostały zainstalowane wspomniane wcześniej narzędzia, wystarczy wykonać następujący skrypt w głównym katalogu solucji:
dotnet publish $PSScriptRoot/AwsLambdaDotnet.Functions -c Release
dotnet lambda deploy-serverless `
--project-location $PSScriptRoot/AwsLambdaDotnet.Functions `
--configuration Release `
--region eu-west-1 `
--stack-name aws-lambda-dotnet `
--s3-bucket your-deploy-bucket `
--s3-prefix AwsLambdaDotnet.Functions/ `
--template $PSScriptRoot/AwsLambdaDotnet.Functions/serverless.yaml
Pierwsza komenda przygotowuje kod do wdrożenia, a druga – faktycznie je realizuje. W treści komendy należy zmienić parametr --s3-bucket
na nazwę wcześniej stworzonego bucketa, który będzie służył do przechowywania artefaktów wdrożenia. Przy pierwszym wywołaniu skryptu powinniśmy zobaczyć rezultat publikowania projektu oraz pełny proces wdrażania funkcji:

Testowanie działania rozwiązania
Weryfikacja działania w chmurze
Po wdrożeniu funkcji warto zweryfikować jej działanie. Możesz zrobić to, wywołując ją między innymi bezpośrednio z portalu AWS lub z linii poleceń. W celu wywołania funkcji z portalu należy znaleźć ją na liście funkcji i wywołać z poprawnym zapytaniem. Przykładowe zapytanie wykorzystane do wywołania funkcji może wyglądać następująco:
{
"LectureGrade": 3,
"ExerciseGrade": 4,
"WorkshopGrade": 4.5
}
Po wykonaniu funkcji powinniśmy otrzymać komunikat z odpowiedzią:

Można również wywołać funkcję z wiersza poleceń za pomocą komendy:
aws lambda invoke `
--function-name calculate-student-grade-lambda `
--payload file://payload.json `
response.json
Plik payload.json
powinien zawierać wcześniej wspomniane zapytanie, natomiast plik response.json
będzie zawierać odpowiedź po wywołaniu funkcji. Po wykonaniu komendy w wierszu poleceń powinna pojawić się informacja o poprawnym wywołaniu funkcji. Z kolei plik response.json
powinien zawierać tę samą odpowiedź co w portalu.
Lokalne uruchamianie funkcji
W celu przetestowania działania funkcji niekoniecznie trzeba wdrażać ją na środowisko. Dzięki AWS SAM CLI możesz uruchomić funkcję lokalnie. Żeby to zrobić, najpierw stwórz środowisko symulujące rzeczywiste środowisko wywołania funkcji. W głównym katalogu solucji należy wywołać następującą komendę:
sam local start-lambda -t AwsLambdaDotnet.Functions/serverless.yaml
To spowoduje przygotowanie kontenera gotowego na obsługę wywołań. Po tej operacji możesz wywołać funkcję następującym poleceniem:
aws lambda invoke `
--function-name calculate-student-grade-lambda `
--payload file://payload.json `
--endpoint-url "http://127.0.0.1:3001" `
response-local.json
Podobnie jak w przypadku wywoływania funkcji w rzeczywistym środowisku, w odpowiedzi otrzymamy informację o sukcesie, a odpowiedź zostanie zapisana we wskazanym przez nas pliku.
Testy automatyczne
W każdym oprogramowaniu testy automatyczne stanowią bardzo istotny element. Nie inaczej jest w przypadku tworzenia funkcji Lambda. Powinniśmy dążyć do tego, żeby jak największa część logiki w przygotowywanej funkcji była pokryta testami jednostkowymi. Warto jednak przygotować również zestaw testów automatycznych, które weryfikują poprawność działania funkcji już po wdrożeniu na środowisko chmurowe. Taki test mógłby wyglądać w następująco:
private readonly IAmazonLambda _lambdaClient = new AmazonLambdaClient();
private const string CalculateStudentFinalGradeLambdaName = "calculate-student-grade-lambda";
[Fact]
public async Task InvokeLambda_WithCorrectParameters_ShouldReturnCorrectResponse()
{
// Given
const double validExerciseGrade = 3.5;
const double validLectureGrade = 2.1;
const double validWorkshopGrade = 4.9;
var requestModel = new CalculateStudentFinalGradeRequest
{
ExerciseGrade = validExerciseGrade,
LectureGrade = validLectureGrade,
WorkshopGrade = validWorkshopGrade
};
var lambdaRequest = new InvokeRequest
{
FunctionName = CalculateStudentFinalGradeLambdaName,
Payload = JsonSerializer.Serialize(requestModel)
};
// When
var lambdaResponse = await _lambdaClient.InvokeAsync(lambdaRequest);
// Then
var responseModel = await JsonSerializer.DeserializeAsync<CalculateStudentFinalGradeResponse>(lambdaResponse.Payload);
responseModel.Should().NotBeNull();
responseModel.FinalGrade.Should().NotBe(default);
responseModel.HasPassed.Should().BeTrue();
}
Test stworzony w ten sposób możesz z powodzeniem dołączyć do swojego procesu CI/CD jako weryfikację działania po wdrożeniu nowej wersji. We wszystkich przykładach zakładaliśmy, że wywołanie funkcji zakończy się sukcesem. Jak radzić sobie z obsługą błędów podczas wywołań? Tego możesz dowiedzieć się w tym artykule.
Wszystkie przykłady gotowe do samodzielnego przetestowania znajdziesz w moim repozytorium.
Zobacz w jaki sposób pracować z AWS Lambda z wykorzystaniem innych środowisk i języków programowania: