Writing Open Source code for .Net Core

DateTime.AfterPost - DateTime.Now = 4 minutes

One of the disadvantages we rapidly recognized when thinking about making the system out of MicroServices, was how to manage each MS configuration.

At first we thought of creating a MS that will be the Configuration Distributor. It would work like this: It would expose a REST API that each MS can access, and in return receive its configuration in JSON form.
Very simple and clean. It was so easy to use, it became part of the infrastructure. So much so, the developer himself would receive this functionality as part of the template.
Moreover, the Configuration Distributor had another functionality, it was capable of receiving new configurations from the user (another REST endpoint) and telling the specific MS it’s configuration has changed. All MS’s would have an event called when the configuration changed and it could be updated with no down-time.

This worked pretty well but it had a drawback. The configuration for the MS wouldn’t be set at the constructor but at a later point. This makes you do funny things to make the MSs compatible with this “special” “feature”.

It was on the back of my head for days that this is not the best way to manage the configuration. One day, I decided to look at how .Net Core builds the configuration for the MS and I had a idea: Extend .Net Core Configuration Builder to have a MongoDb provider.

First, lets look how it works.

.Net Core DI Configuration

The configuration is built for us like this (default):

var configuration = new ConfigurationBuilder()
         .SetBasePath(env.ContentRootPath)
         .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
         .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
         .AddEnvironmentVariables()
.AddCommandLine(args)
.Build();

Each “Add*” will add a different provider for the configuration.

AddJsonFile – Reads a Json file. It can be optional and it has the option to set up a watchdog on the file and reload the configuration if the file has changed. .Net Core loads two files by default; the “real” one and the Development one.

AddEnvironmentVariables – Adds the environment variables to the configuration. You can set up that it will only add environment variables with a specific prefix.

AddCommandLine – Adds the command line arguments the app was run with.

The order of the Add* is very important. Each one will override it’s predecessor’s configuration having the same key.

For example, if I have my appsettings.json file:

{
“SomeKey” : “AValue”
}

And my appsettings.Development.json file:

{
“SomeKey” : “DevelopmentValue”
}

Then configuration[“SomeKey”] will be “DevelopmentValue” if the app is run in development and “AValue” if the app is run in release.

You can even override json’s keys by using the command line arguments:

myApp.exe –SomeKey “CommandLineValue”

Back to our project

Seeing all the possibilities the configuration had, I thought to myself that it’s probably not so hard to extend it. I started playing with the idea to add another provider, a custom one, that would receive the configuration from the Configuration Distributor. A few days later, we came to the realization that the Configuration Distributor isn’t really necessary and, even more important, it is a single point of failure we don’t want in our system.

Still, we wanted the configurations for the MS to be easily accessible. We decided they would still sit in a MongoDb but instead of using the Configuration Distributor to read the configuration, each MS will read the configuration directly from the DB.

MongoConfiguration

I searched for a MongoDB implementation of the IConfiguration interface but didn’t find one. Happily, I set myself a challenge to implement it myself.
I thought this could be of great use to other developers as well, so I decided to make it Open Source and available on nuget.org

The idea is pretty simple, it connects to a MongoDB database and reads the configuration from there. It works the same as the JsonFile provider, with the same conventions, so it can operate the same. It will override keys already setted and is open to be overridden by next providers.

We changed all our working MSs to work with this nuget instead of the Configuration Distributor and it works great!

I’ve really enjoyed working on this small Open Source project and hope it helps many programmers. The first few days it already had ~200 downloads!

Extending IConfiguraton

You can implement directly the IConfiguration interface and add your implementation to the configuration using the AddConfiguration() extension method.

When implementing IConfiguration’s interface you’ll need to implement these functions:

  •  Item[String] – A Get property that returns the value of key according to your provider.
  • GetChildren() – In .Net Core’s configuration, as everything is considered a String, the configuration is separated by sections. If you have composite values, they will enter into the previous’ key section. This function should return and IEnumerable of IConfigurationSection representing the key’s inner values.
  • GetReloadToken() – This function returns a IReloadToken to be used to listen to when the configuration needs to be reloaded.
  • GetSection(string) – Like GetChildren(), this function should return the IConfigurationSection described by the key passed to it.

Implementing the IConfiguration interface is pretty tedious and you just want to implement the business logic itself.

Luckily for us, .Net Core’s programmers are ready for this and they implemented a simpler way to do this:

ConfigurationProvider

This class gives all the basic things you need and lets you focus on the real business layer of your implemenation; all you need to do is to override the Load() function. (example)

  1. Get all the configuration you need in the constructor (that will be called from the extension method you’ll create later)
  2. Read the data from your own source
  3. Fill the “Data” parameter from the base class with your data.
Then, you’ll need to implement the ConfigurationSource interface. This interface has only one function: IConfigurationProvider Build(). (example)
In this function you should return a IConfigurationProvider implementation, which, luckily for us, is excactly what we did!
Use it’s constructor to receive the data you need to use in your ConfigurationProvider.

 

The (almost) last thing to do is to add an extension method to the IConfigurationBuilder (example) to make it easier to use your provider. In the extension method make sure to receive all the needed parameters to build your ConfigurationProvider. 

The last thing to do is to test it! You can see in my repository which tests I did. (here)
I started by doing some simple tests to see I can read the configuration properly and advanced to see if my loaded configuration overrides other configurations as it should.

Writing something that is intended to be Open Source makes you aware that it doesn’t need to “just work” but it also needs to be well written and following conventions. It makes you aware of every little thing you write. I’ve learned a lot by just defining this project as Open Source.

Comments: 1

  1. […] are working on a new project, with many microservices, databases, API and other fancy words. Sadly/Happily, one of the developers had to take a break […]

Add your comment