From f40daf4b1faeae3d1b4c597e5331974c40da0a1d Mon Sep 17 00:00:00 2001 From: Patrick Schiefer Date: Sat, 5 Mar 2022 19:43:29 +0100 Subject: [PATCH] added command queue --- content/docs/patterns/command-queue/index.md | 216 ++++++++++++++++++ content/docs/patterns/command-queue/queue.png | Bin 0 -> 7334 bytes 2 files changed, 216 insertions(+) create mode 100644 content/docs/patterns/command-queue/index.md create mode 100644 content/docs/patterns/command-queue/queue.png diff --git a/content/docs/patterns/command-queue/index.md b/content/docs/patterns/command-queue/index.md new file mode 100644 index 00000000..aec2e223 --- /dev/null +++ b/content/docs/patterns/command-queue/index.md @@ -0,0 +1,216 @@ +--- +title: "Command Queue" +tags: ["AL"] +categories: ["Pattern"] +--- + +_Created by Patrick Schiefer, Described by Patrick Schiefer_ + +## Abstract +The goal of this pattern is to control the flow of multiple actions + + +## Problem +Sometimes its neccassary to perform multiple actions in Business Central, for example you want to post more than one order or before you post an Order you also have to post a purchase order this often leads to spaghetti code with big if else structures, which is not easy to read + + +## Description +To structure this problem we can use the "Command Queue" pattern. The pattern consist of two main parts the queue and the command interface + +![image](queue.png) + +First the command interface, it only has one procedure to execute the command +```al +interface ICommand +{ + procedure Execute(); +} +``` + + +And then the Queue which consist of two codeunits, the Queue itself and a Queue Entry + +```al +codeunit 50100 "Queue" +{ + procedure Push(var value: Interface ICommand) + var + Entry: Codeunit QueueEntry; + begin + Entry.SetValue(value); + if count = 0 then begin + first := Entry; + last := Entry; + end + else begin + last.SetNextEntry(Entry); + last := Entry; + end; + count += 1; + end; + + procedure Pop() value: Interface ICommand; + begin + if count > 0 then begin + value := first.GetValue(); + first := first.GetNextEntry(); + count -= 1; + end + else + Error('The Queue is empty!'); + end; + + procedure GetSize(): Integer + begin + exit(count); + end; + + var + first: Codeunit QueueEntry; + last: Codeunit QueueEntry; + count: Integer; +} + + + +codeunit 50102 "QueueEntry" +{ + procedure SetValue(var v: Interface ICommand); + begin + value := v; + end; + + procedure GetValue(): Interface ICommand; + begin + exit(value); + end; + + procedure GetNextEntry(): Codeunit QueueEntry; + begin + exit(NextEntry); + end; + + procedure SetNextEntry(var Entry: Codeunit QueueEntry); + begin + NextEntry := Entry; + end; + + var + value: Interface ICommand; + NextEntry: Codeunit QueueEntry; +} +``` + +As we see the queue entry stores a command, since the command is an interface we can hide each business logic behind + +## Benefits +The logical flow is very easy to adopt, it is even possible to add entries to the queue while it is processed. + +## Example + +In this short example I show you how to post multiple sales orders and display message after finishing the last post. + + +We have two commands in this example, the "SalesOrderPostCommander" is used to post a sales order and the "MessageCommander" displays a message. +```al +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]; +} + + +codeunit 50103 "MessageCommander" implements ICommand +{ + procedure SetText(value: Text); + begin + t := value; + end; + + procedure Execute() + begin + Message(t); + end; + + var + t: Text; +} +``` + +Using this two codeunits we can now implement a patch posting +```al + +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; +} + +``` + + +## References +[Detailed Explanation of the pattern](https://patrickschiefer.wordpress.com/2022/02/24/part-2-how-to-implement-a-command-queue-in-pure-al/) + diff --git a/content/docs/patterns/command-queue/queue.png b/content/docs/patterns/command-queue/queue.png new file mode 100644 index 0000000000000000000000000000000000000000..a7411ca6d45dc8099c0523bc4289933e36c953c1 GIT binary patch literal 7334 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 literal 0 HcmV?d00001