This pattern creates an Amazon API Gateway HTTP API, an AWS Lambda function, and a DynamoDB Table using SAM and .NET 8.
Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage. Please see the AWS Pricing page for details. You are responsible for any AWS costs incurred.
.NET 8
The framework used to deploy the infrastructure is SAM
The AWS services used in this pattern are
API Gateway - AWS Lambda - DynamoDB
The SAM template contains all the information to deploy AWS resources (an API Gateway, 4 Lambda functions and a DynamoDB table) and also the permission required by these services to communicate.
You will be able to create and delete the CloudFormation stack using the SAM CLI.
After the stack is created you can access the follow routes on the API to perform different CRUD actions:
List all products in the database.
Retrieve a specific product from the database.
Create or update a product in the database, the API expects the below payload:
{
"Id": "my-unique-id",
"Name": "Test Product",
"Price": 10
}
Deletes an existing product.
The application follows Clean/Hexagonal/Ports & Adapters Architecture principles. This means core business logic has no external dependencies and all integrations are pushed to the outside. The solution is split down into 3 projects:
- ServerlessTestApi.Core Contains all core business/domain logic
- ServerlessTestApi.Infrastructure Contains code for any integrations, in this case DynamoDB
- Function projects:
Any integrations required by the Core layer are abstracted behind an interface, for example:
namespace ServerlessTestApi.Core.DataAccess;
public interface ProductsDAO
{
Task<ProductDTO> GetProduct(string id, CancellationToken cancellationToken);
Task PutProduct(Product product, CancellationToken cancellationToken);
Task DeleteProduct(string id, CancellationToken cancellationToken);
Task<ProductWrapper> GetAllProducts(CancellationToken cancellationToken);
}
This enables easy mocking.
The AWS SAM CLI is used to deploy the application. When working through the sam deploy --guided
take note of the stack name used.
sam init
sam build
sam deploy --guided
To test the endpoint, first send data using the following command. Be sure to update the URL with the endpoint of your stack.
curl -X POST https://{ApiUrl}/dev/a-unique-product-id -H "Content-Type: application/json" -d '{"Id": "a-unique-product-id", "Name": "Test Product", "Price": 10}'
The source code for this sample includes automated unit and integration tests. xUnit is the primary test framework used to write these tests. A few other libraries and frameworks are used depending on the test case pattern. Please see below.
Unit Tests (MockPutProductFunctionTests.cs)
The goal of these tests is to run a unit test on the handler method of the Lambda functions. It uses FakeItEasy for the mocking framework. The ProductsDAO
interface is faked.
[Fact]
public async Task PutProduct_WithValidBody_ShouldReturnSuccess()
{
// arrange
var product = default(Product);
var dto = new ProductDTO("testid", "test product", 10);
var request = new ApiRequestBuilder()
.WithHttpMethod("PUT")
.WithBody(testProduct)
.WithPathParameter("id", testProduct.Id)
.Build();
var logger = A.Fake<ILogger<Function>>();
var jsonOptions = Options.Create(new JsonSerializerOptions(JsonSerializerDefaults.Web));
var fakeDao = A.Fake<ProductsDAO>();
A.CallTo(() => fakeDao.PutProduct(A<Product>._, A<CancellationToken>._))
.Returns(Task.FromResult(UpsertResult.Inserted));
var function = new Function(fakeDao, logger, jsonOptions);
// act
var response = await function.FunctionHandler(apiRequest, new TestLambdaContext());
// assert
response.StatusCode.Should().Be(201);
response.Headers["Location"].Should().Be("https://localhost/dev/testid")
A.CallTo(() => fakeDao..PutProduct(testProduct))
.MustHaveHappened();
}
A custom class following the builder pattern is used to build the API request to be sent into the handler.
To execute the tests:
Powershell
dotnet test tests\ApiTests.UnitTest\ApiTests.UnitTest.csproj
Bash
dotnet test tests/ApiTests.UnitTest/ApiTests.UnitTest.csproj
Integration Tests (IntegrationTest.cs)
The goal of this test is to demonstrate a test that runs the Lambda function's code against deployed resources. The tests interact with the API endpoints directly and tests the expected responses returned by the API. Before running these tests, resources will need to be deployed using the steps in the Deployment Commands
section above. Tests are there for both happy and sad paths.
It uses the IClassFixture feature of xUnit to perform setup and teardown logic. The setup code retrieves the API URL and DynamoDB table name from the deployed CloudFormation stack. Teardown code ensures all created Product Ids are deleted from the table.
The tests themselves make API calls directly to the deployed resource.
To execute the tests:
Powershell
$env:AWS_SAM_STACK_NAME = <STACK_NAME_USED_IN_SAM_DEPLOY>
$env:AWS_SAM_REGION_NAME = <REGION_NAME_USED_IN_SAM_DEPLOY>
dotnet test .\tests\ApiTests.IntegrationTest\ApiTests.IntegrationTest.csproj
Bash
AWS_SAM_STACK_NAME=<STACK_NAME_USED_IN_SAM_DEPLOY>
AWS_SAM_REGION_NAME=<REGION_NAME_USED_IN_SAM_DEPLOY>
dotnet test ./tests/ApiTests.IntegrationTest/ApiTests.IntegrationTest.csproj
Run the given command to delete the resources that were created. It might take some time for the CloudFormation stack to get deleted.
sam delete
- Create an AWS account if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
- AWS CLI installed and configured
- Git Installed
- AWS Serverless Application Model (AWS SAM) installed
- .NET 8 installed