Saltar al contenido principal

Policy Providers

Los policy providers son la forma recomendada de asociar políticas de resiliencia a tus requests. Mantienen la configuración de políticas fuera del modelo de comando/query — tus objetos de dominio se mantienen enfocados en datos, mientras que la infraestructura vive en clases dedicadas o en el registro de startup.

Orden de Resolución​

ResilienceBehavior resuelve la política para cada request en este orden:

PrioridadMecanismoCuándo usarlo
1AddResiliencePolicy<T> / AddResiliencePolicyProvider<T,P>Recomendado — política separada del comando
2IResilient en el comandoObsoleto — solo para compatibilidad
3AddGlobalResiliencePolicyPor defecto para todos los requests sin política específica

Si no se encuentra ninguna política, el request pasa sin ningún envoltorio de resiliencia.


Registro Inline (sin clase)​

Para la mayoría de los casos, una lambda en startup es suficiente:

// Program.cs
services.AddResiliencePolicy<LoginCommand>(req =>
ResiliencePolicy.Create()
.RateLimiter(opts =>
{
opts.Algorithm = RateLimiterAlgorithm.SlidingWindow;
opts.PermitLimit = 5;
opts.Window = TimeSpan.FromMinutes(1);
opts.PartitionKeyResolver = r => ((LoginCommand)r).Email;
})
.Build());

services.AddResiliencePolicy<PlaceOrderCommand>(req =>
ResiliencePolicy.Create("place-order")
.Retry(3)
.CircuitBreaker(opts =>
{
opts.CircuitKey = "payment-gateway";
opts.FailureThreshold = 5;
opts.BreakDuration = TimeSpan.FromSeconds(30);
})
.Timeout(TimeSpan.FromSeconds(10))
.Build());

El comando en sí queda limpio — sin preocupaciones de infraestructura:

public class LoginCommand : IRequest<Result<string>>
{
public string Email { get; init; } = string.Empty;
public string Password { get; init; } = string.Empty;
}

Provider por Clase​

Usá una clase cuando el provider necesita dependencias inyectadas como IOptions, ILogger o feature flags:

public class PlaceOrderPolicyProvider : IResiliencePolicyProvider<PlaceOrderCommand>
{
private readonly ResilienceSettings _settings;

public PlaceOrderPolicyProvider(IOptions<ResilienceSettings> opts)
=> _settings = opts.Value;

public ResiliencePolicy GetPolicy(PlaceOrderCommand request) =>
ResiliencePolicy.Create("place-order")
.Retry(opts =>
{
opts.MaxRetries = _settings.MaxRetries;
opts.BackoffType = BackoffType.ExponentialWithJitter;
opts.RetryOnErrorTypes.Add(ErrorType.Failure);
})
.CircuitBreaker(opts =>
{
opts.CircuitKey = "payment-gateway";
opts.FailureThreshold = _settings.CircuitBreakerThreshold;
opts.BreakDuration = TimeSpan.FromSeconds(_settings.BreakDurationSeconds);
})
.Timeout(TimeSpan.FromSeconds(10))
.Build();
}

// Registro
services.AddResiliencePolicyProvider<PlaceOrderCommand, PlaceOrderPolicyProvider>();
Lifetime

AddResiliencePolicyProvider usa Scoped por defecto. Si el provider no tiene dependencias con scope, pasá ServiceLifetime.Singleton para mejor performance.


Política Global​

Una política global es el fallback para todos los requests que no tienen un provider específico:

// Política fija para todo
services.AddGlobalResiliencePolicy(
ResiliencePolicy.Create()
.Retry(3)
.Timeout(TimeSpan.FromSeconds(30))
.Build());

El overload con factory recibe el objeto request, útil para diferenciar por tipo:

services.AddGlobalResiliencePolicy(req =>
ResiliencePolicy.Create()
.Retry(req is IQuery ? 3 : 1)
.Timeout(TimeSpan.FromSeconds(30))
.Build());
nota

La política global usa AddSingleton internamente.


Ejemplo Completo en Program.cs​

builder.Services.AddValiMediator(config =>
{
config.RegisterServicesFromAssemblyContaining<Program>();
config.AddResilienceBehavior();
});

// Global: retry + timeout para todo
builder.Services.AddGlobalResiliencePolicy(
ResiliencePolicy.Create()
.Retry(3)
.Timeout(TimeSpan.FromSeconds(30))
.Build());

// Login: rate limit por usuario (sobreescribe el global)
builder.Services.AddResiliencePolicy<LoginCommand>(req =>
ResiliencePolicy.Create()
.RateLimiter(opts =>
{
opts.Algorithm = RateLimiterAlgorithm.SlidingWindow;
opts.PermitLimit = 5;
opts.Window = TimeSpan.FromMinutes(1);
opts.PartitionKeyResolver = r => ((LoginCommand)r).Email;
})
.Build());

// Pago: circuit breaker vía clase (necesita IOptions)
builder.Services.AddResiliencePolicyProvider<PlaceOrderCommand, PlaceOrderPolicyProvider>();