Active Object Design Pattern in Delphi (Part 1): Method Requests

6 minute read

In this blog post I will start to describe the process to develop a solution in Delphi using the Active Object Design Pattern. This pattern is a concurrency design pattern based in part on the Proxy pattern.

As far as I can determine it originated from “Pattern-Oriented Software Architecture, Volume 2: Patterns for Concurrent and Networked Objects” and you can view an updated paper on the subject here: http://www.dre.vanderbilt.edu/~schmidt/PDF/Act-Obj.pdf. I will refer to the paper from time to time and it may be good to first read it to grasp what it entails.

The Active Object Pattern describes a system where the Client interacts with a Proxy that directs commands to a Servant Object. The Proxy presents the same or similar interface as the Servant to consumers. The Proxy uses a Scheduler (or Activation Queue) to queue method requests to the Servant and dispatches them in order*. Methods with return values are returned as Futures (also known as Promises). Demanding a Future Value blocks until the value is ready.

Every call to the Active Object gets turned into a Method Request. So let us start there

  TMethodRequest = class
    // some details covered later
  public
    function Guard: boolean; 
    procedure Call; 
  end;

The method request just like we saw in Lavender and Schmidt’s paper has a guard that determines if the call can be made and a call to invoke the actual method on the client. The Scheduler loops through the queued requests, checks their Guard and then issues a Call.

Some method requests need to satisfy Futures or Promised Values. Instead of the standard property “getters” in the Servant, the Proxy should generally return the value in a Future, so that the client can delay the use of the value for as long as possible since it may be a blocking action if the value is not ready. To facilitate with the return of Future Values consider this simple generic interface

  IFutureValue<T> = interface
    function GetValue: T;
    property Value: T read GetValue;
  end;

Note: Delphi has a IFuture that is run via a TTask, that interface is more complex than we need to implement.

I cover Future values in more depth in Part 3: Futures.

Direct conversion of Sample

In the sample presented in the paper each method request descends from the abstract method request and are constructed using the Servant and - in cases returning a value - a Future or Promise that needs to be satisfied. I recreated a skeletal structure representing the design of the sample in the paper below. The main difference here is that the Future is created with a Method Request. In the paper the Proxy created the Future and passed it into the Method Request to be filled.

 TMethodRequest = class abstract
 public
    function guard: boolean; virtual; abstract;
    procedure call; virtual; abstract;
 end;

// The sample only had one future. 
IMessage_Future = Interface(IInterface)
  function GetValue: TMessage;
  property Value : TMessage read GetValue;
end;

// each method invocation is a class
// The sample only had a Put and a Get methods to implement

 TPut = class(TMethodRequest)
 private
   FServant : TPassiveObject;
   FArg : TMessage;
 public
   constructor Create(ARep: TPassiveObject; AArg: TMessage);

   function guard: boolean; override;  // result := not FServant.full_i;
   procedure call; override;  // FServant.put_i(FArg);
 end;


TGet = class(TMethod_Request)
public type
   TMessage_Future = class(TInterfacedObject,  IMessage_Future)
      // detail removed
   end;

 private
   FServant : TPassiveObject;
   FFuture: IMessage_Future;
 public
   constructor Create(ARep: TPassiveObject); // creates the Future Value. 
                                             // Client can read it from the Future property  
                                             // and hold it for delayed evaluation

   function guard: boolean; override;
   procedure call; override; // TMessage_Future(FFuture).SetValue(FServant.get_i);  

   property Future:   IMessage_Future read FFuture;
end;

And the Proxy would use the objects as follows

procedure TProxy.put(const msg: TMessage);
var
  LPut: TPut;
begin
  LPut := TPut.Create(FServant, msg);
  FScheduler.enqueue(LPut);
end;

function TProxy.get: IMessage_Future;
var
  LGet: TGet;
begin
   LGet := TGet.Create(FServant);
   result := LGet.Future;
   FScheduler.enqueue(LGet);
end;

Generalizing the Sample

While this somewhat direct conversion of the sample from the paper would work in Delphi there are a few problems with the design. First, the sample is very simple. Only two method invocations with one type passed. There is only on Future Value type and lastly the Method requests are tightly bound to the Passive objects on which they operate.

We can use anonymous functions and closures to generalize the Method Request class

  TMethodRequest = class(TObject)
  private
    FCall: TProc;
    FGuard: TFunc<boolean>;
    FGuardless: boolean; // set to Assigned(FGuard) in constructor  
  public
    constructor Create(const ACall: TProc; const AGuard: TFunc<boolean> = nil);

    function guard: boolean;  // returns true if Guardless else invokes FGuard
    procedure call; virtual;  // invokes FCall
  end;

We can pass a simple TProc that is invoked in TMethodRequest.call via the Scheduler. We can also pass a Guard that can determine if the call can be made or needs to wait.

procedure TProxy.put(const msg: TMessage);
var
  LMsg: TMessage;
begin
  LMsg := msg;
  FScheduler.Equeue(
    TMethodRequest.Create(
      // Call
      procedure
      begin
        FServant.put_i(LMsg);
      end,

      // Optional Guard
      function : boolean
      begin
        result := not FServant.full_i;
      end
    )
  );
end;

We no longer need to subclass TMethodRequest, but how we return Futures? First, let us define the implementation of of our TFutureValue<T> generic class:

  TFutureValue<T> = class(TInterfacedObject, IFutureValue<T>)
  private
    FResult: T;
    function GetValue: T;
    // synchronization covered in Part 3 of this series
  public
    procedure SetValue(AResult: T); // called when method request is invoked
    property Value: T read GetValue;
  end;

Our code on the Proxy side is a bit more code, but it is still better than creating a new MethodRequest sub-class

function TProxy.get: IFuture<TMessage>;
var
  LActiveFuture: TFuture<TMessage>;
begin
  LActiveFuture := TFutureValue<TMessage>.Create;
  result := LActiveFuture;

  FScheduler.Enqueue(
    TMethodRequest.Create(
      // Call
      procedure
      begin
        LActiveFuture.SetValue(FServant.get_i); // closure over the future and servant
      end,

      // Optional Guard
      function : boolean
      begin
        result := not FServant.empty_i;
      end
    )
  );
end;

The above looks like more work, but consider the case where the Servant had many more methods or many more properties. We would have to create classes for each of those. Say my Servant object has another property PropA of type integer we can easily and quickly add a setter and getter to the Proxy.

function TProxy.getPropAFuture: IFuture<integer>;
var
  LActiveFuture: TFuture<integer>;
begin
  LActiveFuture := TFutureValue<integer>.Create;
  result := LActiveFuture;

  FScheduler.Enqueue(
    TMethodRequest.Create(
      // Call without a Guard
      procedure
      begin
        LActiveFuture.SetValue(FServant.PropA); // closure over the future and servant
      end
    )
  );
end;

procedure TProxy.SetPropA(const Value: Integer);
var
  LValue: Integer;
begin
  LValue := Value;
  FScheduler.Equeue(
    TMethodRequest.Create(
      // Call without a Guard
      procedure
      begin
        FServant.propA := LValue;
      end
    )
  );
end;

// For backwards compatibility only... better for clients to use the Future
procedure TProxy.GetPropA: integer;
begin
  result:=  GetPropAFuture.Value;
end;

You will notice that Futures are now created by the Proxy as described in the paper. The closure we formed with the anonymous procedure now allows the scheduler to have no knowledge of Futures.

Why did I not simply use Tasks and Futures from the System.Threading library? You can create a Task without running it immediately and with a bit of effort you can create a Future that is not started immediately so perhaps I could have run those as the scheduler dequeues them. I initially went down that path, but the code was actually more complex and more unreadible. I needed a simple solution that checks a guard and calls a method. While most methods do not need guards, it is part of the pattern and was hard to implement with the out-of-the-box structures

You can download the source code. Please comment or contribute.

Leave a Comment