How do you avoid a messy jumble of configuration management? First, you can rule out holding settings internally to the app. Hard coding values in a compiled artifact limits almost everything about an application’s ability to scale. Attaching a .config file to each instance of the application is also not reasonable. (Yes, your externalized config values from the artifact.) But you’re no better off with managing and updating things.
This class will visit the Microsoft.Extensions.Configuration package and the many options it offers .NET Core developers. We’ll cover everything from using the default providers (and what’s going on under the covers) to custom providers implemented in Steeltoe. There are many stops in between where developers can achieve the best mix of business requirements and technical needs.
Attend this class to learn the following:
● How to use external configurations with Spring Config using Steeltoe
● Best practices for externalizing configuration
● How to get the most from Spring Config without adding complexity
David Dieruf, Principal Product Marketing Manager, Pivotal
2. Let's Talk About...
Cloud-native environment and goals
Why we are here
Designing config and environment variables
Different patterns
About .NET Core configuration
Building config providers in WebHost and its hierarchy of values
The options pattern
Extending basic config with objects
External configuration servers
How they work and why you choose them
Putting the pieces together
Using Steeltoe and Spring Config to reach cloud-native goals
3. Environment: Local
Network
Services: DB, Cache, SMB
Environment: Stage Environment: Prod
Team Foundation Server (CI/CD): Git, Build, Release
My-App
Network
Services: DB, Cache, SMB
Network
Services: DB, Cache, SMB
My-App My-App
- One build
- Test once (for all environments)
- Parity between environments
- Automation
My-App My-App
public void ConfigureServices(IServiceCollection services) {
String connString = Environment.GetEnvironmentVariable("conn_string");
services.AddDbContext<MyContext>(options => options.UseSqlServer(connString));
}
4. Environment variables in the cloud
How is the value set?
Do you have to manually update the value per
instance, or script the action?
How easy is promotion?
Do you have to recompile to move between
environments? Does your build pipeline manipulate
settings?
Do you have a history of changes?
Do you know when a value was set and by who?
Atomic value in the
cloud
Per app instance
App can not run without
a value
Agnostic to its platform
or IaaS (portable)
5. Appsettings.json
● [Good] External to the
compiled application
● [Good] No recompilation
needed
● [Challenge] Have to change
at every instance
Internal Class Command Line
● [Good] Secure information
in compiled bits
● [Challenge] Hard coded
values in app
● [Challenge] Can not change
without recompiling
● [Good] External to the
compiled application
● [Challenge] Have to change
at every instance
● [Challenge] Lost values
because no source check-in
(Manual)
Environment Variables
Popular Configuration Design
● [Good] External to the
compiled application
● [Good] Application
compiled once and
promoted through
environment
● [Challenge] Have to change
at every host
class MyClass {
string connString = "xxxx"
}
{
"ConnectionStrings": {
"a-database": "xxxxx"
}
}
C: dotnet run my-app `
--conn "xxxx" `
--something "yyyyy"
string connString =
Environment.GetEnvironmentVari
able("my-conn")
7. Using Default
WebHost
Loading configuration into your app
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
- use Kestrel as the web server and configure it using the
application's configuration providers
- set the ContentRootPath to the result of GetCurrentDirectory()
- load IConfiguration from 'appsettings.json' and
'appsettings.[EnvironmentName].json'
- load IConfiguration from User Secrets when EnvironmentName
is 'Development' using the entry assembly
- load IConfiguration from environment variables
- load IConfiguration from supplied command line args
- configure the ILoggerFactory to log to the console and debug
output
- enable IIS integration
program.cs
WebHost.CreateDefaultBuilder(args)
8. Build your own
WebHost
Loading configuration into your app
public static IWebHost BuildWebHost(string[] args) {
var builder = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) => {
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json",
optional: true);
config.AddEnvironmentVariables();
config.AddCommandLine(args);
})
.UseIISIntegration()
.UseStartup<Startup>();
return builder.Build();
}
program.cs
- Options manipulate how configuration values are overwritten
- Options to [not] include other configuration providers, like
custom external config server provider
var builder = new WebHostBuilder()
9. Config value
hierarchy
Overwrite values based on
environment
public static IWebHost BuildWebHost(string[] args) {
var builder = new WebHostBuilder()
...
.ConfigureAppConfiguration((hostingContext, config) => {
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json");
config.AddEnvironmentVariables();
config.AddCommandLine(args);
})
...
1. Load values from appsettings.json
2. (if provided) Load values from environment
specific appsettings.{env}.json
3. (if present) Load values from environment
variables
4. (if present) Load values provided when app
was started
config.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json");
config.AddEnvironmentVariables();
config.AddCommandLine(args);
12. Consuming
Values
Using
Microsoft.Extensions.Configuration
public class ValuesController : Controller{
private readonly IConfiguration _config;
public ValuesController(IConfiguration config){
_config = config;
}
[HttpGet]
public ActionResult<string> Get(){
string val = _config.GetValue<string>("my_val");
return val;
}
}
- No consideration for handler type(s)
- Simple key:value store
- Can be complex type
ValuesController.cs
14. Options Pattern Using Microsoft.Extensions.Configuration.Options
{
"section0": {
"key0": "value",
"key1": "value"
},
"section1": {
"key0": "value",
"key1": "value"
}
}
public void ConfigureServices(IServiceCollection services) {
services.AddOptions();
services.Configure<SectionZeroOptions>(Configuration.GetSection("section0"));
...
}
public class SectionZeroOptions {
public string key0 {get;set;}
public string key1 { get; set; }
}
public class MyService : IMyService {
private IOptions<SectionZeroOptions> _SectionZeroOptions;
public MyService(IOptions<SectionZeroOptions> sectionZeroOptions) {
_SectionZeroOptions = sectionZeroOptions;
}
public string DoSomething(){
string key0 = _SectionZeroOptions.Value.key0;
string key1 = _SectionZeroOptions.Value.key1;
return key0;
}
}
1. appsettings.json values
2. Matching class to json
3. Enable the Options configuration
4. Consume values
{
"section0": {
"key0": "value",
"key1": "value"
},
"section1": {
"key0": "value",
"key1": "value"
}
}
public class SectionZeroOptions {
public string key0 {get;set;}
public string key1 { get; set; }
}
public void ConfigureServices(IServiceCollection services) {
services.AddOptions();
services.Configure<SectionZeroOptions>(Configuration.GetSection("section0"));
...
}
public class MyService : IMyService {
private IOptions<SectionZeroOptions> _SectionZeroOptions;
public MyService(IOptions<SectionZeroOptions> sectionZeroOptions) {
_SectionZeroOptions = sectionZeroOptions;
}
public string DoSomething(){
string key0 = _SectionZeroOptions.Value.key0;
string key1 = _SectionZeroOptions.Value.key1;
return key0;
}
}
15. Business Case using options pattern
You have an api that is responsible for approving loans. There are
business rules within the app that need to be applied, to decide
approval. One of those rules compares the loan amount being asked
for, to a max loan amount the business is willing to approve. The max
amount changes throughout the year based on other events.
Place the max approval amount (integer) in an external config and give
yourself the flexibility to change values at any moment, with no
downtime.
17. About Config
Server
At boot time…
➔ Platform puts values in environment variables
➔ Client looks for server
➔ Client gets config by NAME
➔ Config organised by PROFILE
At runtime...
➔ Config can be refreshed live
➔ Call /actuator/refresh
➔ Values get reloaded
➔ No need for restart!
19. Simplicity vs Flexibility
External configuration can be challenging in itself It offers so much
design options that things can get out of hand quickly. Use the domain
around you to decide how far to go.
Config First Approach
Don’t hold anything in your appsettings.json, only a pointer to the
config server. Everything is filled from there.