Site icon Patrick Schiefer

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.

Exit mobile version