MediatR
π Language: English | EspaΓ±ol
In-process messaging for Delphi β commands, queries, and notifications dispatched through a single IMediator interface. Inspired by the .NET MediatR library.
Concepts
| Type | Direction | Returns | Use case |
|---|---|---|---|
IRequest |
1 β 1 handler | β | Command (fire-and-forget) |
IRequest<TResponse> |
1 β 1 handler | TResponse |
Query |
INotification |
1 β N handlers | β | Domain event |
Quick Start β Command
// 1. Define command
type
TCreateOrderCommand = class(TRequest)
OrderId: Integer;
end;
// 2. Implement handler
type
TCreateOrderHandler = class(TRequestHandler<TCreateOrderCommand>)
procedure Handle(Request: TCreateOrderCommand); override;
end;
// 3. Register handler
Services.AddTransient<IRequestHandler<TCreateOrderCommand>, TCreateOrderHandler>;
// 4. Dispatch
Mediator.Send<TCreateOrderCommand>(Cmd);Quick Start β Query
type
TGetOrderQuery = class(TRequest<TOrderDTO>);
TGetOrderHandler = class(TResponseHandler<TOrderDTO, TGetOrderQuery>)
function Handle(Request: TGetOrderQuery): TOrderDTO; override;
end;
var DTO := Mediator.Send<TOrderDTO, TGetOrderQuery>(Query);Quick Start β Notification
type
TOrderCreatedEvent = class(TNotification);
TEmailNotifyHandler = class(TNotificacionHandler<TOrderCreatedEvent>)
procedure Handle(Notification: TOrderCreatedEvent); override;
end;
// Register as many handlers as needed
Services.AddTransient<INotificationHandler<TOrderCreatedEvent>, TEmailNotifyHandler>;
Services.AddTransient<INotificationHandler<TOrderCreatedEvent>, TAuditLogHandler>;
// Publish β ALL handlers are called
Mediator.Publish<TOrderCreatedEvent>(Evt);DI Registration
uses Daf.MediatR.DependencyInjection;
Services.AddMediatR; // registers IMediator singleton + scans for handlersOr manually per handler:
Services.AddTransient<IRequestHandler<TMyCommand>, TMyCommandHandler>;Documentation
Pipeline Behaviors
Behaviors wrap every Send call with cross-cutting logic (logging, validation, caching, etc.).
There are three behavior flavors β all use the same Next.Call pattern:
| Base class | Applies to | Next type |
Override |
|---|---|---|---|
TPipelineBehavior |
every request | TNextValue |
function Handle(Request: TObject; Next: TNextValue): TValue |
TPipelineBehavior<TReq> |
one void request type | TNext |
procedure Handle(Request: TReq; Next: TNext) |
TPipelineBehavior<TRes, TReq> |
one response request type | TNext<TRes> |
function Handle(Request: TReq; Next: TNext<TRes>): TRes |
Global behavior (all 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('Before ' + Request.ClassName);
Result := Next.Call;
Log('After ' + Request.ClassName);
end;Typed void behavior (one IRequest type)
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; // short-circuit β handler never called
Next.Call;
end;Typed response behavior (one IRequest<TResponse> type)
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 order(s) returned', [Result.Count]));
end;The framework uses RTTI to detect the concrete request type of Handle and skips the behavior for non-matching types automatically. Typed behaviors also apply to subclasses.
Registration
// Auto-discovery (scans package, skips [MediatorAbstract])
MediatR.AddTo(ServiceCollection, _T.PackageOf<TMyClass>);
// Manual
MediatR.AddBehavior(ServiceCollection, TLoggingBehavior);
MediatR.AddBehavior(ServiceCollection, TValidationBehavior);Execution order
Behaviors execute in registration order β first registered is the outermost wrapper:
Behavior1 β Behavior2 β Handler β return to Behavior2 β return to Behavior1
Short-circuiting
Donβt call Next.Call to abort the pipeline. The handler and inner behaviors are never invoked:
procedure TValidationBehavior.Handle(Request: TCreateOrderCommand; Next: TNext);
begin
if not IsValid(Request) then
Exit; // handler is never called
Next.Call;
end;Exception handling
Wrap Next.Call in a try/except to catch exceptions from the handler or inner behaviors:
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;Dependency injection
Behaviors are resolved from the DI container. Constructor parameters are injected automatically:
type
TLoggingBehavior = class(TPipelineBehavior)
private
FLogger: ILogger;
public
constructor Create(const Logger: ILogger);
function Handle(Request: TObject; Next: TNextValue): TValue; override;
end;Backward compatibility
Pipeline behaviors are opt-in. If no behaviors are registered, Send invokes the handler directly with no additional overhead.
Documentation
Abstractions
MediatR.Abstractions
π Language: English | EspaΓ±ol
Core interfaces and base classes for the DAFce MediatR in-process messaging. Reference these from application code and handler implementations without taking a dependency on the concrete mediator.
Interfaces
IRequest
Fire-and-forget command β dispatched to exactly one handler, no return value.
IRequest = interface
end;IRequest<TResponse>
Query β dispatched to exactly one handler, returns TResponse.
IRequest<TResponse> = interface(IRequest)
end;INotification
Event β published to all registered handlers.
INotification = interface
end;IMediator
IMediator = interface
procedure Send<TRequest: IRequest>(Request: TRequest); overload;
function Send<TResponse; TRequest: IRequest<TResponse>>(
Request: TRequest): TResponse; overload;
procedure Publish<TNotification: INotification>(
Notification: TNotification);
end;IMediatorHelper
Extends IMediator with SendARC β returns the response wrapped in ARC<TResponse> for safe reference-counted ownership.
IMediatorHelper = interface(IMediator)
function SendARC<TResponse: IInterface; TRequest: IRequest<TResponse>>(
Request: TRequest): ARC<TResponse>;
end;IRequestHandler<TRequest>
IRequestHandler<TRequest: IRequest> = interface
procedure Handle(Request: TRequest);
end;IResponseHandler<TResponse, TRequest>
IResponseHandler<TResponse; TRequest: IRequest<TResponse>> = interface
function Handle(Request: TRequest): TResponse;
end;INotificationHandler<TNotification>
INotificationHandler<TNotification: INotification> = interface
procedure Handle(Notification: TNotification);
end;Base classes
| Class | Use |
|---|---|
TRequest |
Inherit to define a command |
TRequest<TResponse> |
Inherit to define a query |
TNotification |
Inherit to define an event |
TRequestHandler<TReq> |
Inherit to handle a command β override Handle |
TResponseHandler<TRes,TReq> |
Inherit to handle a query β override Handle |
TNotificacionHandler<TNot> |
Inherit to handle an event β override Handle |
Attributes
[MediatorAbstract]
Apply to abstract handler base classes to prevent the mediator from resolving them directly:
[MediatorAbstract]
TBaseHandler<T: TRequest> = class(TRequestHandler<T>);