ums

ADR-0060: Estrategia de Concerns Cross-Cutting AOP — DispatchProxy sobre MediatR Behaviors

Estado

Aceptado

Fecha

2026-05-24

Responsable de Decisión

Arquitectura

Relacionados


Contexto

Los command handlers de UMS necesitan concerns cross-cutting estructurados: logging de entrada/salidacon duración, distributed tracingcon tags de tenant, métricas (señales RED), ycaptura de excepciones. Estos concerns deben ser:

  1. Selectivos — aplicados por-handler o por-método, no uniformemente a todos los requests.
  2. No invasivos — cero cambios a la lógica de negocio del handler.
  3. Async-correct — firing hooks después del resultado awaited, no cuando el objeto Task es retornado.
  4. Testeables en aislamiento — handlers unit-testados sin infraestructura cross-cutting.

UMS ya usa MediatR IPipelineBehavior<TRequest, TResponse> para concernsuniformesde pipeline (ValidationBehavior). La pregunta es si extender ese mecanismo para concerns cross-cutting o adoptar un modelo diferente.

Alternativas consideradas

Opción Mecanismo Selectivo? Async-correct? Dep. externa? Decisión  
A MediatR IPipelineBehavior<,> all-or-nothing por tipo     Rechazado para cross-cutting  
B Clases decorator por handler manual     Rechazado — O(n) boilerplate  
C Castle.DynamicProxy / Autofac interceptors     nuevo NuGet requerido Rechazado — stack pollution  
D BeyondNetCode.Shell.Aop + System.Reflection.DispatchProxy attribute-driven (after fix) owned shell lib Adoptado #### Por qué MediatR IPipelineBehavior no fue suficiente

IPipelineBehavior<TRequest, TResponse> aplica a cada comando que coincide con su constraint de tipo. Este es el modelo correcto para concernsuniformes (validación, idempotencia) pero crea acoplamiento inaceptable para concernsselectivos:

Resolución:Los behaviors de MediatR permanecen como el mecanismo canónico para concerns de pipeline uniformes. BeyondNetCode.Shell.Aop es el mecanismo canónico para decoración selectiva, por-método.

Por qué se eligió BeyondNetCode.Shell.Aop sobre una nueva dependencia NuGet


DecisiónAdoptar BeyondNetCode.Shell.Aop con System.Reflection.DispatchProxy como el mecanismo para concerns cross-cutting selectivos, por-método en los command handlers de UMS.

1. Separación de responsabilidades

Concern Mecanismo Aplica a  
Validación de input ValidationBehavior (MediatR) Todos los commands uniformemente  
Idempotencia IdempotencyMiddleware (HTTP) Todos los endpoints mutantes  
Logging (selectivo) LoggerAspect vía BeyondNetCode.Shell.Aop Por-handler, opt-in vía [LoggerAspect]  
Tracing (Fase 2) TracingAspect vía BeyondNetCode.Shell.Aop Por-handler, opt-in vía [Tracing]  
Métricas (Fase 2) MetricsAspect vía BeyondNetCode.Shell.Aop Por-handler, opt-in vía [Metrics]  
Retry (selectivo) RetryAspect vía BeyondNetCode.Shell.Aop Por-método, opt-in vía [RetryAspect] ### 2. Fix async de proxy — prerrequisito obligatorio

System.Reflection.DispatchProxy.Invoke es síncrono. Antes de este ADR, OnMethodBoundaryAspect.OnSuccess y OnExit disparaban cuando un Task era retornado, no cuando completaba. Esto causaba que los hooks observaran estado incompleto.

Fix (implementado en BeyondNetCode.Shell.Aop/Impl/OnMethodBoundaryAspect.cs):Después de joinPoint.Proceed(), detectar tipos de retorno Task / Task<TResult> y envolvarlos en continuation tasks vía ConfigureAwait(false). El path original Task<TResult> se maneja vía un MethodInfo genérico cacheado (WrapAsyncOfT<TResult>) para preservar el valor de resultado. El bloque síncrono finally { OnExit() } se skippea para paths async para prevenir double-firing.

3. Alcance de adopción

Fase 1 (implementada — 2026-05-24)

Fase 2 (planificada)

Fase 3 (consideración futura)

4. Política PII para logging aspects

Logger Valores de argumentos logueados Cuando usar  
MelLogger Nunca — solo nombres/tipos Default; todos los handlers  
SerilogLogger Destructureados (opt-in) Solo después de revisión y aprobación de PII explícita [LoggerAspect(LogArguments = [])] (array vacío) es el default PII-safe y debe setearse en todos los handlers a menos que un argumento específico sea revisado y clearance.

5. Referencias de capa introducidas

Ums.Application.csproj
 └── BeyondNetCode.Shell.Aspects ← contrato de atributos solo ([LoggerAspect], etc.)

Ums.Infrastructure.csproj
 ├── BeyondNetCode.Shell.DI ← AddAop(), AddAopProxy<>()
 └── BeyondNetCode.Shell.Logger.Serilog ← Adaptador SerilogLogger

Ums.Domain noreferencia ningún proyecto BeyondNetCode.Shell.Aop. La pureza del Domain se preserva.


Consecuencias

Positivas

Trade-offs

No-decisiones


Cumplimiento

Las siguientes verificaciones son obligatorias después de cualquier cambio a un aspecto AOP o registro de proxy AOP:

# Build de la solución completa
dotnet build src/apps/ums.api/Ums.sln

# Ejecutar todos los test suites
dotnet test src/apps/ums.api/Ums.sln --verbosity minimal
dotnet test src/libs/shell/aop/src/BeyondNetCode.Shell.Aop.Tests/BeyondNetCode.Shell.Aop.Tests.csproj --verbosity minimal

# Verificar que no hay violación de pureza del Domain
grep -r "BeyondNetCode.Shell.Aop" src/apps/ums.api/Ums.Domain/ --include="*.csproj"
# Esperado: sin output

Índice ADR Guía de Desarrollo AOP ADR-0053 OpenTelemetry ADR-0054 Aislamiento de Shell