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
| Tipo | Descripcion | Datos de entrada | Datos de salida |
|---|---|---|---|
liveness | Verificacion facial en vivo | Imagen, opciones | IsLive, score, session_id |
ocr | Extraccion de datos de documento | Imagen frente/reverso, tipo doc | Campos extraidos, score |
biometric | Match facial (selfie vs documento) | Imagen probe, imagen referencia | IsMatch, score |
signature | Firma digital/electronica | Documento, firmante, tipo firma | Documento firmado, certificado |
demographic | Consulta datos demograficos | Identificador del sujeto | Datos personales, estado |
registry | Validacion contra registros oficiales | Identificador, tipo consulta | Estado, datos validados |
sms | Envio de SMS | Telefono, mensaje | Delivery status |
storage | Almacenamiento de archivos | Archivo, metadata | URL, 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:
- Implementar
IProviderAdapter - Registrar en DI con su
provider_code - Crear
PROVIDER_CONFIGen 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
| Parametro | Default | Descripcion |
|---|---|---|
failure_threshold | 5 | Fallos consecutivos para abrir |
recovery_timeout_seconds | 30 | Tiempo en Open antes de HalfOpen |
success_threshold | 2 | Exitos 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
| Metrica | Tipo | Labels | Descripcion |
|---|---|---|---|
provider_request_duration_ms | Histogram | service_type, provider_code | Latencia por proveedor |
provider_request_total | Counter | service_type, provider_code, status | Total requests |
provider_failover_triggered | Counter | service_type, primary, secondary | Failovers activados |
provider_circuit_breaker_state | Gauge | provider_code | 0=closed, 1=open, 2=half_open |
provider_error_total | Counter | provider_code, error_type | Errores por tipo |
Seguridad
Almacenamiento de Credenciales
connection_configenprovider_configsse 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.