One question that is raised a lot lately is how events and hooks are compared and when to use events and when to use hooks.
But what if you are on NAV2016. Does that mean I don’t need hooks? And events are preferred?
Let’s compare the two features and how to implement it. But before we do that we will explain the problem we want to solve. It is key to focus on the problem, and then use the best tool to solve it. If you only have a hammer, everything looks like a nail. Before NAV2016 we only had the hammer, now we also have superglue.
Dynamics NAV is open code. Microsoft shares their entire codebase with us and allows us to change (almost) everything. The downside is that while we implement the current version they continue development on the same code and we have to merge their changes into our solutions each time they release. (Microsoft tends to think we merge our change into their code, but it is the other way around J).
The more complex the change we make to their code, the more challenging it becomes to merge. This is something that everyone who ever did a merge experienced. It gets even more interesting if you have to merge someone else’s changes and more complicated if the original code is refactored.
The PowerShell merge commandlets or the MergeTool by Per Mogensen help us to overcome this challenge but the bigger the changes the lesser chance of an automatic merge.
Hence comes the idea of reducing footprint in code you don’t control.
When you start coding in NAV, especially in older versions, but still in routines like Sales Post you get the impression that in C/AL it is best practice to just put all business logic in one big function, or in the OnRun trigger. This is where a lot of things go wrong.
I started to put my changes in functions of their own many winters ago, before we started Partner Ready Software and introduced Design Patterns in NAV. This way of working was formalized in Hooks by Gary and Waldo. The main idea is not to write more than one line of code and avoid adding variables or even functions. I must say in all honesty that I don’t follow their formal pattern all the way, but the idea is what is most important.
Let’s investigate how to implement a hook or events in a posting routine. For this I go to my infamous Example add-on that you will soon find on GitHub.
Documentation() OnRun(VAR Rec : Record "Example Document Header") ThrowErrorIfNoSelection(Rec); TestNear(Rec); TestFar(Rec); ReplaceDatesIfAttrExists(Rec); PostExampleOne(Rec); PostExampleTwo(Rec); PostExJnlLine(Rec); DeleteExampeDocument(Rec); LOCAL ThrowErrorIfNoSelection(VAR ExDoc : Record "Example Document LOCAL TestNear(VAR ExDoc : Record "Example Document Header") LOCAL TestFar(VAR ExDoc : Record "Example Document Header") LOCAL ReplaceDatesIfAttrExists(VAR ExDoc : Record "Example Document Header") LOCAL PostExampleOne(VAR ExDoc : Record "Example Document Header") LOCAL PostExampleTwo(VAR ExDoc : Record "Example Document Header") LOCAL DeleteExampeDocument(VAR ExDoc : Record "Example Document Header") SetArguments(VAR ArgumentsIn : Record "Example Posting Arguments") LOCAL PostExJnlLine(ExDoc : Record "Example Document Header")
This codeunit is hopefully giving an impression of how a well-structured posting routine could look like and it includes test near, test far, do-it, clean up.
So what do we have to do in order to add hooks to this codeunit. First of all we need to add a new codeunit which serves as a proxy between the Example Post codeunit and the code we want to add. Each codeunit we want to change has a hook codeunit of their own.
Documentation() OnRun() OnBeforeTestNear(VAR ExampleDocumentHeader : Record "Example Document Header") OnAfterDelete(VAR ExampleDocumentHeader : Record "Example Document Header")
This codeunit contains multiple global functions that are called in the original code. The real business logic should not be in here, but in a codeunit of its own, if you want to follow the pattern 100%
To add this hook into the Example-Post codeunit we have to do two things:
- Add the hook codeunit as a global variable. Make sure that this has a unique ID that is not used by others.
- Add the function calls to the code at the right place, which in this case are the TestNear and Delete functions.
That is simple isn’t it? We might still have merge issues if someone changes a function where we have a hook, but this is easy to fix.
So let’s see what steps we need with events.
The first step is that we don’t need a proxy codeunit. Eventing is a platform feature that allows us to loosely couple codeunits with properties.
So taking in account that that is not a real step, this is the things we need to do
- Add extra functions to the Example-Post codeunit with the property event publisher
- Call the functions from the required places in the C/AL code
LOCAL DeleteExampeDocument(VAR ExDoc : Record "Example Document Header") OnBeforeDelete; WITH ExDoc DO DELETE; LOCAL [BusinessEvent] OnAfterTestNear() LOCAL [BusinessEvent] OnBeforeDelete()
As you can see, there are not that many differences. When using events we still have to go into the original code and make raw sourcecode modifications. The differences are that we don’t use a global variable but functions. This is how I normally do hooks too, I don’t have the hook codeunit but use functions instead. (Sorry Waldo).
So should I use events?
If you ask me, no. There are two “negative” sideeffects to events.
- Execution order is not guaranteed. This is not a huge issue but it is something that you don’t control anymore as a developer. This is something I explained in an earlier blogpost.
- Go-To definition does not work. From the event I cannot see what happens, what codeunits are called.
What does it fix?
Nothing. The chance of a merge conflict is 100% equal to hooks.
When to use events?
Use the system events on tables and pages. This is super cool and for free. This really guarantees no more conflicts during merge.
If Microsoft were to give us OnBefore and OnAfter events for all of our own C/AL functions, then we could really solve the merge issues. Let’s see if this happens.
Oh, and if this happens (I really don’t know guys), it would also be nice to have an override property just in case I want to replace someone else’s code.