OrleansContrib/Orleans.Providers.MongoDB

Configuration from *.json

IhnatKlimchuk opened this issue · 16 comments

I'm trying to setup MongoDB GrainStorage with options from appsettings.json or from environment variables in common case, but don't see an elegant way to do this. Below I will provide problem short description.

For example in AddMongoDBGrainStorage(this ISiloHostBuilder builder, string name, Action<MongoDBGrainStorageOptions> configureOptions) action configureOptions do not have access to current IConfiguration and as result we can't get options from appsettings file.

Does it make sense to add AddMongoDBGrainStorage(this ISiloHostBuilder builder, string name, Action<HostBuilderContext, MongoDBGrainStorageOptions> configureOptions) overload? In this case we can keep knowledge about adding grain storage and options source in one place.

Will be glad to see other ways to configure options from appsettings.json file.

Yes, can you make a PR?

Hi @bboyle1234, will try to make one.

I am not 100% sure, but I think you just have to configure the options object in the service locator after your registration

services.Configure(Config.Get("section"));

Yes, @SebastianStehle is correct. The provider shouldn't enforce any schema or section but, if you define a section and it has the same structure as the code-config options object and you call the GetSection() on the configure, it would work...

Being able to configure from enironment variables is important too. (My own orleans project does that, and instantiates a static config object using environment variables before creating the silo builder). It's a bit anti-pattern and I'd like to do it better too.

@SebastianStehle, being a bit new to configuration/DI patterns, I wasn't able to understand exactly how to use the code snippet you provided above.

@bboyle1234 exactly. That is why I mentioned that not rely on a specific format is the magic to make this new Microsoft.Extensions.Options to work. Json, text, xml, ENVVar, SQL, it doesn't matter. Just write it in a way that can be parsed thru a provider and then set properties on whatever config Options class you are targeting to and it will magically work.

Check this: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.1#suboptions-configuration

That particular topic is interesting as it show you how to get (for example) the Json file and read a section configuring it from a json file directly. Ignore the JSON, and look for similar examples to envvars and you will see it is exactly the same mechanics.

Is this what you mean?

siloBuilder
    .ConfigureServices(services => {
        services.Configure<MongoDBGrainStorageOptions>("Default", options => {
            options.ConnectionString = Environment.GetEnvironmentVariable("MONGODB_CONNECTION_STRING") ?? "mongodb://localhost:27017";
            options.DatabaseName = $"TradingSilo-{Environment.GetEnvironmentVariable("APEX_ENVIRONMENT") ?? "dev"}-Storage";
        });
        services.Configure<MongoDBGrainStorageOptions>("PubSubStorage", options => {
            options.ConnectionString = Environment.GetEnvironmentVariable("MONGODB_CONNECTION_STRING") ?? "mongodb://localhost:27017";
            options.DatabaseName = $"TradingSilo-{Environment.GetEnvironmentVariable("APEX_ENVIRONMENT") ?? "dev"}-PubSubStorage";
        });
    })
    .AddMongoDBGrainStorageAsDefault()
    .AddMongoDBGrainStorage()

Thank you. Here's some test code I produced, based on what I've learned and it's working for me. Were you able to get what you need @IhnatKlimchuk?

Environment.SetEnvironmentVariable("MONGODB__GRAINSTORAGE__DEFAULT__ConnectionString", "mongodb://localhost:27017");
Environment.SetEnvironmentVariable("MONGODB__GRAINSTORAGE__DEFAULT__DatabaseName", "TradingSilo-dev-Storage");
Environment.SetEnvironmentVariable("MONGODB__GRAINSTORAGE__PUBSUB__CONNECTIONSTRING", "mongodb://localhost:27017");
Environment.SetEnvironmentVariable("MONGODB__GRAINSTORAGE__PUBSUB__DatabaseName", "TradingSilo-dev-PubSubStorage");

var configuration = new ConfigurationBuilder()
    .AddEnvironmentVariables()
    .Build();

// ------ Other TestCode --------------
//var sectionDefault = configuration.GetSection("MongoDB:GrainStorage:Default");
//var sectionPubSub = configuration.GetSection("MongoDB:GrainStorage:PubSub");

//var services = new ServiceCollection()
//    .Configure<MongoDBGrainStorageOptions>("Default", configuration.GetSection("MongoDB:GrainStorage:Default"))
//    .Configure<MongoDBGrainStorageOptions>("PubSub", configuration.GetSection("MongoDB:GrainStorage:PubSub"))
//    .BuildServiceProvider();

//var configDefault = services.GetOptionsByName<MongoDBGrainStorageOptions>("Default");
//var configPubSub = services.GetOptionsByName<MongoDBGrainStorageOptions>("PubSub");
// ------ Other TestCode --------------

var mySilo = new SiloHostBuilder()
    .UseLocalhostClustering()
    .AddMongoDBGrainStorageAsDefault()
    .AddMongoDBGrainStorage("PubSub")
    .ConfigureServices(s => s
            .Configure<MongoDBGrainStorageOptions>("Default", configuration.GetSection("MongoDB:GrainStorage:Default"))
            .Configure<MongoDBGrainStorageOptions>("PubSub", configuration.GetSection("MongoDB:GrainStorage:PubSub")))
    .Build();

I then modified it slightly to use this:

.ConfigureServices(s => {
    foreach(var section in configuration.GetSection("MongoDB:GrainStorage").GetChildren()) {
        s.Configure<MongoDBGrainStorageOptions>(section.Key, section);
    }
})

The disadvantage to this that I'm finding right now is that it doesn't allow custom logic to be applied to the configuration values. For example, I had to manually type "dev" into each database name environment variable instead of automagically inserting it from another environment variable. I also couldn't provide default values if an environment variable was missing.

Exactly. So, using this configuration system, how does one change the options.DatabaseName (for example) from something-dev to something-prod without typing it directly into the MongoDB__GrainStorage__Default__DatabaseName environment variable?

Just updated my previous question

No idea what to use with this issue, I will just close it for now.