Developing .NET apps with Amazon DynamoDB

Advertisements

You need to store data, but you don’t have a vast schema. You need to fetch your data in a split second, and you don’t need referential integrity or transactions. You require a super stable and scalable datastore, but you don’t have the personnel to manage a DBMS. You need to persist data, but you also need to stream the data to external systems. If this sounds familiar, you should checkout Amazon DynamoDB.

DynamoDB is a fully managed NoSQL, serverless Amazon service that is capable of running applications at just about any scale you require. DynamoDB also allows for multiple integration points with Amazon services, such as Amazon S3, AWS Glue, Amazon Kinesis Data Streams, AWS CloudTrail and Amazon CloudWatch. These integration points enable you to query, analyze, and aggregate data.

The Solution

In this solution, we will first create a local development DynamoDB database using docker. We will then create a DynamoDB table using the AWS CLI. Once we have the DynamoDB database and table setup, we will build a .NET application that will insert data into a DynamoDB table and also query the data that we inserted.

Remember, for any example solution from AWS with .NET, we focus on the code that exemplifies the problem we are trying to solve. We don’t include logging, input validation, exception handling, etc., and we embed the configuration data within classes instead of using environment variables, configuration files, key/value stores and the like. These items should not be skipped for proper solutions.

Prerequisites

To complete this solution, you will need the .NET CLI which is included in the .NET SDK. In addition, you will need to install Docker and Docker Compose. You will also need to download the AWS CLI and configure your environment.

Warning: some AWS services may have fees associated with them.

Development/Local DynamoDB Database

Amazon has made available a downloadable version of DynamoDB. This local or development database allows developers to create and harden their solutions in a development environment and then move the solution to the cloud once they are satisfied with it. There are three options to install this local DynamoDB database: Maven, manual download and docker image. For this solution, we’ll use docker-compose with the docker image.

Let’s first create a file named docker-compose.yaml with the following contents.

version: '3'
services:
dynamodb:
command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ./data"
image: "amazon/dynamodb-local:latest"
ports:
"8000:8000"
volumes:
"./docker/dynamodb:/home/dynamodblocal/data"
working_dir: /home/dynamodblocal

Let’s spin up the DynamoDB database using the following command:

On Windows:

$ docker compose up

On non-Windows Systems:

$ docker-compose up

With the database in place, let’s create the table at the CLI. Note, when interacting with the local or “development” DynamoDB instance, you must specify the local endpoint with each command, like so:

–endpoint-url http://localhost:8000

$ aws dynamodb create-table \

––table-name Vehicles \

––attribute-definitions \

AttributeName=VIN,AttributeType=S \

––key-schema AttributeName=VIN,KeyType=HASH \

––provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \

––table-class STANDARD \

––endpoint-url http://localhost:8000

Let’s verify that the table was created by running the following command:

$ aws dynamodb list-tables ––endpoint-url http://localhost:8000

You should see the following response.

{
“TableNames”: [
“Vehicles”
]
}

With the database and table ready, let’s build out our .NET application. Keep this terminal running as we will need it later on.

DynamoDB based .NET Web API

Abstractions

First we’ll need a place to store our cross-project interfaces and we’ll create a project named, “Abstractions” at the CLI for that.

$ dotnet new classlib ––name Abstractions

The first interface we’ll create is IDataConnectionSettingsFactory. This interface has one method named, GetSettings.

namespace Abstractions;
public interface IDataConnectionSettingsFactory<T>
{
T GetSettings();
}

The next interface we’ll create is IDataClientFactory. The IDataClientFactory also only has one method, GetClient.

namespace Abstractions;
public interface IDataClientFactory<T>
{
T GetClient();
}

Domain

Similarly, we’ll need to create a project to store our “domain” classes. We can do that also at the CLI, like so:

$ dotnet new classlib ––name Domain

For this solution, we will have only one class in this project, Vehicle, which is defined below.

namespace Domain;
public class Vehicle
{
public string Vin { get; set; }
public string? Year { get; set; }
public string? Make { get; set; }
public string? Model { get; set; }
}
view raw Vehicle.cs hosted with ❤ by GitHub

DynamoDB Implementations

The last supporting project will contain the DynamoDB based implementations of our abstractions. We can create this project with the following .NET CLI command:

$ dotnet new classlib ––name DynamoDB

This project will require a dependency on the Abstractions project. We’ll add that dependency like so:

$ dotnet add ./DynamoDB reference ./Abstractions

The DynamoDB project also needs to reference the AWS DynamoDB SDK. We can also add that reference via the command line:

$ dotnet add ./DynamoDB package AWSSDK.DynamoDBv2

The first implementation will be the LocalDynamoDBConnectionSettingsFactory class which implements IDataConnectionSettingsFactory. IDataConnectionSettingsFactory is a generic interface and the type we will use will be AmazonDynamoDBConfig.

public class LocalDynamoDBConnectionSettingsFactory :
IDataConnectionSettingsFactory<AmazonDynamoDBConfig>
{
}

For this implementation, we simply new up an AmazonDynamoDBConfig object and then set the ServiceUrl property. Finally, we return the AmazonDynamoDBConfig object.

public class LocalDynamoDBConnectionSettingsFactory :
IDataConnectionSettingsFactory<AmazonDynamoDBConfig>
{
public AmazonDynamoDBConfig GetSettings()
{
AmazonDynamoDBConfig amazonDynamoDBConfig = new AmazonDynamoDBConfig();
amazonDynamoDBConfig.ServiceURL = "http://localhost:8000";
return amazonDynamoDBConfig;
}
}

The second implementation, DynamoDBClientFactory, will implement IDataClientFactory. IDataClientFactory is also a generic interface, and the type will be, AmazonDynamoDBClient.

DynamoDBClientFactory has a dependency on an IDataConnectionSettingsFactory based class and we will inject that via constructor injection.

public class DynamoDBClientFactory :
IDataClientFactory<AmazonDynamoDBClient>
{
private readonly IDataConnectionSettingsFactory<AmazonDynamoDBConfig> _dataConnectionSettingsFactory;
public DynamoDBClientFactory(IDataConnectionSettingsFactory<AmazonDynamoDBConfig> dataConnectionSettingsFactory){
_dataConnectionSettingsFactory = dataConnectionSettingsFactory;
}
}

To complete the DynamoDBClientFactory class, we need to create the GetClient method as seen below.

public class DynamoDBClientFactory : IDataClientFactory<AmazonDynamoDBClient>
{
private readonly IDataConnectionSettingsFactory<AmazonDynamoDBConfig> _dataConnectionSettingsFactory;
public DynamoDBClientFactory(IDataConnectionSettingsFactory<AmazonDynamoDBConfig> dataConnectionSettingsFactory){
_dataConnectionSettingsFactory = dataConnectionSettingsFactory;
}
public AmazonDynamoDBClient GetClient()
{
AmazonDynamoDBConfig amazonDynamoDBConfig = _dataConnectionSettingsFactory.GetSettings();
AmazonDynamoDBClient client = new AmazonDynamoDBClient(amazonDynamoDBConfig);
return client;
}
}

That completes the DynamoDB project.

.NET Web API Test App

We’ll create a .NET Web API to surface data from the new DynamoDB table. To create a templated .NET Web API app, run the following command:

$ dotnet new webapi ––name Api

Next, we’ll add the references to the supporting libraries.

$ dotnet add ./Api reference ./DynamoDB ./Domain ./Abstractions

We’ll also need to add the same package reference to the AWS DynamoDB SDK as we did earlier.

$ dotnet add ./Api package AWSSDK.DynamoDBv2

Now that our application is setup with its dependencies, let’s get to work on the code.

First, we’ll setup the dependency injection. To do this, add the following lines to the builder variable in the Program.cs file.

builder.Services.AddScoped<IDataConnectionSettingsFactory<AmazonDynamoDBConfig>, LocalDynamoDBConnectionSettingsFactory>();
builder.Services.AddScoped<IDataClientFactory<AmazonDynamoDBClient>, DynamoDBClientFactory>();
view raw Program.cs hosted with ❤ by GitHub

Now, let’s build out some of the logic to create data and get data from the DynamoDB database. The first step is to create a controller named VehicleController.cs and inject the client factory via constructor injection.

public VehicleController(IDataClientFactory<AmazonDynamoDBClient> factory)
{
_client = factory.GetClient();
}

In the VehicleController class, let’s create the following method that will accept a Vehicle as input and then creates a document in the DynamoDB database that represents the vehicle data. Let’s call the method, “Post.”

[HttpPost]
public async Task<IActionResult> Post(Vehicle vehicle)
{
Table table = Table.LoadTable(_client, "Vehicles");
var vehicleDocument = new Document();
vehicleDocument["VIN"] = vehicle.Vin;
vehicleDocument["YearManufactured"] = vehicle.Year;
vehicleDocument["Make"] = vehicle.Make;
vehicleDocument["Model"] = vehicle.Model;
await table.PutItemAsync(vehicleDocument);
return Created("/vehicle/" + vehicle.Vin, vehicle);
}

Great! Now that we can get data into the DynamoDB database, let’s work on getting data out. We’ll create a method named, Get, that takes a string variable named, vin. We will use the vin variable to “query” the DynamoDB database to get the corresponding document. We then parse the document, pulling data out of the attributes and then using that data to hydrate a Vehicle object. We then return the vehicle data with a 200 HTTP status code. The Get method should look something like this.

[HttpGet("{vin}")]
public async Task<IActionResult> Get(string vin)
{
Table table = Table.LoadTable(_client, "Vehicles");
Document vehicleDocument = await table.GetItemAsync(vin);
var vehicle = new Vehicle{
Vin = vehicleDocument["VIN"],
Year = vehicleDocument["YearManufactured"],
Make = vehicleDocument["Make"],
Model = vehicleDocument["Model"]
};
return Ok(vehicle);
}

That’s it for our API project.

With all the code and projects in place, let’s create a solution file using the .NET CLI.

$ dotnet new sln ––name dynamodb

Then, let’s add all of the projects to the solution file.

$ dotnet sln add ./Abstractions ./Api ./DynamoDB ./Domain

With everything in place, let’s build the solution.

$ dotnet build

Great! Now that everything builds and looks good, let’s run the API application.

$ dotnet run ––project ./Api

Testing the DynamoDB backed API with Swagger UI

With everything in place, let’s test the API we just built. To test the API, feel free to use any tool that you are comfortable with. Swagger UI is included in this solution, so we’ll use that.

In your favorite browser, navigate to: http://localhost:5000/swagger/index.html. You should see the following UI.

To create data in our local DynamoDB database, click on the “POST” button and then click the “Try it out” button. Finally, replace the text in the “Request Body” with the JSON object below and click the “Execute” button.

{
  "vin": "1",
  "year": "2020",
  "make": "BMW",
  "model": "330i"
}

Once the request has finished, Swagger UI should update with a 201 response.

Great! We now have data in our local DynamoDB database. Let’s try to fetch the data we just created. Let’s scroll up and click the “GET” button and then again, the “Try it out” button. In the “vin” parameter input box, enter “1”, the value that we just used for the vin in our POST request and then click the “Execute” button. Swagger UI should update with the data that we just created.

Challenges

Now that you have a working solution, let’s take things a little further…

  • Extend the solution to update a vehicle.
  • Extend the solution to delete a vehicle.
  • Add logic in the Get method of the vehicle controller that will return a 404 status code if a vehicle cannot be found.
  • Add logic in the Post method of the vehicle controller that will return a 400 status code if a vehicle with the supplied VIN is already in the database.
  • Move this solution to the DynamoDB service.

That’s it! We have concluded this article where you have learned how to create a local DynamoDB database as well as create a DynamoDB table using the CLI. You also learned how to create data and fetch data from DynamoDB using the .NET CLI.

Get all the code on GitHub.

Want to know more about the tech in this article? Checkout these resources:

.NET CLI

AWS CLI 

Configuring the AWS CLI

ASP.NET Web APIs

Amazon DynamoDB

Using Amazon DynamoDB via the AWS CLI

Advertisements
%d bloggers like this: