Config Platform - Service Integration Blueprint¶
Overview¶
This document provides concrete service integration patterns for Config Platform microservices. Use these patterns to generate service integration code, API gateway configurations, and event handlers.
Service Architecture¶
Service Components¶
flowchart TB
subgraph Edge[Edge Layer]
Gateway[API Gateway]
Auth[Auth Service]
end
subgraph Core[Core Services]
Registry[Config Registry]
Policy[Policy Engine]
Refresh[Refresh Orchestrator]
Adapter[Adapter Hub]
end
subgraph Data[Data Layer]
DB[(CockroachDB)]
Cache[(Redis)]
Bus[(Event Bus)]
end
Gateway --> Auth
Gateway --> Registry
Gateway --> Policy
Registry --> DB
Registry --> Cache
Registry --> Refresh
Refresh --> Bus
Policy --> Registry
Adapter --> Gateway
Adapter --> DB
Hold "Alt" / "Option" to enable pan & zoom
Service Interaction Patterns¶
Config Resolution Flow¶
Sequence Diagram:
sequenceDiagram
participant Client
participant Gateway
participant Registry
participant Policy
participant Cache
participant Refresh
Client->>Gateway: GET /configs/{path}:resolve
Gateway->>Gateway: Authenticate & Authorize
Gateway->>Cache: GET tenant:path:etag
alt Cache Hit
Cache-->>Gateway: 304 Not Modified
Gateway-->>Client: 304
else Cache Miss
Gateway->>Registry: resolve(path, context)
Registry->>Policy: validateAndOverlay(config, context)
Policy-->>Registry: resolvedValue + etag
Registry->>Cache: SET tenant:path -> value
Registry-->>Gateway: 200 + value + ETag
Gateway-->>Client: 200 + value + ETag
end
Hold "Alt" / "Option" to enable pan & zoom
Implementation Pattern:
public class ConfigRegistryService
{
private readonly IConfigRepository _repository;
private readonly IPolicyEngine _policyEngine;
private readonly IRedisCache _cache;
public async Task<ResolvedConfig> ResolveAsync(
string path,
ResolutionContext context)
{
// Check cache first
var cacheKey = $"tenant:{context.TenantId}:path:{path}";
var cached = await _cache.GetAsync<ResolvedConfig>(cacheKey);
if (cached != null && cached.ETag == context.ETag)
{
return cached;
}
// Load from repository
var config = await _repository.GetByPathAsync(path, context);
// Apply policy overlays
var resolved = await _policyEngine.ResolveAsync(config, context);
// Cache result
await _cache.SetAsync(cacheKey, resolved, TimeSpan.FromHours(1));
return resolved;
}
}
Config Publish Flow¶
Sequence Diagram:
sequenceDiagram
participant UI
participant Gateway
participant Registry
participant Policy
participant Refresh
participant Bus
UI->>Gateway: POST /configs/{path}:publish
Gateway->>Registry: publish(path, draftId)
Registry->>Policy: validate(draft, context)
alt Validation Failed
Policy-->>Registry: validation errors
Registry-->>Gateway: 400 Bad Request
Gateway-->>UI: 400
else Validation Success
Policy-->>Registry: validated
Registry->>Registry: createVersion()
Registry->>Refresh: emit ConfigPublished event
Refresh->>Bus: publish ConfigPublished
Registry-->>Gateway: 202 Accepted
Gateway-->>UI: 202
Bus->>Bus: notify subscribers
end
Hold "Alt" / "Option" to enable pan & zoom
Implementation Pattern:
public class ConfigRegistryService
{
private readonly IConfigRepository _repository;
private readonly IPolicyEngine _policyEngine;
private readonly IEventPublisher _eventPublisher;
public async Task<PublishResult> PublishAsync(
string path,
Guid draftId,
PublishContext context)
{
// Load draft
var draft = await _repository.GetDraftAsync(draftId);
// Validate with policy engine
var validation = await _policyEngine.ValidateAsync(draft, context);
if (!validation.IsValid)
{
throw new ValidationException(validation.Errors);
}
// Create immutable version
var version = await _repository.CreateVersionAsync(draft, context);
// Emit domain event
await _eventPublisher.PublishAsync(new ConfigPublishedEvent
{
ConfigItemId = draft.Id,
Path = path,
Version = version.VersionNumber,
TenantId = context.TenantId,
OccurredAt = DateTime.UtcNow
});
return new PublishResult
{
VersionId = version.Id,
Version = version.VersionNumber,
ETag = version.ETag
};
}
}
API Gateway Routing¶
YARP Configuration¶
{
"ReverseProxy": {
"Routes": [
{
"RouteId": "config-registry",
"Match": {
"Path": "/api/v1/config-sets/{**catch-all}"
},
"ClusterId": "config-registry-cluster",
"Transforms": [
{
"RequestHeader": "X-Tenant-Id",
"Set": "{tenantId}"
},
{
"RequestHeader": "X-Correlation-Id",
"Set": "{correlationId}"
}
]
},
{
"RouteId": "policy-engine",
"Match": {
"Path": "/api/v1/policies/{**catch-all}"
},
"ClusterId": "policy-engine-cluster"
},
{
"RouteId": "refresh-orchestrator",
"Match": {
"Path": "/api/v1/refresh/{**catch-all}"
},
"ClusterId": "refresh-orchestrator-cluster"
}
],
"Clusters": {
"config-registry-cluster": {
"Destinations": {
"destination1": {
"Address": "https://config-registry.svc.cluster.local"
}
},
"HealthCheck": {
"Active": {
"Enabled": true,
"Interval": "00:00:10",
"Path": "/health",
"Policy": "Passive"
}
}
},
"policy-engine-cluster": {
"Destinations": {
"destination1": {
"Address": "https://policy-engine.svc.cluster.local"
}
}
},
"refresh-orchestrator-cluster": {
"Destinations": {
"destination1": {
"Address": "https://refresh-orchestrator.svc.cluster.local"
}
}
}
}
}
}
Envoy Configuration¶
http_filters:
- name: envoy.filters.http.jwt_authn
typed_config:
providers:
ecs:
issuer: "https://id.connectsoft.cloud"
audiences: ["ecs.api", "ecs.admin"]
remote_jwks:
http_uri:
uri: "https://id.connectsoft.cloud/.well-known/jwks.json"
cluster: idp
cache_duration: 600s
rules:
- match:
prefix: "/api/"
requires:
provider_name: "ecs"
- name: envoy.filters.http.ext_authz
typed_config:
grpc_service:
envoy_grpc:
cluster_name: pdp
transport_api_version: V3
failure_mode_allow: false
- name: envoy.filters.http.ratelimit
typed_config:
domain: "ecs"
rate_limit_service:
grpc_service:
envoy_grpc:
cluster_name: rls
Event-Driven Communication¶
CloudEvents Schema¶
ConfigPublished Event:
{
"specversion": "1.0",
"type": "ecs.config.v1.ConfigPublished",
"source": "config-registry",
"id": "uuid",
"time": "2024-01-01T00:00:00Z",
"datacontenttype": "application/json",
"data": {
"configItemId": "uuid",
"path": "api.baseUrl",
"version": 1,
"configSetId": "uuid",
"tenantId": "uuid",
"environment": "production"
},
"ecstenantid": "uuid",
"ecseditionid": "pro",
"ecsenvironment": "production",
"ecspath": "api.baseUrl",
"ecsversion": "1"
}
MassTransit Consumer Pattern¶
public class ConfigRefreshConsumer : IConsumer<ConfigPublishedEvent>
{
private readonly IConfigCache _cache;
private readonly IRefreshNotifier _notifier;
public async Task Consume(ConsumeContext<ConfigPublishedEvent> context)
{
var @event = context.Message;
// Invalidate cache
var cacheKey = $"tenant:{@event.TenantId}:path:{@event.Path}";
await _cache.RemoveAsync(cacheKey);
// Notify subscribers
await _notifier.NotifyAsync(new RefreshNotification
{
ConfigSetId = @event.ConfigSetId,
Paths = new[] { @event.Path },
Version = @event.Version,
TenantId = @event.TenantId
});
}
}
// Registration
services.AddMassTransit(x =>
{
x.AddConsumer<ConfigRefreshConsumer>();
x.UsingAzureServiceBus((context, cfg) =>
{
cfg.Host(connectionString);
cfg.ReceiveEndpoint("config-published", e =>
{
e.ConfigureConsumer<ConfigRefreshConsumer>(context);
});
});
});
Service Discovery¶
Kubernetes Service Discovery¶
apiVersion: v1
kind: Service
metadata:
name: config-registry
namespace: config-platform
spec:
selector:
app: config-registry
ports:
- port: 80
targetPort: 8080
type: ClusterIP
Service Mesh Integration¶
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: config-registry
spec:
hosts:
- config-registry
http:
- match:
- uri:
prefix: "/api/v1/config-sets"
route:
- destination:
host: config-registry
port:
number: 80
timeout: 30s
retries:
attempts: 3
perTryTimeout: 10s
Inter-Service Communication Contracts¶
Registry to Policy Engine¶
Contract:
public interface IPolicyEngineClient
{
Task<ValidationResult> ValidateAsync(
ConfigDraft draft,
ValidationContext context);
Task<ResolvedConfig> ResolveAsync(
ConfigItem config,
ResolutionContext context);
}
gRPC Service Definition:
service PolicyEngine {
rpc Validate(ValidateRequest) returns (ValidateResponse);
rpc Resolve(ResolveRequest) returns (ResolveResponse);
}
message ValidateRequest {
ConfigDraft draft = 1;
ValidationContext context = 2;
}
message ValidateResponse {
bool valid = 1;
repeated ValidationError errors = 2;
}
Registry to Refresh Orchestrator¶
Contract:
public interface IRefreshOrchestratorClient
{
Task PublishRefreshAsync(ConfigPublishedEvent @event);
Task InvalidateCacheAsync(string tenantId, string[] paths);
}
Event-Based Communication:
// Registry publishes event
await _eventPublisher.PublishAsync(new ConfigPublishedEvent
{
ConfigItemId = configItem.Id,
Path = configItem.Key,
Version = configItem.Version,
TenantId = context.TenantId
});
// Refresh Orchestrator consumes event
public class RefreshOrchestratorService
{
public async Task HandleConfigPublishedAsync(ConfigPublishedEvent @event)
{
// Invalidate cache
await _cache.InvalidateAsync(@event.TenantId, new[] { @event.Path });
// Notify subscribers
await _notifier.NotifyAsync(@event);
}
}
Data Flow Patterns¶
Read Path (Hot Path)¶
Client Request
-> API Gateway (auth, rate limit)
-> Redis Cache (check)
-> Config Registry (if cache miss)
-> Policy Engine (resolve overlays)
-> Redis Cache (store)
-> Client Response
Write Path (Warm Path)¶
Client Request
-> API Gateway (auth, rate limit)
-> Config Registry (validate, version)
-> Policy Engine (validate policy)
-> CockroachDB (persist)
-> Event Bus (publish)
-> Refresh Orchestrator (invalidate cache)
-> Client Response (202 Accepted)
References¶
- Solution Architecture - Detailed service design
- API Contract Specification - API endpoints
- Integration Patterns - Client integration patterns