Skip to content

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