Exploring Interface Implementation: A Journey from Events to Enums

Abstract

Last week, I had an conversation with my colleague Natalie Karolak. She faced a unique challenge: replacing an event-driven feature with interfaces. Her goal was to provide a template for end-users to implement every necessary function while ensuring the possibility of executing multiple implementations of the interface.

As the designated pattern enthusiast at our company, Natalie turned to me for insights. My initial thought was to leverage the command queue pattern, storing implementations in a queue. However, the complexity of the pattern and the fact that you would need an event to add implementations to the queue anyway lead us to think about other solution

Since I’m a .net developer I started brainstorming how I would solve this kind of issue in C#. What came to my mind was a metadata search over the assemblies like it is done on this stackoverflow (see here)

public static class TypeLoaderExtensions {
    public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly) {
        if (assembly == null) throw new ArgumentNullException("assembly");
        try {
            return assembly.GetTypes();
        } catch (ReflectionTypeLoadException e) {
            return e.Types.Where(t => t != null);
        }
    }
}

private IEnumerable<Type> GetTypesWithInterface(Assembly asm) {
    var it = typeof (IMyInterface);
    return asm.GetLoadableTypes().Where(it.IsAssignableFrom).ToList();
}

The Event Solution

Imagine we have a codeunit which holds an list of integer.

codeunit 50102 MyEventPublisherTest
{
    
    ...
    ...
    ...


    var
        listInt : list of [Integer];
}

This list should be exposed via an event to other apps to manipulate entries, we can do it like that

    trigger OnRun()
    begin
        ManipulateList(listInt);
    end;

    [IntegrationEvent(false, false)]
    local procedure ManipulateList(listInt : list of [Integer])
    begin
    end;

This solution is fully functional but has one big problem. The order of executions of the event subscripers is not fixed, so we might run in to problems when the order is changing for some reason after an update.

The Enum Metholodogy

After trying around a little bit with the possibilitie of AL, something very special of AL came to my mind. The way AL is implementing interfaces via enums is, so far as I know, unique. I haven’t seen something in other programming languages (and I have seen a lot of languages in my earlier programming life). AL allows you to assingn an enum value to an interface variable as long as the enum is implenting the interface. (I would like to know how this magic is implemented in the background). So by assigning an enum value to an interface we get an instance of the implemantion.

…and no it comes…

We can actually search for enum values 🎉🎉
And with this little trick we can search for interface implentations. Here is a small example how you can do it.

First of all we need an Interface to implement and some implentations

interface TestInterface
{
    procedure MyProcedure(l: List of [Integer]);
}

codeunit 50100 TestCodeunit implements TestInterface
{
    procedure MyProcedure(l: List of [Integer]);
    begin
        l.Add(1);
    end;

}

codeunit 50101 TestCodeunit2 implements TestInterface
{
    procedure MyProcedure(l: List of [Integer]);
    var
        l2: List of [Integer];
    begin
        l.Add(2);
    end;
}

Then need an extensible enum which is implementing the interface, so that other apps could add values. Since the value is an int and we will iterate the values, we also ensure the order of execution

enum 50100 MyEnum implements TestInterface
{
    Extensible = true;

    value(0; MyValue)
    {
        Implementation = TestInterface = TestCodeunit;
    }
    value(1; MyValue1)
    {
        Implementation = TestInterface = TestCodeunit2;
    }
}

And now the search for implementations and execute “MyProcedure”

namespace DefaultPublisher.InterfaceEventTest;

using Microsoft.Sales.Customer;

pageextension 50100 CustomerListExt extends "Customer List"
{
    trigger OnOpenPage();
    var
        myEnumValue: integer;
        implementation: Interface TestInterface;
        l: List of [Integer];
        listValue: Integer;
    begin

        foreach myEnumValue in Enum::MyEnum.Ordinals() do begin
            implementation := Enum::MyEnum.FromInteger(myEnumValue);;
            implementation.MyProcedure(l);
        end;

        foreach listValue in l do begin
            Message(Format(listValue));
        end;
    end;
}

Using this type of implementation you have a highly predictable solution. The order of values in the enum will always be the order of execution. And it doesn’t matter how much procedures you have inside of your interface, the users of your code only have to implement it and the don’t have to lookup in the documentation which events they have to subscribe.

6 thoughts on “Exploring Interface Implementation: A Journey from Events to Enums

  1. There are some points you should consider:
    1. Enums do not have a predictable order by design. You may assign any unique value to an ENUM-Value as you want. The order is only predictable if only you define the values.
    2. Interfaces in AL are nearly set in stone. If you need to change interfaces, you have to reimplement them with a new Enum and a new interface, while you need to support the old. if you have done this several times, it will become a little bit chaotic. Because of this i would avoid to use interfaces in AL whenever possible.

    • ad 1) the order of enums do not change with upgrades, while the order of event execution changes from time to time and is really not predictible. So in this case you can rely on that a BC Upgrade will not break anything.
      ad 2) there are solutions for this “problem” of course interfaces are set in stone otherwise it would be a big problem. Imagine you get a software update and your UI is changing completly evertime it would be a problem for you to find features. The same applies for Interfaces and the compiler. But what you can do (especially when you work with namespaces) you can version your interfaces and you could implement different versions of your interface in the same codeunit. Maybe I can write a blog about this to

      • 1) What i meant with the enum was: If you define an extensible enum X with the values A=1 and B=10, another extension could define (wanted or not) a Value C=5. Then the order is not predictable.
        And if Copilot is right, enum.Ordinals() returns in order of definition, not in order of Value, so sorting is unpredictable, if you extended the enum, you will not know in which order the values are read from the different extensions. If all this is true( i did not test it), you have first to put the values into a temporary table that is sorted by it’s ordinal and then loop with “findset/repeat until next” through the table and execute your interfaces.
        Assuming Copilot is right, i would think this solution is a little bit overdosed and will become depending on usage case an performance issue.

        2. If an interface is as easy as in your example, different versions might be no problem, but if you have “real” interfaces, you normally would have code that is used by multiple versions or required functionality is completely different. If you didn’t think about this issue in the first version, you will get an unmaintainable code.

      • 1) If you say you would have to create temp table I would really suggest you to have a look at data structures. Using temp tables is an obsolete strategy because the overhead is very high. I would rather use a sorted list instead. I know there is no sorted list in AL but you can write your own sorted list in 15 minutes. And for your case adding 5 between 1-10 might also be an behaviour you want to make possible. But at least for AppSource Apps you are only allowed to use numbers within your range (AppSourceCop prevents you from doing it) and for PTEs it’s your own responsibility so feel free to do what ever you like. But it’s still a bad habbit of a developer to take numbers outside of the app id ranges. And at least in projects where I’m responsible something like that will not go through review. Writing bad code can always destroy your app…

        2) What are real interfaces. I use interfaces very often and I always apply to principles on it, the first one is KISS (Keep it simple stupid) and the “Single responsibility principle” from the solid principles. Using this two principles you get very small objects with very simple code. You would need many objects for this solution but at least for SaaS objects are for free. And I’ve never seen someone hit the 50000 free objects.
        Of course if you take an old monolith app which has code units with thousand of lines you will not be able to apply Interfaces on it.

  2. Pingback: Moving code by using an interface – The BC Docs Librarian

  3. Pingback: Exploring Interface Implementation: A Journey from Events to Enums | Pardaan.com

Leave a Reply to Hans H. FiddelkeCancel reply