Skip to content

ImagFlow — Provider Gateway

Proposito

El Provider Gateway es la capa de abstraccion entre ImagFlow y los proveedores externos. Implementa el Adapter Pattern para que cada tipo de servicio tenga una interfaz comun, independiente del proveedor concreto. Incluye failover configurable, circuit breaker y metricas.

Arquitectura

Adapter Pattern

Interface Base

csharp
public interface IProviderAdapter
{
    string ServiceType { get; }    // "liveness", "ocr", "biometric", etc.
    string ProviderCode { get; }   // "provider-a-liveness", "firmalo-sign"
    
    Task<ProviderResponse> ExecuteAsync(
        ProviderRequest request, CancellationToken ct = default);
    
    Task<bool> HealthCheckAsync(CancellationToken ct = default);
}

public record ProviderRequest(
    string ServiceType,
    string ProviderCode,
    Dictionary<string, object> Data,
    Dictionary<string, object> Options
);

public record ProviderResponse(
    bool Success,
    Dictionary<string, object> Data,
    Dictionary<string, object> Metadata,
    int DurationMs,
    string? ErrorCode = null,
    string? ErrorMessage = null
);

Tipos de Servicio

TipoDescripcionDatos de entradaDatos de salida
livenessVerificacion facial en vivoImagen, opcionesIsLive, score, session_id
ocrExtraccion de datos de documentoImagen frente/reverso, tipo docCampos extraidos, score
biometricMatch facial (selfie vs documento)Imagen probe, imagen referenciaIsMatch, score
signatureFirma digital/electronicaDocumento, firmante, tipo firmaDocumento firmado, certificado
demographicConsulta datos demograficosIdentificador del sujetoDatos personales, estado
registryValidacion contra registros oficialesIdentificador, tipo consultaEstado, datos validados
smsEnvio de SMSTelefono, mensajeDelivery status
storageAlmacenamiento de archivosArchivo, metadataURL, storage_id

Registro de Proveedores

csharp
// Cada proveedor se registra con su service_type y provider_code
services.AddKeyedScoped<IProviderAdapter, FirmaloSignatureAdapter>("firmalo-sign");
services.AddKeyedScoped<IProviderAdapter, UanatacaSignatureAdapter>("uanataca-sign");
services.AddKeyedScoped<IProviderAdapter, CerticamaraSignatureAdapter>("certicamara-sign");
services.AddKeyedScoped<IProviderAdapter, GenericLivenessAdapter>("generic-liveness");

Para agregar un nuevo proveedor:

  1. Implementar IProviderAdapter
  2. Registrar en DI con su provider_code
  3. Crear PROVIDER_CONFIG en la base de datos

Failover

Configuracion

Cada proveedor puede tener un proveedor de failover configurado:

json
{
  "provider_config_id": "uuid-primary",
  "service_type": "ocr",
  "provider_code": "provider-a-ocr",
  "connection_config": {
    "base_url": "https://api.provider-a.com",
    "api_key": "encrypted:...",
    "timeout_ms": 30000
  },
  "failover_provider_id": "uuid-secondary",
  "failover_conditions": {
    "trigger_on": ["timeout", "5xx", "connection_error"],
    "timeout_ms": 30000,
    "max_retries_before_failover": 1,
    "error_codes": [500, 502, 503, 504]
  }
}

Flujo de Failover

Implementacion

csharp
public class FailoverHandler
{
    public async Task<ProviderResponse> ExecuteWithFailoverAsync(
        ProviderConfig config, ProviderRequest request, CancellationToken ct)
    {
        // Intentar proveedor primario
        var primaryAdapter = _adapterFactory.Resolve(config.ProviderCode);
        
        try
        {
            var response = await _circuitBreaker.ExecuteAsync(
                config.ProviderCode,
                () => primaryAdapter.ExecuteAsync(request, ct));

            if (response.Success) return response;

            // Evaluar si debe hacer failover
            if (!ShouldFailover(response, config.FailoverConditions))
                return response;
        }
        catch (Exception ex) when (IsFailoverTrigger(ex, config.FailoverConditions))
        {
            // Timeout o error de conexion - intentar failover
        }

        // Failover
        if (config.FailoverProviderId is null)
            return ProviderResponse.Failure("NO_FAILOVER", "Primary failed, no failover configured");

        var failoverConfig = await _configRepo.GetByIdAsync(config.FailoverProviderId.Value, ct);
        var failoverAdapter = _adapterFactory.Resolve(failoverConfig.ProviderCode);

        var failoverResponse = await failoverAdapter.ExecuteAsync(request, ct);
        failoverResponse.Metadata["failover_used"] = true;
        failoverResponse.Metadata["primary_provider"] = config.ProviderCode;

        return failoverResponse;
    }
}

Circuit Breaker

Configuracion

ParametroDefaultDescripcion
failure_threshold5Fallos consecutivos para abrir
recovery_timeout_seconds30Tiempo en Open antes de HalfOpen
success_threshold2Exitos en HalfOpen para cerrar

Implementacion (Polly)

csharp
// Circuit breaker por proveedor
services.AddHttpClient(providerCode)
    .AddPolicyHandler(Policy
        .Handle<HttpRequestException>()
        .OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
        .CircuitBreakerAsync(
            handledEventsAllowedBeforeBreaking: 5,
            durationOfBreak: TimeSpan.FromSeconds(30)));

Metricas

MetricaTipoLabelsDescripcion
provider_request_duration_msHistogramservice_type, provider_codeLatencia por proveedor
provider_request_totalCounterservice_type, provider_code, statusTotal requests
provider_failover_triggeredCounterservice_type, primary, secondaryFailovers activados
provider_circuit_breaker_stateGaugeprovider_code0=closed, 1=open, 2=half_open
provider_error_totalCounterprovider_code, error_typeErrores por tipo

Seguridad

Almacenamiento de Credenciales

  • connection_config en provider_configs se almacena cifrado con AWS KMS
  • Solo el Provider Gateway tiene acceso a la CMK de descifrado
  • Las credenciales nunca se loguean ni se incluyen en eventos
  • Rotacion manual (depende del proveedor externo)

Comunicacion con Proveedores

  • TLS 1.2+ obligatorio
  • Timeout configurable por proveedor (default 30s)
  • No se envian datos del tenant al proveedor (solo datos del sujeto necesarios)
  • Respuestas se sanitizan antes de almacenar (remover headers sensibles)

Agregar un Nuevo Proveedor

Paso 1: Implementar el Adapter

csharp
public class NuevoProveedorOcrAdapter : IProviderAdapter
{
    public string ServiceType => "ocr";
    public string ProviderCode => "nuevo-proveedor-ocr";

    private readonly HttpClient _httpClient;
    private readonly ILogger<NuevoProveedorOcrAdapter> _logger;

    public async Task<ProviderResponse> ExecuteAsync(
        ProviderRequest request, CancellationToken ct)
    {
        // Implementacion especifica del proveedor
        var apiResponse = await _httpClient.PostAsync(...);
        
        return new ProviderResponse(
            Success: apiResponse.IsSuccess,
            Data: MapResponseData(apiResponse),
            Metadata: new() { ["raw_score"] = apiResponse.Score },
            DurationMs: (int)stopwatch.ElapsedMilliseconds
        );
    }

    public async Task<bool> HealthCheckAsync(CancellationToken ct)
    {
        var response = await _httpClient.GetAsync("/health", ct);
        return response.IsSuccessStatusCode;
    }
}

Paso 2: Registrar en DI

csharp
services.AddKeyedScoped<IProviderAdapter, NuevoProveedorOcrAdapter>("nuevo-proveedor-ocr");

Paso 3: Crear configuracion en BD

sql
INSERT INTO provider_configs (id, service_type, provider_name, provider_code, connection_config, is_active)
VALUES (
    gen_random_uuid(),
    'ocr',
    'Nuevo Proveedor OCR',
    'nuevo-proveedor-ocr',
    '{"base_url": "https://api.nuevo.com", "api_key": "encrypted:..."}',
    true
);

Paso 4: Asignar a un paso del flujo

El platform admin selecciona el proveedor al configurar un paso en el draft del flujo.

Reimagine Tech LLC — Documentacion Interna