Domain Driven Design with Web API extensions part 10: the MongoDb context

Introduction

In the previous post we installed MongoDb locally. We also started the MongoDb server and connected to it with a client. We then inserted a test console application into our DDD skeleton project, imported the MongoDb .NET driver and connected to the MongoDb server using the driver.

In this post we’ll continue to explore the MongoDb context and some practical limitations compared to the automated tools available in EntityFramework. We’ll also add a new C# console library to the DDD skeleton project. The new library will eventually contain the MongoDb equivalent classes of what we have in the WebSuiteDemo.Loadtesting.Repository.EF layer.

The MongoDb context and some limitations

If you are familiar with EntityFramework and also recall the automated processes that come with EF in Visual Studio then here comes a the first lesson: there’s almost none of it in MongoDb. There’s no built-in context which you can call “SaveChanges” on, there’s no migration tool, there’s no Seed method to fill the database tables, i.e. collections etc.

Anyhow, the migration tool wouldn’t make any sense in MongoDb because there’s no strict schema in MongoDb collections. It’s fine to insert a Car object in a collection which is then followed by a House object. It’s perfectly legal to place them in the same document. Now, in practice you probably won’t do anything like that. However, you are free to add and remove properties to and from an object and not worry about data migration. Say you start with the following Car object properties:

public int Wheels { get; set; }
public string Make { get; set; }
public int YearMade { get; set; }

You start adding these Car objects to the Cars collection and then you want to extend the model with the following properties:

public string CountryOfOrigin { get; set; }
public int MaxNumberOfPassengers { get; set; }

In a traditional relational database, such as MS SQL you would add a column for the new property, declare its type, maybe max size, whether it’s nullable etc. Then you’d need to synchronise the context model with the database model – or vice versa if you follow the code first approach. There’s none of it MongoDb. You can vary your context model, i.e. the domains as you want and not worry about the structure of the objects in the collection. The older objects that don’t have those new properties will simply get the default value of the underlying type, e.g. null for strings and 0 for integers. This requires adding an attribute to the property though of which we’ll see an example in the upcoming posts.

In addition you don’t need to worry about secondary keys. This can be both good and bad in fact:

  • You can easily end up with orphaned objects – you have to write code to get rid of them, there’s no cascade delete in MongoDb
  • On the other hand the lack of secondary keys increases the flexibility of the structure

The lack of a built-in object context that tracks the changes of the entities could be more difficult to accept but it’s still manageable. MongoDb is good at multithreading. Also, you can set various levels of acknowledgement policies on writes that help you check if the insert/update/delete operations have been successful. In practice you’ll write the data access code in a different style compared to EF but I’ve yet to see any serious practical limitation.

Another missing feature is the mapping between the database model and the domain model. We saw the basics of that in the initialisation code here:

CreateTable(
                "dbo.LoadtestTypes",
                c => new
                    {
                        Id = c.Guid(nullable: false),
                        Description_ShortDescription = c.String(nullable: false),
			Description_LongDescription = c.String(nullable: false),
                    })
                .PrimaryKey(t => t.Id);

Again, this is not automated at all. We’ll need to write our own database objects by hand and to keep them separate from the domain model. We could use the domain objects directly as the database model. However, that would mean that we have to add at least one MongoDb specific attribute to the model. Recall that we mentioned that every MongoDb document must have an ObjectId. You can then declare the domain model Id as an ObjectId which resides in the MongoDb.Bson namespace. Alternatively you can decorate a string property as follows:

[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }

No matter which approach you take it would mean polluting the domain object with MongoDb specific elements.

We had to give up at least a little bit of persistence ignorance (PI) in our domain models already to accommodate the automatic mapping between the EF database models and the domain models with the private parameterless constructors. Here’s an example to refresh your memory:

private Agent() : base(Guid.NewGuid()) { }

We said that it was an acceptable step away from 100% PI. It’s only a private constructor, i.e. not something that external consumers can easily misuse. An object with no real technological dependency, such as a private constructor can be easily ported from one project to another if necessary. However, this won’t be true if we decorate our pure domain classes with attributes like BsonRepresentation, BsonConstructor, BsonElement etc. Those would reduce the level of persistence ignorance, POCOness and portability of the domain classes drastically. Therefore we won’t go down that route. Instead we’ll keep the domain and database objects separate in their own namespaces. It will certainly require more work and manual mapping between the classes but we can let the database objects handle the MongoDb-specific logic as they have to. They will therefore not interfere with the domain classes.

The MongoDb repository stub

OK, that’s enough of the theory, let’s see some action. Open the DDD skeleton project and add a new C# class library to it called WebSuiteDemo.Loadtesting.Repository.MongoDb to the BoundedContexts/LoadTesting sub-folder. Remove Class1.cs. At this point we have the following project structure:

After adding the MongoDb repository layer

We’ll start building the MongoDb context now. Add the same MongoDb NuGet package to the repository project as the one we added to the MongoDbDatabaseTester console application in the previous post. Also, add a project reference to WebSuiteDDD.Infrastructure.Common.

Then insert a new class called LoadTestingContext to the MongoDb repository project. We’ll keep it to a minimum to begin with:

public class LoadTestingContext
{
	private IMongoClient Client { get; set; }
	private IMongoDatabase Database { get; set; }
	private const string _databaseName = "Loadtests";
	private static LoadTestingContext _loadTestingContext;

	private LoadTestingContext() { }

	public static LoadTestingContext Create(IConnectionStringRepository connectionStringRepository)
	{
		if (_loadTestingContext == null)
		{
			_loadTestingContext = new LoadTestingContext();
			string connectionString = connectionStringRepository.ReadConnectionString("MongoDbWebSuiteContext");
			_loadTestingContext.Client = new MongoClient(connectionString);
			_loadTestingContext.Database = _loadTestingContext.Client.GetDatabase("WebSuite");
		}			
		return _loadTestingContext;
	}
}

The above code creates a LoadTestingContext object using the factory pattern. The Create method will only build one LoadtestTestingContext and then return it on every new request. In other words we’ll have the same context re-used over and over again. That’s fine, MongoDb is meant to be used like that. There’s absolutely no need to create a brand new instance of the object which handles the connection to the MongoDb server. The underlying mechanism will automatically open and close the connection for you. We won’t get those funny “connection closed” and “row not found or changed” exceptions like in LINQ to Entities.

The above context object is sort of the MongoDb equivalent of LoadTestingContext.cs we currently have in WebSuiteDemo.Loadtesting.Repository.EF:

public class LoadTestingContext : DbContext
{
	public LoadTestingContext()
		: base("WebSuiteContext")
	{
	}

	public DbSet<Agent> Agents { get; set; }
	public DbSet<Customer> Customers { get; set; }
	public DbSet<Engineer> Engineers { get; set; }
	public DbSet<Loadtest> Loadtests { get; set; }
	public DbSet<LoadtestType> LoadtestTypes { get; set; }
	public DbSet<Project> Projects { get; set; }
	public DbSet<Scenario> Scenarios { get; set; }
}

However, what is that IConnectionStringRepository injected into the Create method? And what is the MongoDb equivalent of the DbSet objects?

We’ll find out all that in the next post.

View the list of posts on Architecture and Patterns here.

Advertisement

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.

%d bloggers like this: