Saltar al contenido principal

MediatR

🌍 Idioma: English | Español

Mensajería en proceso para Delphi — comandos, consultas y notificaciones despachados a través de una única interfaz IMediator. Inspirado en la biblioteca .NET MediatR.

Delphi 12+ Licencia


Conceptos

Tipo Dirección Devuelve Caso de uso
IRequest 1 → 1 handler — Comando (fire-and-forget)
IRequest<TResponse> 1 → 1 handler TResponse Consulta
INotification 1 → N handlers — Evento de dominio

Inicio rápido — Comando

// 1. Definir comando
type
  TCreateOrderCommand = class(TRequest)
    OrderId: Integer;
  end;

// 2. Implementar handler
type
  TCreateOrderHandler = class(TRequestHandler<TCreateOrderCommand>)
    procedure Handle(Request: TCreateOrderCommand); override;
  end;

// 3. Registrar handler
Services.AddTransient<IRequestHandler<TCreateOrderCommand>, TCreateOrderHandler>;

// 4. Despachar
Mediator.Send<TCreateOrderCommand>(Cmd);

Inicio rápido — Consulta

type
  TGetOrderQuery    = class(TRequest<TOrderDTO>);
  TGetOrderHandler  = class(TResponseHandler<TOrderDTO, TGetOrderQuery>)
    procedure Handle(Request: TGetOrderQuery; out Result: TOrderDTO); override;
  end;

var DTO := Mediator.Send<TOrderDTO, TGetOrderQuery>(Query);

Inicio rápido — Notificación

type
  TOrderCreatedEvent    = class(TNotification);
  TEmailNotifyHandler   = class(TNotificationHandler<TOrderCreatedEvent>)
    procedure Handle(Notification: TOrderCreatedEvent); override;
  end;

// Registrar tantos handlers como sea necesario
Services.AddTransient<INotificationHandler<TOrderCreatedEvent>, TEmailNotifyHandler>;
Services.AddTransient<INotificationHandler<TOrderCreatedEvent>, TAuditLogHandler>;

// Publicar — se llama a TODOS los handlers
Mediator.Publish<TOrderCreatedEvent>(Evt);

Registro en DI

uses Daf.Rtti, Daf.MediatR.DependencyInjection;

Services.AddMediatR;                                   // registra IMediatorImpl como transient
Services.AddMediatRClasses(_T.PackageOf<TMyHandler>);  // descubre y registra los handlers

O manualmente por handler:

Services.AddTransient<IRequestHandler<TMyCommand>, TMyCommandHandler>;

Behaviors de pipeline

Los behaviors envuelven cada llamada Send con lógica transversal (logging, validación, caché, etc.).

Hay tres variantes — todas usan el mismo patrón Next.Call:

Clase base Se aplica a Tipo de Next Sobreescribe
TPipelineBehavior toda request TNextValue function Handle(Request: TObject; Next: TNextValue): TValue
TPipelineBehavior<TReq> un tipo de request void TNext procedure Handle(Request: TReq; Next: TNext)
TPipelineBehavior<TRes, TReq> un tipo de request con respuesta TNext<TRes> function Handle(Request: TReq; Next: TNext<TRes>): TRes

Behavior global (todas las requests)

type
  TLoggingBehavior = class(TPipelineBehavior)
  public
    function Handle(Request: TObject; Next: TNextValue): TValue; override;
  end;

function TLoggingBehavior.Handle(Request: TObject; Next: TNextValue): TValue;
begin
  Log('Antes de ' + Request.ClassName);
  Result := Next.Call;
  Log('Después de ' + Request.ClassName);
end;

Behavior tipado void (un tipo IRequest)

type
  TValidationBehavior = class(TPipelineBehavior<TCreateOrderCommand>)
  public
    procedure Handle(Request: TCreateOrderCommand; Next: TNext); override;
  end;

procedure TValidationBehavior.Handle(Request: TCreateOrderCommand; Next: TNext);
begin
  if Request.Name.IsEmpty then
    Exit;  // cortocircuito — el handler nunca se llama
  Next.Call;
end;

Behavior tipado con respuesta (un tipo IRequest<TResponse>)

type
  TQueryResultBehavior = class(TPipelineBehavior<TOrderList, TGetOrdersQuery>)
  public
    function Handle(Request: TGetOrdersQuery; Next: TNext<TOrderList>): TOrderList; override;
  end;

function TQueryResultBehavior.Handle(Request: TGetOrdersQuery; Next: TNext<TOrderList>): TOrderList;
begin
  Result := Next.Call;
  Log(Format('%d pedido(s) devueltos', [Result.Count]));
end;

El framework usa RTTI para detectar el tipo concreto de request en Handle y omite el behavior para tipos no coincidentes. Los behaviors tipados también se aplican a subclases.

Registro

// Auto-descubrimiento (omite [MediatorAbstract])
ServiceCollection.AddMediatRBehaviors(_T.PackageOf<TMyClass>);

// Manual
MediatR.AddBehavior(ServiceCollection, TLoggingBehavior);
MediatR.AddBehavior(ServiceCollection, TValidationBehavior);

Los behaviors tienen su propio escaneo: AddMediatRClasses descubre handlers y nada más, así que un paquete registrado solo con él no aporta ningún behavior — y nadie lo avisa, porque una request sin behaviors registrados va directa a su handler.

Usa [MediatorAbstract] en behaviors que solo registres manualmente para excluirlos del auto-descubrimiento.

Orden de ejecución

Los behaviors se ejecutan en orden de registro — el primero registrado es el envoltorio más externo.

Cortocircuito

No llamar a Next.Call aborta el pipeline sin invocar el handler:

procedure TValidationBehavior.Handle(Request: TCreateOrderCommand; Next: TNext);
begin
  if not IsValid(Request) then
    Exit;  // el handler nunca se llama
  Next.Call;
end;

Manejo de excepciones

Envuelve Next.Call en un try/except para capturar las excepciones del handler y de los behaviors internos:

function TErrorBehavior.Handle(Request: TObject; Next: TNextValue): TValue;
begin
  try
    Result := Next.Call;
  except on E: Exception do
  begin
    Log('Error: ' + E.Message);
    Result := Default(TValue);
  end;
  end;
end;

Inyección de dependencias

Los behaviors se resuelven del contenedor de DI. Los parámetros del constructor se inyectan automáticamente:

type
  TLoggingBehavior = class(TPipelineBehavior)
  private
    FLogger: ILogger;
  public
    constructor Create(const Logger: ILogger);
    function Handle(Request: TObject; Next: TNextValue): TValue; override;
  end;

Compatibilidad hacia atrás

Los behaviors de pipeline son opcionales. Si no hay ninguno registrado, Send invoca el handler directamente y sin coste adicional.


Documentación

  • 📖 Guía de uso — IMediatorHelper, respuestas ARC, pipeline, [MediatorAbstract], escaneo de DI
  • 📁 MediatRSample — comandos, consultas y notificaciones en un solo programa

Abstracciones

MediatR.Abstractions

🌍 Idioma: English | Español

Interfaces y clases base fundamentales para la mensajería en proceso MediatR de DAFce. Referencia estas desde el código de aplicación y las implementaciones de handlers sin depender del mediador concreto.

Todos los tipos se encuentran en Daf.MediatR.Contracts — el paquete es Daf.MediatR.Abstractions, que no es una unit y no vale en un uses.


Interfaces

IRequest

Comando fire-and-forget — despachado a exactamente un handler, sin valor de retorno.

IRequest = interface
end;

IRequest<TResponse>

Consulta — despachada a exactamente un handler, devuelve TResponse.

IRequest<TResponse> = interface(IRequest)
end;

INotification

Evento — publicado a todos los handlers registrados.

INotification = interface
end;

IMediator

IMediator = record            // un record, no una interfaz: envuelve un IMediatorImpl
  procedure Send<TRequest: class, IRequest>(Request: TRequest); overload;
  function  Send<TResponse; TRequest: class, IRequest<TResponse>>(
              Request: TRequest): TResponse; overload;
  procedure Publish<TNotification: class, INotification>(
              Notification: TNotification);
end;

IMediatorHelper

Un record helper de IMediator, así que SendARC está sobre el propio mediador. Envuelve una respuesta de clase en ARC<TResponse>, que se libera al salir del scope.

IMediatorHelper = record helper for IMediator
  function SendARC<TResponse: class; TRequest: class, IRequest<TResponse>>(
             const Request: TRequest): ARC<TResponse>;
end;

IRequestHandler<TRequest>

IRequestHandler<TRequest: class, IRequest> = interface
  procedure Handle(Request: TRequest);
end;

IRequestHandler<TResponse, TRequest>

El mismo nombre con dos parámetros genéricos: Delphi distingue las dos sobrecargas por aridad. La respuesta vuelve por un parámetro out, no como resultado de la función.

IRequestHandler<TResponse; TRequest: class, IRequest<TResponse>> = interface
  procedure Handle(Request: TRequest; out Result: TResponse);
end;

INotificationHandler<TNotification>

INotificationHandler<TNotification: class, INotification> = interface
  procedure Handle(Notification: TNotification);
end;

Clases base

Clase Uso
TRequest Hereda para definir un comando
TRequest<TResponse> Hereda para definir una consulta
TNotification Hereda para definir un evento
TRequestHandler<TReq> Hereda para manejar un comando — sobreescribe Handle
TResponseHandler<TRes,TReq> Hereda para manejar una consulta — sobreescribe Handle
TNotificationHandler<TNot> Hereda para manejar un evento — sobreescribe Handle

Atributos

[MediatorAbstract]

Aplicar a clases base abstractas de handlers para que el escaneo de DI las salte: AddMediatRClasses y AddMediatRBehaviors no registran nunca una clase que lo lleve, que es lo que evita pedirle al contenedor una clase abstracta. Sin escaneo, el atributo no hace nada.

[MediatorAbstract]
TBaseOrderHandler<T: TRequest> = class(TRequestHandler<T>);

Relacionado