Saltar al contenido principal

Guía de Migración

v2.x → v3.0​

Core Vali-Mediator 3.0.0; AspNetCore, Caching, Idempotency, Observability y Resilience 2.0.0. Las extensiones llaman a la nueva firma IPipelineBehavior.Handle(..., Func<CancellationToken, Task<T>> next), así que solo funcionan contra el core 3.x — actualiza el core y las cinco extensiones a la vez. Una extensión antigua contra el core 3.x falla en runtime con MissingMethodException. Usa [3.0.0, 4.0.0) como rango de dependencia recomendado.

1. Cambio obligatorio: next recibe un CancellationToken​

// 2.x
public async Task<TResponse> Handle(TRequest request, Func<Task<TResponse>> next, CancellationToken ct)
=> await next();

// 3.0
public async Task<TResponse> Handle(TRequest request, Func<CancellationToken, Task<TResponse>> next, CancellationToken ct)
=> await next(ct); // o un token enlazado si el behavior cancela por su cuenta

Igual para IPipelineBehavior<TDispatch>: Func<CancellationToken, Task> next. Las lambdas que ignoraban el token pasan a _ => ....

2. Cambios de comportamiento​

ÁreaAntesAhoraAcción
Publish<T>Solo corrían los handlers del tipo estáticoUnión de handlers del tipo estático y del tipo en runtime (sin duplicados)Revisa handlers sobre tipos base/INotification que antes no corrían
Escaneo de assembliesEscanear dos veces duplicaba handlers; ganaba el primer registroUn solo registro; gana el último lifetimeNinguna, salvo que dependieras del primer registro
default(Result) / default(Result<T>)Error = null, ErrorType.NoneFallo explícito "no inicializado", ErrorType.Failure. Fail("x", ErrorType.None) conserva NoneNo uses default como éxito
TimeoutBehaviorEl handler seguía corriendo tras el timeoutCancela el token del handler; TimeoutException envuelve la excepción del handler como InnerExceptionPropaga el token en tus operaciones
Excepciones de handlersEnvueltas en TargetInvocationExceptionTipo y stack originalesElimina los catch (TargetInvocationException)
Resilience: ordenFallback → Retry → Timeout → CB → Bulkhead → Hedge → RateLimiter → ChaosFallback → Chaos → RateLimiter → Retry → Timeout → CB → Bulkhead → HedgeUn rechazo del rate limiter ya no se reintenta ni cuenta contra el circuit breaker
Resilience: resolución de policyCacheada por tipo (gana la primera request)Se resuelve por requestReutiliza una instancia, o usa .WithSharedState("clave") para compartir estado de Bulkhead/RateLimiter/CB entre policies construidas por request
Resilience: HedgeDevolvía default si todos los intentos fallabanLanza la última excepción; los perdedores se cancelanManeja la excepción
Resilience: opcionesValores inválidos se tolerabanArgumentOutOfRangeException en .Build()Corrige valores fuera de rango
Idempotency: formato de claveIdempotencyKeyType#len:scope#key; las entradas antiguas se tratan como missEl handler se ejecuta una vez más justo después del upgrade
Idempotency: conflicto de payloadLa misma clave siempre devolvía la respuesta guardadaMisma clave + payload distinto → Conflict / IdempotencyConflictException (desactivable con VerifyRequestFingerprint = false)Evita campos volátiles (timestamps, correlation ids) en el request
Idempotency: Expiration == nullSin expiraciónExpiración por defecto del store (24 h)Define Expiration explícito si querés otro valor
Idempotency / CachingLas respuestas Result fallidas se guardabanNunca se guardan ni invalidanNinguna
AspNetCoreFailure (500) incluía Result.Error en DetailMensaje genérico; ResultHttpOptions.ExposeErrorDetails = true lo restauraActívalo solo en desarrollo
ObservabilityUn error de observer se propagaba como AggregateExceptionAislado; visible vía la Activity / IMetricsCollector.RecordObserverError. Mensaje de excepción oculto salvo IncludeExceptionMessage = trueSuscribite al metrics collector si necesitabas verlo

3. Límites nuevos y sus valores por defecto​

OpciónDefaultDónde
InMemoryCacheOptions.MaxEntries10.000 (antes 1.000)Caching
InMemoryCacheOptions.MaxKeyLength / MaxGroups / MaxKeysPerGroup512 / 10.000 / 10.000Caching
InMemoryCacheOptions.CleanupInterval5 minCaching
CachingOptions.CoalescingWaitTimeout30 s (Timeout.InfiniteTimeSpan permitido)Caching
IdempotencyOptions.MaxKeyLength / VerifyRequestFingerprint256 / trueIdempotency
InMemoryIdempotencyStoreOptions.MaxEntries / DefaultExpiration10.000 / 24 hIdempotency
RateLimiterOptions.MaxPartitions / PartitionIdleTimeout10.000 / 5 minResilience
WithSharedState(key)tope de 10.000 clavesResilience
IValiMediator.SendAll(requests, maxDegreeOfParallelism, ct)sin límite si se omiteCore
InMemoryDeadLetterQueue(maxEntries)1.000Core
ObservabilityOptions.IncludeExceptionMessagefalseObservability
ResultHttpOptions.ExposeErrorDetailsfalseAspNetCore

Todos los límites se validan (lanzan ArgumentOutOfRangeException si son inválidos) y se configuran desde el registro DI del paquete correspondiente.

4. Checklist​

  1. Actualizá Vali-Mediator a 3.0.0 y las cinco extensiones a 2.0.0.
  2. Actualizá tus propios IPipelineBehavior para que llamen a next(ct).
  3. Revisá la tabla de cambios de comportamiento contra tu uso real.
  4. Corré tus pruebas. Si usás Idempotency, esperá una re-ejecución por clave justo después del deploy (cambió el formato de clave).

v1.x → v2.0​

La versión 2.0 introduce varios cambios que rompen la compatibilidad junto con nuevas características. Sigue esta guía para actualizar.

Cambios que Rompen Compatibilidad​

1. IPreProcessor e IPostProcessor ahora retornan Task​

Antes (v1.x):

public class MyPreProcessor : IPreProcessor<MyRequest>
{
public void Process(MyRequest request, CancellationToken ct)
{
// implementación síncrona
}
}

Después (v2.0):

public class MyPreProcessor : IPreProcessor<MyRequest>
{
public async Task Process(MyRequest request, CancellationToken ct)
{
// implementación async
await Task.CompletedTask;
}
}

Para implementaciones síncronas, retorna Task.CompletedTask:

public Task Process(MyRequest request, CancellationToken ct)
{
// trabajo síncrono aquí
return Task.CompletedTask;
}

2. INotificationHandler.Priority ahora tiene implementación por defecto​

Antes (v1.x): Priority era una propiedad abstracta — cada handler debía sobreescribirla.

Después (v2.0): Priority tiene implementación por defecto => 0 — sobreescribe solo cuando es necesario.

// v1.x — requerido
public class MyHandler : INotificationHandler<MyEvent>
{
public int Priority => 0; // debía estar aquí
public async Task Handle(MyEvent n, CancellationToken ct) { ... }
}

// v2.0 — Priority puede omitirse si es 0
public class MyHandler : INotificationHandler<MyEvent>
{
public async Task Handle(MyEvent n, CancellationToken ct) { ... }
}

Nuevas Características en v2.0​

Patrón Result​

// Nuevo en v2.0 — usa Result<T> en lugar de lanzar excepciones
public async Task<Result<ProductDto>> Handle(GetProductQuery req, CancellationToken ct)
{
var product = await _repo.FindAsync(req.Id, ct);
if (product is null)
return Result<ProductDto>.Fail("No encontrado.", ErrorType.NotFound);
return Result<ProductDto>.Ok(product.ToDto());
}

SendOrDefault​

// Nuevo en v2.0 — retorna default en lugar de lanzar HandlerNotFoundException
ProductDto? product = await _mediator.SendOrDefault(new GetProductQuery(id));

SendAll​

// Nuevo en v2.0 — ejecución paralela
Result<ProductDto>[] results = await _mediator.SendAll(queries);

Streaming​

// Nuevo en v2.0
IAsyncEnumerable<ProductDto> stream = _mediator.CreateStream(new GetProductsStream());

ResilientParallel + Cola de Mensajes Muertos​

// Nuevo en v2.0
await _mediator.Publish(notification, PublishStrategy.ResilientParallel);

IHasTimeout​

// Nuevo en v2.0 — timeout declarativo por solicitud
public record SlowQuery() : IRequest<Result<string>>, IHasTimeout
{
public int TimeoutMs => 5000;
}

Pasos de Migración​

  1. Actualizar paquetes a la versión 2.0.0
  2. Corregir IPreProcessor/IPostProcessor — cambiar void Process(...) a Task Process(...)
  3. Eliminar overrides obligatorios Priority => 0 donde no sea necesario (limpieza opcional)
  4. Adoptar Result<T> — refactorizar handlers que lanzan excepciones de negocio
  5. Instalar paquetes de extensión según sea necesario (Resilience, Caching, etc.)
  6. Agregar behaviors al DI — config.AddCachingBehavior(), etc.
tip

La migración puede hacerse incrementalmente. Los handlers sin Result<T> siguen funcionando. Adopta el patrón Result handler por handler.