Amazon DocumentDB and .NET
Tags: dotnet, database, aws
Published: 2022-05-16 17:08:38

[Amazon DocumentDB](https://aws.amazon.com/documentdb/) is a fully managed, scalable, and highly available document database service that uses a distributed, fault-tolerant, self-healing storage system that auto-scales up to 64 TB per cluster. Amazon DocumentDB reduces database I/O by only persisting write-ahead logs and does not need to write full buffer page syncs, avoiding slow, inefficient, and expensive data replication across network links.

This design allows for the separation of compute and storage. This means that you can scale each of those areas independently. DocumentDB is designed for 99.99% availability and replicates six copies of your data across three availability zones. It also continually monitors cluster instance health and automatically fails over to a read replica in the event of a failure–typically in less than 30 seconds. You can start with a single instance that delivers high durability then, as you grow, can add a second instance for high availability, and easily increase the number of instances you use for read-only. You can scale read capacity to millions of requests per second by scaling out to 15 low latency read replicas that can be scattered across three availability zones

One of the interesting characteristics is that DocumentDB was built with MongoDB compatibility. Why is that important?

## MongoDB

[MongoDB](https://www.mongodb.com/) is a source-available cross-platform document database that was first released in 2009 and is, by far, the most popular document database available. Since it was both the first, and available open-sourced, it tended to be adopted early by companies running on-premises and experimenting with the concept of document stores so there is a huge number of on-premises software systems that rely completely on, or in part on, MongoDB. This means that any movement of systems to the cloud would have to come up with a way to support the software written around MongoDB otherwise this would be a massive impediment in migrating to the cloud.

AWS realized this and released DocumentDB to include compatibility with the MongoDB APIs that were available at the time. At the time of this writing (and at release), DocumentDB implements the MongoDB 3.6 and 4.0 API responses. However, MongoDB released version 5.0 in July 2021, so there is no feature parity. Also, since there were annual releases for 4.2, 4.4, 4.4.5, and 4.4.6 that AWS did not support it appears that there will not be additional support moving forward.

While current compatibility with MongoDB is not supported, the design of DocumentDB, together with optimizations like advanced query processing, connection pooling, and optimized recovery and rebuild, provide a very performant system. AWS claims that DocumentDB achieves twice the throughput of currently available MongoDB managed services.

## Setting up a DocumentDB Database

Now that we have a bit of understanding about DocumentDB, let’s go set one up. Log in to the console, and either search for Document DB or find it using Services > Database > Amazon DocumentDB. Select the **Create Cluster** button on the dashboard to bring up the Create cluster page as shown in Figure 1.

 <img src="https://i0.wp.com/billthevestguy.com/wp-content/uploads/2022/05/image-3.png" />
_Figure 1. Create Amazon DocumentDB Cluster screen_

First, fill out the Configuration section by adding in a _Cluster identifier_. This identifier must be unique to DocumentDB clusters within this region. Next, you’ll see a dropdown for the _Engine version_. This controls the MongoDB API that you will want to use for the connection. This becomes important when selecting and configuring the database drivers that you will be using in your .NET application. We recommend that you use 4.0 as this gives you the ability to support transactions. The next two fields are _Instance class_ and _Number of instances_. This is where you define the compute and memory capacity of the instance as well as the number of instances that you will have. For the Instance class, the very last selection in the dropdown is _db.t3.medium_, which is eligible for the free tier. We selected that class. When considering the number of instances, you can have a minimum of 1 instance that acts as both read-write, or you can have more than one instance which would be primary instance plus replicas. We chose two (2) instances so that we can see both primary and replica instances.

You’ll see a profound difference if you compare this screen to the others that we saw when working with RDS as this screen is much simpler and seems to give you much less control over how you set up your cluster. The capability to have more fine control over configuration is available, however; you just need to click the slider button at the lower left labeled **Show advanced settings**. Doing that will bring up the _Network settings_, _Encryption_, _Backup_, _Log exports_, _Maintenance_, _Tags_, and _Deletion protection_ configuration sections that you may be familiar with from other AWS databases, especially RDS.

Once you complete the setup and click the **Create cluster** button you will be returned to the Clusters list screen that will look like Figure 2.

   <img src="https://i0.wp.com/billthevestguy.com/wp-content/uploads/2022/05/image-4.png" />
_Figure 2. Clusters screen immediately after creating a cluster_

As Figure 2 shows, there are initial roles for a _Regional cluster_ and a _Replica instance_. As the creation process continues, the regional cluster will be created first, and then the first instance in line will change role to become a _Primary instance_. When creation is completed, there will be one regional cluster, one primary instance, and any number of replica instances. Since we chose two instances when creating the cluster, we show one replica instance being created. When the cluster is fully available you will also have access to the regions in which the clusters are available. At this point, you will have a cluster on which to run your database, but you will not yet have a database to which you can connect. But, as you will see below when we start using DocumentDB in our code, that’s ok.

## DocumentDB and .NET

When looking at using this service within your .NET application, you need to consider that like Amazon Aurora, DocumentDB emulates the APIs of other products. This means that you will not be accessing the data through AWS drivers, but instead will be using MongoDB database drivers within your application. It is important to remember, however, that your database is not built using the most recent version of the MongoDB API, so you must ensure that the drivers that you do use are compatible with the version that you selected during the creation of the cluster, in our case MongoDB 4.0. Luckily, however, MongoDB provides a compatibility matrix at [https://docs.mongodb.com/drivers/csharp/](https://docs.mongodb.com/drivers/csharp/).

The necessary NuGet package is called **[MongoDB.Driver](https://www.nuget.org/packages/mongodb.driver)**. Installing this package will bring several other packages with it including **MongoDB.Driver.Core**, **MongoDB.Driver.BSON**, and **MongoDB.Libmongocrypt**. One of the first things that you may notice is there is not an “Entity Framework” kind of package that we got used to when working with Amazon RDS. And that makes sense because Entity Framework is basically an Object-Relational Mapper (ORM) that helps manage the relationships between different tables – which is exactly what we do not need when working with NoSQL systems. Instead, your table is simply a collection of things. However, using the MongoDB drivers allow you to still use well-defined classes when mapping results to objects. It is, however, more of a deserialization process than an ORM process.

When working with DocumentDB, you need to build your connection string using the following format: mongodb://[user]:[password]@[hostname]/[database]?[options]

This means you have five components to the connection string:

- **user** – user name
- **password** – password 
- **hostname** – url to connect to
- **database** – Optional parameter - database with which to connect
- **options** – a set of configuration options

It is easy to get the connection string for DocumentDB, however. In the DocumentDB console, clicking on the identifier for the regional cluster will bring you to a summary page. Lower on that page is a _Connectivity & security tab_ that contains examples of approaches for connecting to the cluster. The last one, Connect to this cluster with an application, contains the cluster connectionstring as shown in Figure 3.

<img src="https://i0.wp.com/billthevestguy.com/wp-content/uploads/2022/05/image-5.png" /> 
_Figure 3. Getting DocumentDB connectionstring_

You should note that the table name is not present in the connection string. You can either add it to the connection string or you can use a different variable as we do in the example below.
Once you have connected to the database and identified the collection that you want to access, you are able to access it using lambdas (the anonymous functions, not AWS’ serverless offering!). Let’s look at how that works. First, we define the model that we want to use for the deserialization process.

```
public class Person
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]

    public string Id { get; set; }

    [BsonElement("First")]
    public string FirstName { get; set; }

    [BsonElement("Last")]
    public string LastName { get; set; }
}
```
What you’ll notice right away is that we are not able to use a plain-ol’ class object (POCO) but instead must provide some MongoDB BSON attributes. Getting access to these will require the following libraries to be added to the using statements:

```
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes; 
```

There are many different annotations that you can set, of which we are using three:
- **BsonId** – this attribute defines the document’s primary key. This means it will become the easiest and fastest field on which to retrieve a document.
- **BsonRepresentation** – this attribute is primarily to support ease-of use. It allows the developer to pass the parameter as type string instead of having to use an ObjectId structure as this attribute handles the conversion from string to ObjectId.
- **BsonElement** - this attribute maps the property name “Last” from the collection to the object property “LastName” and the property name “First” to the object property “FirstName”.
That means that the following JavaScript shows a record that would be saved as a Person type. 

```
{
 "Id": "{29A25F7D-C2C1-4D82-9996-03C647646428}",
 "First": "Bill",
 "Last": "Penberthy"
}
```

We mentioned earlier that accessing the items within DocumentDB becomes straightforward when using lambdas. Thus, a high-level CRUD service could look similar to the code in Listing 1.

```
using MongoDB.Driver;
public class PersonService
{
    private readonly IMongoCollection<Person> persons;

    public PersonService ()
    {
        var client = new MongoClient("connection string here");
        var database = client.GetDatabase("Production");
        persons = database.GetCollection<Person>("Persons");
    }

    public async Task<List<Person>> GetAsync() => 
        await persons.Find(_ => true).ToListAsync();

    public async Task<Person?> GetByIdAsync(string id) =>
        await persons.Find(x => x.Id == id).FirstOrDefaultAsync();

    public async Task< Person?> GetByLastNamAsync(string name) =>
        await persons.Find(x => x.LastName == name).FirstOrDefaultAsync();

    public async Task CreateAsync(Person person) =>
        await persons.InsertOneAsync(person);

    public async Task UpdateAsync(string id, Person person) =>
        await persons.ReplaceOneAsync(x => x.Id == id, person);

    public async Task RemoveAsync(string id) =>
        await persons.DeleteOneAsync(x => x.Id == id);
}
```
_Code Listing 1. CRUD service to interact with Amazon DocumentDB_

One of the conveniences with working with DocumentDB (and MongoDB for that matter) is that creating the database and the collection is automatic when the first item (or document) is saved. Thus, creating the database and book collection is just as simple as saving your first book. Of course, that means you have to make sure that you define your database and collection correctly, but it also means that you don’t have to worry about your application not connecting if you mistype the database name – you’ll just have a new database!