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.

Delphi 12+ License


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 handlers

Or 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>);