NAV Design Pattern of the Week – the Hooks Pattern

NAV Design Pattern of the Week – the Hooks Pattern

  • Comments 7

A new series of NAV Design Patterns is just starting. To make a good beginning, the first one is not chosen randomly. It is indeed a very powerful pattern – the “Hooks”, by Eric Wauters. It is also different from most patterns you’ve seen so far, in that it does not exist in the core product. “How can it be? A powerful NAV pattern which is not in NAV?”, you wonder.

This is exactly the point of the Hooks. It is a partner pattern – something you, as a partner, can use, when you implement your customization. And if done correctly, it has the potential to make the next upgrade superfast.   

Are you having a hard time installing update after update, rollup after rollup? Does it consume more resources than you had wished? Is the customization code entangled with the core product code, in such a way that each update requires complicated merges, with the developer trying to figure out which code goes where?

To keep it short, here it is – the Hooks pattern. Most powerful when starting a new customization, as it can be implemented fully. It will keep things simple. As for the legacy customizations... it is still possible to use it, by refactoring key areas such as areas with the most update merge issues.

Hooks Pattern

by Eric Wauters (waldo), Partner-Ready-Software

Meet the Pattern

By minimizing the code in already existing application objects, you will make the upgrade process much easier, and all customization business logic will be grouped in new objects.  When using atomic coding, it will be very readable what is being customized on a certain place in an existing part of the application.

To minimize the impact of customizations, the idea of hooks is:

  • First of all, name the places in the already existing code where customization is needed;
  • Second, place your business logic completely outside the already existing application code.

Know the Pattern

When doing development over years, by different developers with different mindsets, the standard codebase gets changed a lot, adding multiple lines of code, adding local and global variants, adding or changing keys, changing existing  business logic, … .  In other terms, the standard text objects are being changed all over the place.. .

After years, it's not clear why a change was done, and what was the place where the change was intended to be done.  And the latter is quite important in an upgrade process, when code in the base product is being refactored: if the exact place of the posting of the Customer Entry is being redesigned to a separate number, the first thing I need to know, is that I did a certain change at the place: "where the posting of the Customer Entry starts".  The definition of that place, we call a "Hook".

I recommend to use this concept on the following:

  • All objects of the default applications that need to be changed
  • On objects that should not hold any business logic (like tables, pages, XMLPorts)

Use the Pattern

Step 1 - if it doesn't exist yet - you create your Hook codeunit.  As the name assumes .. this is always a codeunit.  We apply the following rules to it:

  • One Hook always hooks into one object.  Which basically means that I will only declare this new codeunit in one other object (which is its parent object)
  • The naming convention is: The_Original_Object_Name Hook.  Naming conventions are important, just to find your mapped object, and also to be able to group the Hooks.

Step 2, you  create the hook, which is basically a method (function) in your codeunit.  The naming is important:

  • The naming of the hook should NOT describe what it is going to do (So, examples like CheckMandatoryFields, FillCustomFields should not be used as a hook)
  • The naming of the hook should describe WHERE the hook is placed, not what the hook will be doing (as nobody is able to look into the future .. :-))
  • To help with the naming, it is a good convention to use the "On"-prefix for these triggers.  This way, it's very clear what are hooks, and what aren't..

Step 3, it's time to hook it to its corresponding object and right place in the business logic of that object.  You do this by declaring your codeunit as a global in your object, and using the created hook function on its place in the business logic.  This way, these one-liners apply:

  • A Hook codeunit is only used once in one object only (its corresponding object)
  • A Hook (function) is used only once in that object.  As a consequence, changing the parameters has no consequence: you only need to change one function-call
  • The codeunit is declared as a global.  That exact global is the only custom declaration in the existing object .. Everything else is pushed to the hook-codeunit.

Step 4, implement your business logic in the hook.  Do this in the most atomic way, as there is a good chance that this same hook is going to be used for other business logic as well.  Best is to use a one-line-function-call to business logic, so that the Hook Function itself stays readable.

Example

Suppose, we want to add business logic just before posting a sales document.  In that case, we have to look for the most relevant place, which is somewhere in the "Sales-Post" codeunit.  So:

Step 1: create codeunit Sales-Post Hook:

Step 2: create the hook function OnBeforePostDocument:

Step 3: declare a global in the Sales-Post codeunit, called SalesPostHook.  Then,...

Continue reading on the NAV Patterns Wiki...

 

Best regards,

The NAV Patterns team

Leave a Comment
  • Please add 1 and 4 and type the answer here:
  • Post
  • We discussed this internally a couple years back when we were looking at escalating upgrade costs, and the 3 major draw backs that I came up with on this approach (since we discussed this very topic) were the following:

    >>> (1) This is only doable for mods / changes that are to happen pre or post a particular function. If you notice in the above, he alludes to that very fact in his "hook" codeunits w/ function names are something like "BeforeOnRun" or "AfterOnRun". This procedure falls apart as much of our code changes are needed within the middle of existing functions. i.e. When was the last time that you looked at a Codeunit 80 or 90 change and the mod was limited to something happening only at the beginning or the end of the "Run" function... in my experience... NEVER. So after thinking about this pattern, it is mostly not useable, since most changes happen in the middle of the function code.

    (2) Even if you were to copy the function to the "Hook" codeunit and make your own version of it, and then apply your changes in that copied version of the function (i.e. cu 80 - Run), then the problem is that you have to keep 2 functions in sync permenantly. So this doesn't buy you anything in an upgrade because you'd still have to re-copy the new function to your "hook" codeunit and re-merge your customs into that function. In every situation that I've seen an ISV do that for an add-ons, it just proved to difficult to keep the copied function in sync and they reverted back to making their changes in the base object.

    (3) Even if you had only Before or After changes to a function, the moment that you have changes in the middle of that function, then it's just too complicated to keep up w/ changes in 3 separate places (a before hook function, the middle of the actual function for mods that can't be moved, and the after hook function.

    Also, by keeping these hook changes in different codeunits, you would now have to look in multiple locations to see what a process is doing instead of just having it all in 1 function.  So the research costs of debugging might go up w/ the added complexity.

    So in my opinion, this is mostly not useable unless your changes were limited to only before and after changes to a function. But for the most part this pattern mostly breaks down b/c of the added complexity of keeping changes to functions in multiple places.

  • @David Kaupp: Good analysis, these are also our findings. Hooks are a common pattern in programming, but the base system has to be designed for it (like an O/S kernel, or a GUI system, for example). As for NAV... it is not very well suited for hooks.

    with best regards

    Jens

  • Dear David and Jens,

    i'm afraid I'll have to disagree on this one.

    You're focussing on the "beforerun"/"afterrun" of functions, and the fact that hooks don't apply in a middle of a function.  Well .. why not??  In the OnRun of CU80, I have about 15 hooks.. .  Why would that be a problem?  Sure, I have 15 lines of code.  But these 15 lines, describes the exact place in the codeunit .. which means .. when NAV redesigns the codeunit, I can just move that one hook back to its place.  And I know what is his place, just because the hook describes it!  Upgrading van 2013 to 2013R2, there was a change in codeunit 80 (piece of code moved to a function).  There were lots of reactions from the community .. like "Microsoft, please don't do that, now my code don't merge" .. .  I only had to move 1 line to that function.. .

    Copy pieces of NAV logic to your own codeunit doesn't make sense at all .. please never do that!  

    For the rest, it's just a matter of opinion.

    Internally, I have had people stating the exact same as you (about debugging, about readability) .. but after they tried it out, and work with the product, they admit that this actually makes so much more sense .. even the "go to definition" actually makes sense, as it rarely jumps to a different function in the same codeunit .. .

    Anyway .. I think i'm going to dedicate a blog on this .. :-).

  • Dear David and Jens,

    I absolutely love Hooks and I get the feeling that you have misunderstood the concept a little (thinking BIG time) :-)

    First of all it doesn't have to be before and after a given function. You could easily have one that was OnAfterGLJournalLineCreation in or somewhere else in the Codeunit if we are talking about Codeunit 80. Codeunit 80 is much too long by the way and Microsoft should break it down to something smaller.

    Secondly you are talking about copying the entire function and keeping it in sync. That would be totally nuts to try that. The point is exactly not to do this and leave as little a foot print as possible on the existing code.

    With the rapid releases of Update Rollup's and new releases from Microsoft this is one of the tools that are meant to make it easier and faster for you to deploy these updates. Microsoft has not been slowing down in the last years (quite the opposite) and the pace is likely to keep building. If you don't build your code for fast and easy merge and updating you could see your self being in a bad spot.

    My experience with Hooks is extremely good. They are easy, elegant, and very manageable. They really makes merging very effective and separates my code from the standard code in a beautiful way. I love them and will not go back unless something smarter is available.

    Thank,

    Soren

  • Hi Soren, hi Eric,

    experiences with patterns are different. I agree, there are cases where this pattern is useful. The NA version is using hooks for additional fields in G/L entries (for example). If you have an Add-On, hooks might be the way to go... unless it's not producing too much of a call mess.

    An Example: "Our" Add-Ons are extending basic G/L functionality of NAV. This requires "some" changes in the posting codeunits, most of them in CU 12. Problem ist, there are more than 200 changes in CU12 alone... but only a few additional variables, most of the context is in the existing ones. Refactoring this after development into a hooks-based architecture is possible, but I won't recommend it. Developing it with hooks is practically impossible.

    There are examples what comes out of it in NAV standard - just try to make sense of the CU598x service posting routines. It takes a while. Point is - hooks-based code is not as easy to understand, mostly due to the partitioning of the code over several codeunits.

    The hook call itself is a context change, and this can be good (encapsulation) and bad (you need to change the hook context, i.e. the variables you expose, in the course of development). And this is what I mean with the base system needs to designed for it. In an OS kernel for example, the hookable parts are predefined, and also the variables you have access to (usually in a message structure). They are for catching and handling events. Your code can utilize it, but you can't change the code of the OS kernel should you need other variables. In NAV, only a few hooks are predefined (aside from the triggers in objects). Defining useful hooks in C/SIDE would be doable, however it would make the core NAV code very much harder to understand.

    with best regards

    Jens

  • I can only agree with David

    A single change of code in the right place is a lot easier to maintain going forward and a lot of things can not be done this way usinng "hooks". It require real nowledge about the complexity of merging to understand that simple chance in the right place is a lot easier to upgrade and merge than expected.

    Making tricks and putting code in different places is make code hard to read and un-necessary complex.

  • This pattern is obviously good if used correctly in the correct place. But, for every pattern, there is an anti-pattern with it's mis-use. I believe that there is an Anti-Pattern associated with the misuse of this pattern. The "Hook Pattern" reminds me of Object Oriented Programming's concept of encapsulating change. The Hook moves the "change" into an isolated class or object, minimizing the manipulation of base NAV objects. This might seem like it solves a difficult merge scenario, and, the auto-merge might seem to handle the merge, but, the minute you try to compile, you may find yourself in a world of refactoring, especially in heavily changed base NAV code(i.e., Codeunit 80, 2013R2 vs 2013). Difficult merging is usually due to the fact that MS has modified base that is also modified by a customization. If you hook it away, it may be even harder to decipher what changes you need to make in order to solve compile or run-time issues or correct logic post merge. In the Hook Pattern, the intent of the customizations is obfuscated by the isolation, or encapsulation attempt. So, I'm going to give this pattern(if used blindly, and/or incorrectly), the "Head in the Sand" Anti-Pattern.

Page 1 of 1 (7 items)