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:
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:
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¶
- API Contract Specification - API endpoint details
- SDK Usage Guide - Detailed SDK patterns
- Solution Architecture - Integration architecture