mercredi 14 juillet 2021

Is it possible to create a generic Mediator for Delphi to handle generic commands

I must first admit that I am from the .Net world and am currently relearning Delphi (XE 10.x) (from back in high school - MANY years ago). In .Net, the mediator pattern is fairly well handled by libraries such as MediatR or MassTransit. Yet, I have found very few libraries that support a dynamic (or semi-dynamic) implementation of the Mediator Pattern in Delphi. Without going to the fancy level of scanning the executing Rtti information, I wanted to create a simple mediator where I could register a CommandHandler by Request and then get a response back. Is this possible?

Here is some example code that I've made so far - but I'm just getting stuck on how to dynamically create the objects and whether my approach is even sound.

Before examining the code, I am not stuck on using a TDictionary<string, string> for registering the types, however, my limited knowledge of Rtti makes it difficult to figure out whether it should be using TClass or TRttiTypes. If either of those would be helpful, I would appreciate additional assistance on that.

// interface

uses
  System.Generics.Collections;

type
  TUnit = record
  end;

  IRequest<TResponse> = interface
  end;

  IRequest = interface(IRequest<TUnit>)
  end;

  IRequestHandler<TResponse; TRequest: IRequest<IResponse>> = interface(IInvokable)
    function Handle(ARequest: TRequest): TResponse;
  end;

  IRequestHandler<TRequest: IRequest<TUnit>> = interface(IRequestHandler<TUnit, TRequest>)
  end;

  TMediator = class
  private
    FRequestHandlers: TDictionary<string, string>;
  public
    constructor Create;
    destructor Destroy; override;
    procedure RegisterHandler(AHandlerClass, ARequestClass: TClass);
    function Send<TResponse, TRequest>(ARequest: TRequest): TResponse;
  end;

// implementation

constructor TMediator.Create;
begin
  Self.FRequestHandlers := TDictionary<string, string>.Create;
end;

destructor TMediator.Destroy;
begin
  Self.FRequestHandlers.Free;
  inherited;
end;

procedure TMediator.RegisterHandler(AHandlerClass, ARequestClass: TClass);
var
  LTempRequestClass : string;
  rContext          : TRttiContext;
  rType             : TRttiType;
begin
  if Self.FRequestHandlers.TryGetValue(ARequestClass.QualifiedClassName, LTempRequestClass) then
    exit;

  { I would like to add some error checking functionality to prevent classes 
    that do not implement IRequest or IRequest<> from being added here. }

  Self.FRequestHandlers.Add(ARequestClass.QualifiedClassName, AHandlerClass.QualifiedClassName);
end;

function TMediator.Send<TResponse, TRequest>(ARequest: TRequest): TResponse;
var
  LRequestHandlerClassName: string;
  LRequestHandler   : IRequestHandler<TResponse, TRequest>;
begin
  if not Self.FRequestHandlers.TryGetValue(ARequest.QualifiedClassName, LRequestHandlerClassName) then
    raise Exception.Create('Handler class not registered with this mediator.');

  { Not sure what to do here to get the LRequestHandler - I'm also using Spring4d, 
    so I considered using the QualifiedClassName as a way to resolve classes 
    registered in the TContainer }

  Result := LRequestHandler.Handle(ARequest);

end;

My anticipated usage of this would be:

// interface

type
  TMyResponse = class
  private
    FFoo: string;
  public
   property Foo: string read FFoo write FFoo;
  end;

  TMyRequest = class(TInterfacedObject, IRequest<TMyResponse>)
  private
    FBar: string;
  public
    property Bar: string read FBar write FBar;
  end;

  TMyRequestHandler = class(TInterfacedObject, IRequestHandler<TMyResponse, TMyRequest>)
  public
    function Handle(ARequest: TMyRequest): TMyResponse;
  end;

// implementation

var 
  AMediator: TMediator;
  ARequest: TMyRequest;
  AResponse: TMyResponse; 
begin
  AMediator := TMediator.Create;
  ARequest := TMyRequest.Create;
  try
    ARequest.Bar := 'something';

    // Not sure how I would get these either - seems best to use the qualified class name
    AMediator.Register(TMyRequestHandler.QualifiedClassName, TMyRequest.QualifiedClassName);

    AResponse := AMediator.Send(ARequest);

    // Do something with this value
  finally
    AResponse.Free;
    ARequest.Free;
    AMediator.Free;
  end;
end.

Aucun commentaire:

Enregistrer un commentaire