From 9026d6aaaebc863ca88855a0b72afc9002fe8a2e Mon Sep 17 00:00:00 2001 From: waldo1001 Date: Thu, 10 Feb 2022 13:59:35 +0100 Subject: [PATCH 1/3] Subsciber Codeunits --- .../Performance/SubscriberCodeunits/index.md | 195 ++++++++++++++++++ content/BCBestPractices/Performance/_index.md | 9 + 2 files changed, 204 insertions(+) create mode 100644 content/BCBestPractices/Performance/SubscriberCodeunits/index.md create mode 100644 content/BCBestPractices/Performance/_index.md diff --git a/content/BCBestPractices/Performance/SubscriberCodeunits/index.md b/content/BCBestPractices/Performance/SubscriberCodeunits/index.md new file mode 100644 index 00000000..fafbe849 --- /dev/null +++ b/content/BCBestPractices/Performance/SubscriberCodeunits/index.md @@ -0,0 +1,195 @@ ++++ +title = "Subscriber Codeunits" +weight = 1180 ++++ + +<_Created by waldo, Described by waldo_\> + +## Description + +In general, subscribers have to be put in codeunits. There are a few performance considerations that you should keep in the back of your minds, when designing such a codeunit. + +- Keep the codeunit as small as possible +- Work with a single instance codeunit +- only subscribe when necessary +- Avoid generic OnInsert/OnModify/OnDelete + +Let's discuss all points + +## Keep the codeunit as small as possible +Every time a subscriber gets called, a new instance of the codeunit is being loaded in memory, which takes memory and processing power. The smaller the codeunit, the less memory, and the faster it is. + +Therefore, it's suggested to split the subscribers by functionality and avoid putting business logic in the actual codeunit. Tip: put all business logic in an "[Method Codeunit](https://alguidelines.dev/bcpatterns/generic-method-pattern/)". + +Examples: +- if you app does things on Sales and Purchase, create a Sales-subs codeunit, and a Purchase-subs. +- if you have multiple functionalities in your app (let's call'm modules), create a subs-codeunit per module, and only add the subscribers in there that are necessary for that module. + +### Bad code +```AL +codeunit 2037325 "Setup Subs" +{ + SingleInstance = true; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)] + local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup") + var + AppId: ModuleInfo; + NameListLbl: Label 'Linked Texts Framework - List', Locked = true; + DescriptionListLbl: Label 'Edit linked texts', Locked = true; + KeyWordListLbl: Label 'LT,Distri,Technical,Functional,Reports', Locked = true; + NameReportLbl: Label 'Linked Texts Framework - Reports', Locked = true; + DescriptionReportLbl: Label 'View linked texts reports', Locked = true; + KeyWordReportLbl: Label 'LT,Distri,Technical,Functional,Reports', Locked = true; + begin + navapp.GetCurrentModuleInfo(AppId); + Sender.Insert(NameListLbl, DescriptionListLbl, KeyWordListLbl, page::"LTE Linked Text List", AppId.Id(), "Manual Setup Category"::General); + Sender.Insert(NameReportLbl, DescriptionReportLbl, KeyWordReportLbl, page::"LTE Linked Texts Reports", AppId.Id(), "Manual Setup Category"::General); + end; + + [EventSubscriber(ObjectType::Codeunit, codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)] + local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup") + var + AppId: ModuleInfo; + NameLayoutLbl: Label 'Report Helper - Layout', Locked = true; + DescriptionLayoutLbl: Label 'Set up or update report layout list', Locked = true; + KeyWordLayoutLbl: Label 'RH,Distri,Technical,Functional,Reports,Layout', Locked = true; + NameCaptionsLbl: Label 'Report Helper - Captions', Locked = true; + DescriptionCaptionsLbl: Label 'Set up or update captions list', Locked = true; + KeyWordCaptionsLbl: Label 'RH,Distri,Technical,Functional,Reports,Captions', Locked = true; + NameFunctionsLbl: Label 'Report Helper - Functions', Locked = true; + DescriptionFunctionsLbl: Label 'Set up or disable functions', Locked = true; + KeyWordFunctionsLbl: Label 'RH,Distri,Technical,Functional,Reports,Functions', Locked = true; + NameDFCLbl: Label 'Report Helper - Default Footer', Locked = true; + DescriptionDFCLbl: Label 'Set up or update default footer', Locked = true; + KeyWordDFCLbl: Label 'RH,Distri,Technical,Functional,Reports,Default,Footer', Locked = true; + begin + navapp.GetCurrentModuleInfo(AppId); + Sender.Insert(NameLayoutLbl, DescriptionLayoutLbl, KeyWordLayoutLbl, page::"RHE Report Layout List", AppId.Id(), "Manual Setup Category"::General); + Sender.Insert(NameCaptionsLbl, DescriptionCaptionsLbl, KeyWordCaptionsLbl, page::"RHE Captions", AppId.Id(), "Manual Setup Category"::General); + Sender.Insert(NameFunctionsLbl, DescriptionFunctionsLbl, KeyWordFunctionsLbl, page::"RHE Functions", AppId.Id(), "Manual Setup Category"::General); + Sender.Insert(NameDFCLbl, DescriptionDFCLbl, KeyWordDFCLbl, page::"RHE Default Footer Card", AppId.Id(), "Manual Setup Category"::General); + end; +} +``` +### Good code + +Split into 2 codeunits, and move the business logic out. + +```AL +codeunit 2037325 "LTE Setup Subs" +{ + SingleInstance = true; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)] + local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup") + var + RegisterLTEManualSetup: codeunit "Register LTE Manual Setup"; + begin + RegisterLTEManualSetup.RegisterLTEManualSetup(); + end; +} + +codeunit 2037324 "RHE Setup Subs" +{ + SingleInstance = true; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)] + local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup") + var + RegisterRHEManualSetup: codeunit "Register RHE Manual Setup"; + begin + RegisterRHEManualSetup.RegisterRHEManualSetup(); + end; +} + + +``` + +## Work with a single instance codeunit + +To avoid the extra "loading of the content" while a subscriber is being executed, use Single Instance codeunit for subscribers. Do take into account, of course, that it would share the state across the entire session. + +### Bad code +```AL +codeunit 2037324 "RHE Setup Subs" +{ + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)] + local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup") + var + RegisterRHEManualSetup: codeunit "Register RHE Manual Setup"; + begin + RegisterRHEManualSetup.RegisterRHEManualSetup(); + end; +} +``` +### Good code +```AL +codeunit 2037324 "RHE Setup Subs" +{ + SingleInstance = true; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)] + local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup") + var + RegisterRHEManualSetup: codeunit "Register RHE Manual Setup"; + begin + RegisterRHEManualSetup.RegisterRHEManualSetup(); + end; +} +``` + +## only subscribe when necessary + +If possible, only execute the subscriber when really necessary by using Manual Binding. + +### Bad code +```AL + //subscriber - code should actually only run when Color=Red. + [EventSubscriber(ObjectType::Table, Database::"Just Some Table WLD", 'OnAfterValidateEvent', 'Message 2', false, false)] + local procedure JustDoSomthing(var Rec: Record "Just Some Table WLD"; var xRec: Record "Just Some Table WLD") + begin + if Rec.color <> 'RED' then exit; //only execute when necessary + + ... + end; + + //business logic + if JustSomeTable.FindSet() then + repeat + JustSomeTable.Validate("Message 2", format(Random(1000))); + until JustSomeTable.Next() < 1; +``` +### Good code +```AL + if JustSomeTable.FindSet() then + repeat + if JustSomeTable.Color = 'RED' then + BindSubscription(DemoSubs); + + JustSomeTable.Validate("Message 2", format(Random(1000))); + + if JustSomeTable.Color = 'RED' then + UnbindSubscription(DemoSubs); + until JustSomeTable.Next() < 1; +``` + +## Avoid OnInsert/OnModify/OnDelete +The reason for this is, that it breaks the batch-calls: +- Any "OnInsert" subscriber breaks the bulk inserts, simply because it needs to perform an operation after every record that was inserted +- Any "OnModify" subscriber slows down the "ModifyAll", simply because it needs to perform an operation after every record that was modified. I fact: 1 SQL call is turned into a loop of SQL calls. +- Any "OnDelete" subscriber slows down the "DeleteAll", simply because it needs to perform an operation after every record that was deleted. I fact: 1 SQL call is turned into a loop of SQL calls. + +Avoid subscribers to these events. + +## [Discussions](https://github.com/microsoft/alguidelines/discussions/92) + +You can discuss this guidelines [here](https://github.com/microsoft/alguidelines/discussions/92). + +You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices). + +If you don't find the discussion of this guideline, please feel fee to create a new one with the same title as this article. + +## References + +The [Generic Method Pattern](https://alguidelines.dev/bcpatterns/generic-method-pattern/) \ No newline at end of file diff --git a/content/BCBestPractices/Performance/_index.md b/content/BCBestPractices/Performance/_index.md new file mode 100644 index 00000000..61c558fe --- /dev/null +++ b/content/BCBestPractices/Performance/_index.md @@ -0,0 +1,9 @@ ++++ +title = "Performance" +weight = 980 ++++ +## AL Coding Guidelines + +## **Performance** + +Some guidelines are simply better for performance considerations rather than readability or anything else. In this section, let's look into some.. \ No newline at end of file From 28dde8ce6f28c3d7cbee7a877e5760e548be28c6 Mon Sep 17 00:00:00 2001 From: waldo1001 Date: Thu, 10 Feb 2022 16:25:01 +0100 Subject: [PATCH 2/3] cleanup --- .../BCBestPractices/Performance/SubscriberCodeunits/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/BCBestPractices/Performance/SubscriberCodeunits/index.md b/content/BCBestPractices/Performance/SubscriberCodeunits/index.md index fafbe849..8e7314bf 100644 --- a/content/BCBestPractices/Performance/SubscriberCodeunits/index.md +++ b/content/BCBestPractices/Performance/SubscriberCodeunits/index.md @@ -188,7 +188,7 @@ You can discuss this guidelines [here](https://github.com/microsoft/alguidelines You can find discussions on all "Best Practices" [here](https://github.com/microsoft/alguidelines/discussions/categories/bc-best-practices). -If you don't find the discussion of this guideline, please feel fee to create a new one with the same title as this article. +If you don't find the discussion of this guideline, please feel free to create a new one with the same title as this article. ## References From 554f1c0edd55dd7f6efb24fe35ff20df739cf8cb Mon Sep 17 00:00:00 2001 From: waldo Date: Sat, 12 Feb 2022 10:02:24 +0100 Subject: [PATCH 3/3] Update content/BCBestPractices/Performance/SubscriberCodeunits/index.md Co-authored-by: Henrik Helgesen --- .../BCBestPractices/Performance/SubscriberCodeunits/index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/BCBestPractices/Performance/SubscriberCodeunits/index.md b/content/BCBestPractices/Performance/SubscriberCodeunits/index.md index 8e7314bf..9839458f 100644 --- a/content/BCBestPractices/Performance/SubscriberCodeunits/index.md +++ b/content/BCBestPractices/Performance/SubscriberCodeunits/index.md @@ -149,7 +149,8 @@ If possible, only execute the subscriber when really necessary by using Manual B [EventSubscriber(ObjectType::Table, Database::"Just Some Table WLD", 'OnAfterValidateEvent', 'Message 2', false, false)] local procedure JustDoSomthing(var Rec: Record "Just Some Table WLD"; var xRec: Record "Just Some Table WLD") begin - if Rec.color <> 'RED' then exit; //only execute when necessary + if Rec.color <> 'RED' then + exit; //only execute when necessary ... end;