Site icon Patrick Schiefer

Part 2: How to Implement a Command Queue in pure AL

In my last blogpost I wrote about how we can implement a Command Queue in AL. In the second part I will go a little bit in detail about the Solution. First of all you have to understand the differenences between valuetypes and referencetypes. If you already know you skip to the next paragraph.

TL;DR; Explanation of valuetypes and referencetypes

Business Central is build on top of .net so it also uses the same technologies. In .net there are mainly to types of datatypes: valuetypes and referencetypes.

Valuetypes are datatypes which directly store the value in the memory. One common used example is integer.


How those this look in the memory

procedure ValueTest()
var
  a : int;
  b : int; 
  c : int;
begin
  a := 1;
  b := 2; 
  c := a;
end;

As you can see the values of the variable is directly stored

When we look to reference types, the variable only holds an reference to the “value” (i will call it object later), and the “value” is stored on a different place. In AL most object types are reference types (i haven’t checked for all but IMHO all Objecttypes are reference types). In my example I take codeunits. A codeunit is instanciate when it is used for the first time.

So what happens in the storage?

procedure ReferenceTest()
var 
  a : Codeunit "My Codeunit";
  b : Codeunit "My Codeunit";
  c : Codeunit "My Codeunit";
begin
  a.SetMessage('a');
  b.SetMessage('b');
  c := a;
  c.SetMessage('c');
  c.GetMessage(); // Resolves to "c"
end;
As you can see here variable “a” and variable “c” reference to the same object, so if I would call a function from variable “c”, it will also affect “a”. The codeexample shows that SetMessage from “c” also changes the message of “a”

The “Queue” Object

A queue is a First in First out collection. To pop the first Item we need to now the beginning of the queue and to push new objects we need to now where the queue ends.

If we remember the first part of this post, the Queue has 2 global codeunit variables

    var
        first: Codeunit QueueEntry;
        last: Codeunit QueueEntry;

So what we can store now is something like this

When we have a look to the queue Entry it has a global codeunit and a global interface (I will handle the Interface in the next paragraph

    var
        value: Interface ICommand;
        NextEntry: Codeunit QueueEntry;

The NextEntry codeunit connects every qeueueentry to the next entry. Using this we can make a linked list of queue entries, where every member links the next one.

When we now want to add a new entry to the queue we will have to set the last of the queue to new entry and connect the prior entry to the new last.
When we want to pop the first entry we need to set the reference of the first item to the second object

The “ICommand” Interface

Now we know how to get the next command of the command queue, but now we want to execute the next command.

Remember the last post the Interface “ICommand” is pretty simple it just has one known procedure

interface ICommand
{
    procedure Execute();
}

When we now have a look how the memory of an interface variable simplified look it maybe gets a little bit clearlier. Interfaces are also reference types but the reference points to an instance of a codeunit. For my example I’m using the “MessageCommander” of the last post.

procedure TestInterface()
var 
  messageCommander : codeunit MessageCommander;
  command : interface "ICommand";
begin
  messageCommander.SetText('a');
  command := messageCommander;
  command.Execute(); // Shows message 'a'
end;
The codeunit an the Interface referencing the same object

The interface is a lose coupling between to codeunits, so it is like a contract which codeunits must fullfil if they want to implement the Interface, because of this and the fact that the interface of the above example is referencing to the same object the “Execute” of the will call “Execute” in the codeunit

In Object Oriented Programming we call this coupling. This means we could use every codeunit which is implementing the ICommand, we can even mix it up in the same process

Another usage example

We now will have a second codeunit “SalesOrderPostCommander” which is looking like that

codeunit 50104 "SalesOrderPostCommander" implements ICommand
{
   procedure SetSalesOrderNumber(value : Code[20])
   begin
     No := value;
   end;
   
   procedure Execute()
   begin
     // TODO Post Sales Header
   end;
   

   var 
     No : Code[20];
}

Now lets combine the to “ICommand” Codeunits

codeunit 50105 PatchPostQueue
{
   procedure PatchPost()
   begin
     FilterSalesOrdersToPost();
     if not SalesOrders.Findset(false) then
       exit(); // Nothing to post
     
     repeat
       AddSalesOrderToQueue(SalesOrder."No.");
     until SalesOrders.Next() = 0;

     AddMessageToQueue('Posting Complete');
     ExecuteQueue();
   end;

   local procedure ExecuteQueue()
   var
     object : interface "ICommand";
   begin
     repeat 
       object := queue.Pop();
       object.Execute();
     until queue.GetSize() = 0;
   end;

   local procedure FilterSalesOrdersToPost()
   begin
      // Filter Sales Orders here
   end;
   
   local procedure AddMessageToQueue(message : Text);
   var 
     t: Codeunit MessageCommander;
     object: Interface ICommand;
   begin
     t.SetText(message);
     object := t;
     queue.Push(object);
   end;

   local procedure AddSalesOrderToQueue(No : Text);
   var 
     SaleOrderCommander: Codeunit SalesOrderPostCommander;
     object: Interface ICommand;
   begin
     SaleOrderCommander.SetSalesOrderNumber(No);
     object := SaleOrderCommander;
     queue.Push(object);
   end;
   

   var
     SalesOrders : Record "Sales Header";
     queue: Codeunit Queue;
}
Exit mobile version