DEV Community

Cover image for Reactive, Strongly-Typed Configuration in .NET: Introducing Cocoar.Configuration v3.0
bwi for COCOAR

Posted on

Reactive, Strongly-Typed Configuration in .NET: Introducing Cocoar.Configuration v3.0

⚙️Configuration deserves better.

In .NET, configuration isn’t new — Microsoft’s IConfiguration and IOptions<T> have done a decent job for years.
Add a JSON file, overlay some environment variables — done.

Here's the thing: configuration is often treated as an underdog.
Just some JSON files, some environment variables, a bit of plumbing in Program.cs — nothing worth thinking deeply about.


In reality, configuration is much more important. It is the control plane of your app. It decides which features are enabled, how your logging behaves, what limits apply, and which backend systems you talk to.

That's why I believe configuration deserves more: it should be a first-class subsystem — observable, type-safe, and something you can trust under change and failure.

Over time I kept hitting the same pain points that Microsoft’s system couldn’t solve cleanly — Cocoar.Configuration was born from closing those gaps.


🧵 This is Part 1 of the Cocoar.Configuration Series:
1️⃣ Announcement ← you are here
2️⃣ Deep Dive (coming soon)
3️⃣ Real-World Scenarios (coming soon)
4️⃣ …and more to follow


🚀 Quick Start

A complete example in under a minute:

using Cocoar.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCocoarConfiguration(rule => [
    rule.For<AppSettings>().FromFile("appsettings.json").Select("App"),
    rule.For<AppSettings>().FromEnvironment("APP_")
], setup => [
    setup.ConcreteType<AppSettings>().ExposeAs<IAppSettings>()
]);

var app = builder.Build();
app.MapGet("/config", (IAppSettings s)
    => Results.Ok(new { s.ApiUrl, s.Timeout }));
app.Run();

// --- your config types ---
public interface IAppSettings { string ApiUrl { get; } int Timeout { get; } }
public record AppSettings(string ApiUrl, int Timeout) : IAppSettings;
Enter fullscreen mode Exit fullscreen mode

appsettings.json

{
  "App": { "ApiUrl": "https://api.example.com", "Timeout": 30 }
}
Enter fullscreen mode Exit fullscreen mode

Then run it and test:

curl http://localhost:5000/config
# {"apiUrl":"https://api.example.com","timeout":30}
Enter fullscreen mode Exit fullscreen mode

💡 Change the JSON while the app runs — hit the endpoint again and watch it update instantly!


⚙️ What Makes It Different

1️⃣ Type-Safe by Default

No IOptions<T> ceremony or manual binding.
Just clean injection of your config types or interfaces.

Microsoft style:

builder.Services.Configure<AppSettings>(
    builder.Configuration.GetSection("App"));

builder.Services.AddScoped<IAppSettings>(sp =>
    sp.GetRequiredService<IOptions<AppSettings>>().Value);
Enter fullscreen mode Exit fullscreen mode

Cocoar style:

builder.Services.AddCocoarConfiguration(rule => [
    rule.For<AppSettings>().FromFile("appsettings.json").Select("App"),
    rule.For<AppSettings>().FromEnvironment("APP_")
], setup => [
    setup.ConcreteType<AppSettings>().ExposeAs<IAppSettings>()
]);
Enter fullscreen mode Exit fullscreen mode

Interfaces work natively.
Layering is explicit.
Registration is automatic.


2️⃣ Atomic Multi-Config Updates

IOptionsMonitor<T> notifies each type separately — you can see new AppSettings with old DbSettings.
That’s inconsistent state.

Cocoar.Configuration uses tuple-reactive updates so multiple configs change atomically:

public class MyService
{
    public MyService(IReactiveConfig<(AppSettings App, DbSettings Db)> config)
    {
        config.Subscribe(tuple =>
        {
            var (app, db) = tuple;
            // both updated in one atomic pass
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Whenever any provider changes, all affected types recompute together → no cross-type mismatch.


3️⃣ Reactive by Design

Every configured type is automatically reactive.
You can subscribe and react to live updates:

public class ApiClient
{
    public ApiClient(IReactiveConfig<AppSettings> config, HttpClient http)
    {
        config.Subscribe(newSettings =>
        {
            Console.WriteLine($"Config changed → {newSettings.ApiUrl}");
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Rebuild clients, re-connect services — everything stays in sync.


4️⃣ Built-In Health Monitoring

Out-of-the-box health checks keep you informed when sources fail or recover.

public class ConfigHealthCheck : IHealthCheck
{
    private readonly IConfigurationHealthService _health;
    public Task<HealthCheckResult> CheckHealthAsync(...)
    {
        var down = _health.Snapshot.Rules
            .Where(r => r.Status == RuleResultStatus.Down && r.Required);
        return Task.FromResult(
            down.Any()
              ? HealthCheckResult.Unhealthy()
              : HealthCheckResult.Healthy());
    }
}
Enter fullscreen mode Exit fullscreen mode

Health states include Initializing, Healthy, Degraded, Failed, Recovering — all integrated with ASP.NET Core health checks.


📊 Quick Comparison

Feature Microsoft Config + IOptions Cocoar.Configuration
Type Safety Manual binding Automatic
DI Registration Explicit Configure<T> Automatic (scoped)
Interface Injection Manual factory Native .ExposeAs<>()
Reactivity Per-type only Tuple-reactive atomic
Layering Implicit (provider order) Explicit (rule order)
Health Monitoring Manual Built-in

🧩 When to Use It

✅ Use Cocoar.Configuration if you:

  • Need reactive, dynamic config
  • Combine multiple sources (file + env + HTTP)
  • Care about health and observability
  • Want interface-based, type-safe injection
  • Have multi-tenant or dependent configs
  • Require atomic updates across types

❌ Maybe skip it if you:

  • Load static config once and forget it
  • Only have a few simple settings
  • Don’t need reactivity or health tracking

📦 Installation

Core

dotnet add package Cocoar.Configuration
dotnet add package Cocoar.Configuration.AspNetCore
dotnet add package Cocoar.Configuration.DI
Enter fullscreen mode Exit fullscreen mode

Optional providers

dotnet add package Cocoar.Configuration.HttpPolling
dotnet add package Cocoar.Configuration.MicrosoftAdapter
Enter fullscreen mode Exit fullscreen mode

🔜 What’s Next

This is just the beginning. Coming up next:

  • Part 2 – Deep Dive: How rules, layers, and atomic recomputation work
  • Part 3 – Real World Scenarios: Multi-tenant setups, secrets, graceful fallbacks
  • Later: Design philosophy & integration stories from production

Star the repo or follow me on Dev.to to get notified when the next parts drop!


📚 Resources

License: Apache 2.0


Built by a developer who loves architecture more than ceremony.
— Bernhard / @cocoar-dev

Try it out and let me know what you think 🚀

Top comments (0)