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.
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 handlersO 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
- MediatR — mediador concreto, helpers de registro en DI
- MediatR — Guía de uso