Skip to content

Config Platform - Integration Patterns

Overview

This document provides concrete integration patterns for connecting applications to Config Platform. Use these patterns to generate integration code, SDK wrappers, and adapter implementations.

SDK Integration Patterns

.NET SDK Pattern

Installation:

dotnet add package ConnectSoft.ConfigPlatform.Sdk

Basic Usage:

using ConnectSoft.ConfigPlatform.Sdk;

// Register in DI container
services.AddConfigPlatform(options =>
{
    options.BaseUrl = "https://config.connectsoft.cloud";
    options.TenantId = tenantId;
    options.ClientId = clientId;
    options.ClientSecret = clientSecret;
});

// Inject and use
public class MyService
{
    private readonly IConfigClient _configClient;

    public MyService(IConfigClient configClient)
    {
        _configClient = configClient;
    }

    public async Task<string> GetApiUrlAsync()
    {
        var config = await _configClient.ResolveAsync(
            path: "api.baseUrl",
            environment: "production"
        );
        return config.Value;
    }
}

Options Pattern Integration:

// Configure options class
public class PaymentServiceOptions
{
    public string ApiKey { get; set; }
    public int RetryCount { get; set; }
    public TimeSpan Timeout { get; set; }
}

// Register with Config Platform binding
services.Configure<PaymentServiceOptions>(
    configuration.GetSection("PaymentService")
);

// Bind from Config Platform
services.AddConfigPlatformBinding<PaymentServiceOptions>(
    configSetId: "payment-service",
    path: "payment"
);

Hot Reload Pattern:

services.AddConfigPlatform(options =>
{
    options.EnableHotReload = true;
    options.RefreshInterval = TimeSpan.FromMinutes(5);
});

// Subscribe to changes
public class ConfigChangeHandler : IConfigChangeHandler
{
    public Task OnConfigChangedAsync(ConfigChangeEvent changeEvent)
    {
        // Reload options, update cache, etc.
        return Task.CompletedTask;
    }
}

JavaScript/TypeScript SDK Pattern

Installation:

npm install @connectsoft/config-platform-sdk

Basic Usage:

import { ConfigClient } from '@connectsoft/config-platform-sdk';

const client = new ConfigClient({
  baseUrl: 'https://config.connectsoft.cloud',
  tenantId: 'tenant-id',
  clientId: 'client-id',
  clientSecret: 'client-secret'
});

// Resolve configuration
const config = await client.resolve({
  path: 'api.baseUrl',
  environment: 'production'
});

console.log(config.value);

React Hook Pattern:

import { useConfig } from '@connectsoft/config-platform-sdk/react';

function MyComponent() {
  const { config, loading, error } = useConfig('api.baseUrl', {
    environment: 'production'
  });

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return <div>API URL: {config.value}</div>;
}

Hot Reload with WebSocket:

const client = new ConfigClient({
  enableHotReload: true,
  refreshChannel: 'websocket'
});

client.onConfigChanged((event) => {
  console.log('Config changed:', event.path, event.value);
  // Update application state
});

Direct API Integration Pattern

REST API Client Pattern

public class ConfigApiClient
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl;
    private readonly string _tenantId;

    public async Task<ConfigValue> ResolveAsync(
        string path,
        string environment = "production")
    {
        var request = new HttpRequestMessage(
            HttpMethod.Get,
            $"{_baseUrl}/api/v1/configs/{path}:resolve?env={environment}"
        );

        request.Headers.Add("Authorization", $"Bearer {await GetTokenAsync()}");
        request.Headers.Add("X-Tenant-Id", _tenantId);

        var response = await _httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadFromJsonAsync<ConfigValue>();
    }

    private async Task<string> GetTokenAsync()
    {
        // OAuth2 client credentials flow
        // Return cached token or fetch new one
    }
}

gRPC Client Pattern

using ConnectSoft.ConfigPlatform.Grpc;

var channel = GrpcChannel.ForAddress("https://config.connectsoft.cloud");
var client = new ResolveService.ResolveServiceClient(channel);

var request = new ConfigResolveRequest
{
    Path = "api.baseUrl",
    Environment = "production",
    ServiceId = "payment-service"
};

var response = await client.ResolveAsync(request);
Console.WriteLine($"Value: {response.Value}");

Event-Driven Refresh Pattern

MassTransit Consumer Pattern

public class ConfigRefreshConsumer : IConsumer<ConfigPublishedEvent>
{
    private readonly IConfigCache _cache;
    private readonly ILogger<ConfigRefreshConsumer> _logger;

    public async Task Consume(ConsumeContext<ConfigPublishedEvent> context)
    {
        var @event = context.Message;

        // Invalidate cache for affected paths
        await _cache.InvalidateAsync(@event.ConfigSetId, @event.Paths);

        // Notify application components
        await NotifyConfigChangedAsync(@event);

        _logger.LogInformation(
            "Config refreshed: {ConfigSetId}, Paths: {Paths}",
            @event.ConfigSetId,
            string.Join(", ", @event.Paths)
        );
    }
}

// Register consumer
services.AddMassTransit(x =>
{
    x.AddConsumer<ConfigRefreshConsumer>();

    x.UsingAzureServiceBus((context, cfg) =>
    {
        cfg.Host(connectionString);
        cfg.ConfigureEndpoints(context);
    });
});

Webhook Pattern

[ApiController]
[Route("webhooks/config")]
public class ConfigWebhookController : ControllerBase
{
    [HttpPost("refresh")]
    public async Task<IActionResult> HandleRefresh(
        [FromBody] ConfigRefreshWebhook payload,
        [FromHeader(Name = "X-ECS-Signature")] string signature)
    {
        // Verify HMAC signature
        if (!VerifySignature(payload, signature))
        {
            return Unauthorized();
        }

        // Process refresh
        await _configCache.InvalidateAsync(
            payload.ConfigSetId,
            payload.Paths
        );

        return Ok();
    }

    private bool VerifySignature(object payload, string signature)
    {
        // HMAC-SHA256 verification
    }
}

Multi-Tenant Configuration Access Pattern

Tenant-Scoped Client Factory

public interface ITenantConfigClientFactory
{
    IConfigClient GetClientForTenant(string tenantId);
}

public class TenantConfigClientFactory : ITenantConfigClientFactory
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IConfiguration _configuration;

    public IConfigClient GetClientForTenant(string tenantId)
    {
        var httpClient = _httpClientFactory.CreateClient();

        // Configure tenant-specific settings
        httpClient.DefaultRequestHeaders.Add("X-Tenant-Id", tenantId);

        return new ConfigClient(httpClient, _configuration);
    }
}

Tenant Context Middleware

public class TenantConfigMiddleware
{
    private readonly RequestDelegate _next;

    public async Task InvokeAsync(
        HttpContext context,
        ITenantConfigClientFactory factory)
    {
        var tenantId = context.User.FindFirst("tenant_id")?.Value;

        if (!string.IsNullOrEmpty(tenantId))
        {
            var configClient = factory.GetClientForTenant(tenantId);
            context.Items["ConfigClient"] = configClient;
        }

        await _next(context);
    }
}

Caching and Fallback Strategies

Multi-Layer Cache Pattern

public class CachedConfigClient : IConfigClient
{
    private readonly IConfigClient _innerClient;
    private readonly IMemoryCache _memoryCache;
    private readonly IDistributedCache _distributedCache;

    public async Task<ConfigValue> ResolveAsync(string path)
    {
        var cacheKey = $"config:{path}";

        // L1: Memory cache
        if (_memoryCache.TryGetValue(cacheKey, out ConfigValue cached))
        {
            return cached;
        }

        // L2: Distributed cache
        var distributed = await _distributedCache.GetStringAsync(cacheKey);
        if (distributed != null)
        {
            var value = JsonSerializer.Deserialize<ConfigValue>(distributed);
            _memoryCache.Set(cacheKey, value, TimeSpan.FromMinutes(5));
            return value;
        }

        // L3: API call
        var value = await _innerClient.ResolveAsync(path);

        // Populate caches
        await _distributedCache.SetStringAsync(
            cacheKey,
            JsonSerializer.Serialize(value),
            new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
            }
        );

        _memoryCache.Set(cacheKey, value, TimeSpan.FromMinutes(5));

        return value;
    }
}

Fallback Pattern

public class ResilientConfigClient : IConfigClient
{
    private readonly IConfigClient _primaryClient;
    private readonly IConfigClient _fallbackClient;
    private readonly IOptionsSnapshot<AppSettings> _appSettings;

    public async Task<ConfigValue> ResolveAsync(string path)
    {
        try
        {
            return await _primaryClient.ResolveAsync(path);
        }
        catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.ServiceUnavailable)
        {
            // Fallback to local configuration
            return GetFromLocalConfig(path);
        }
        catch (Exception)
        {
            // Last resort: use default from appsettings
            return GetFromAppSettings(path);
        }
    }

    private ConfigValue GetFromLocalConfig(string path)
    {
        // Read from local cache file or database
    }

    private ConfigValue GetFromAppSettings(string path)
    {
        // Read from appsettings.json as fallback
        var value = _appSettings.Value.GetValue<string>(path);
        return new ConfigValue { Value = value, Source = "fallback" };
    }
}

Error Handling Patterns

Retry with Exponential Backoff

public class RetryableConfigClient : IConfigClient
{
    private readonly IConfigClient _innerClient;
    private readonly ILogger<RetryableConfigClient> _logger;

    public async Task<ConfigValue> ResolveAsync(string path)
    {
        var retryPolicy = Policy
            .Handle<HttpRequestException>()
            .OrResult<ConfigValue>(r => r == null)
            .WaitAndRetryAsync(
                retryCount: 3,
                sleepDurationProvider: retryAttempt =>
                    TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                onRetry: (outcome, timespan, retryCount, context) =>
                {
                    _logger.LogWarning(
                        "Retry {RetryCount} after {Delay}ms",
                        retryCount,
                        timespan.TotalMilliseconds
                    );
                }
            );

        return await retryPolicy.ExecuteAsync(
            () => _innerClient.ResolveAsync(path)
        );
    }
}

Circuit Breaker Pattern

services.AddConfigPlatform(options =>
{
    options.CircuitBreaker = new CircuitBreakerOptions
    {
        FailureThreshold = 5,
        DurationOfBreak = TimeSpan.FromSeconds(30),
        SamplingDuration = TimeSpan.FromMinutes(1)
    };
});

References